Gravatar
我常常追忆未来
积分:1105
提交:188 / 408


$\newcommand\binom[2]{{#1 \choose #2}}$

我们先考虑暴力,暴力枚举每一个$i,j$暴力算$\binom{i}{j}$时间复杂度为$O(T*N^3)$,显然超时

然后我们发现$N,M \le2000$我们考虑使用组合数的递推公式预处理$\binom{0}{0}$到$\binom{2000}{2000}$。

这里说一下组合数递推公式$\binom{i}{j}=\binom{i-1}{j-1}+\binom{i-1}{j}$可以从组合意义上来理解 我们从$i$个数中选$j$个数,对于每一个数可以分为选与不选两种情况,选的时候即$\binom{i-1}{j-1}$,在剩下的$i-1$个数里选$j-1$个数,不选的时候即$\binom{i-1}{j}$,在剩下的$i-1$个数里选出$j$个数.

但是这样复杂度为$O(T*N^2+N^2)$,依旧无法通过,事实上我们需要一种不需要枚举$i,j$的方法才能通过。

我们发现对于多个询问,$k$始终为定值,于是考虑前缀和优化,设$ans_{i,j}$,在预处理时,若$k|\binom{i}{j}$,则将$ans_{i,j}+1$。然后套用二维前缀和。但是我们发现,二维前缀和递推时 $ans_{i,j}=ans_{i-1,j}+ans_{i,j-1},-ans_{i-1,j-1}+[k|\binom{i}{j}]$,在处理到边界时,我们会用未处理的值(因为此时$<j$)来更新。但是我们又发现

对于所有的 $0\leq i\leq n,0\leq j\leq \min \left ( i, m \right )$ 有多少对 $(i,j)$ 满足 $k|\binom{i}{j}$

于是我们直接将未处理的值设为同一行上一个值

由此即可AC这道题了!


点我展开看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
int t,n,m,k,c[2008][2008],ans[2008][2008];
void init(){
    c[0][0]=1;
    c[1][0]=c[1][1]=1;
    for(int i=2;i<=2000;i++){
        c[i][0]=1; 
        for(int j=1;j<=i;j++){
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;
            ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1];
            if(c[i][j]==0){
                ans[i][j]++;
            } 
        }
        ans[i][i+1]=ans[i][i]; 
    }
}
signed main(){
    cin>>t>>k;
    init(); 
    while(t--){
        cin>>n>>m;
        if(n<m){
            cout<<ans[n][n]<<"\n";
        } 
        else{
            cout<<ans[n][m]<<"\n"; 
        }
    }
    
    return 0;
}

题目2559  [NOIP 2016]组合数问题 AAAAAAAAAAAAAAAAAAAA      2      评论
2025-10-04 21:32:30    
Gravatar
llbc123
积分:13
提交:6 / 23

题意

题意:求∑i=1n∑j=1m[i=j](nmodi)⋅(mmodj)。

数据范围:1⩽n,m⩽109。

分析

这是一道简单的推式子题,但是实现比较恶心。

首先不妨设n⩽m(如果n>m交换一下就好了)

然后可以用容斥将题目拆成两个部分:

i=1∑nj=1∑m(nmodi)⋅(mmodj)−i=1∑n(nmodi)⋅(mmodi)

将两个求和拆开:

i=1∑n(nmodi)⋅j=1∑m(mmodj)−i=1∑n(nmodi)⋅(mmodi)

因为取模很难搞,所以可以用一个性质amodb=a−⌊ba⌋⋅b(取模的定义),将上式转换为:

i=1∑n(n−⌊in⌋⋅i)⋅j=1∑m(m−⌊jm⌋⋅j)−i=1∑n(n−⌊in⌋⋅i)⋅(m−⌊im⌋⋅i)

将括号拆开,可以得到:

(n2−i=1∑ni⋅⌊in⌋)⋅(m2−i=1∑mi⋅⌊im⌋)−i=1∑n(nm−mi⋅⌊in⌋−ni⋅⌊im⌋+i2⋅⌊in⌋⋅⌊im⌋)

此时我们的复杂度已经是O(n)了,然而这个复杂度仍然不足以通过本题。

要做这道题需要一个简单的技巧——整除分块。

我们很容易发现很多⌊in⌋的值是一样的,且所有的⌊in⌋为一个不下降子序列,呈块状分布

通过简单的计算可以得到,对于每个起点为i的块,它的值为⌊in⌋,终点为⌊⌊in⌋n⌋,然后我们就可以用O(n

)的算法计算∑i=1ni⋅⌊in⌋了:

主要步骤,对于每一个块[l,r],它乘号前面前缀和差分得到,即(1+2+⋯+r)−(1+2+⋯+(l−1)),乘号后面的就是⌊ln⌋。

代码:

inline long long sum1(long long x){

return x*(x+1)%mod*inv2%mod;

}

l=1,sum=n*n%mod;

while(l<=n){

r=n/(n/l);

sum=(sum-(sum1(r)-sum1(l-1)+mod)%mod*(n/l)%mod+mod)%mod;

l=r+1;

}

同样,∑i=1mi⋅⌊im⌋的值也可以用相同的方法求得。

考虑求∑i=1n(nm−mi⋅⌊in⌋−ni⋅⌊im⌋+i2⋅⌊in⌋⋅⌊im⌋),发现用整除分块完成它需要使区间[l,r]中所有的i都满足⌊in⌋与⌊im⌋相同。

可以用类似的方法,将上面代码中的r=n/(n/l);改为r=min(n/(n/l),m/(m/l));,然后就可以直接求值了!

且慢,虽然前三项nm,mi⋅⌊in⌋,ni⋅⌊im⌋都很容易求得,但是i2⋅⌊in⌋⋅⌊im⌋不是很容易求,因为(12+22+⋯+i2)不好处理。

这里有一个简单的结论:12+22+⋯+n2=6n(n+1)(2n+1),会在文后证明,现在先给出这一部分的代码:

inline long long sum2(long long x){

return x*(x+1)%mod*(2*x+1)%mod*inv6%mod;

}

l=1,tmp3=0;

while(l<=n){

long long a,b,c;

r=min(n/(n/l),m/(m/l));

a=(r-l+1)*n%mod*m%mod;

b=(sum1(r)-sum1(l-1)+mod)%mod*((n/l)*m%mod+(m/l)*n%mod)%mod;

c=(sum2(r)-sum2(l-1)+mod)%mod*(n/l)%mod*(m/l)%mod;

tmp3=(tmp3+a-b+c+mod)%mod;

l=r+1;

}

代码

记得开long long取模很恶心,如果错了多半是这种原因因为三个整数相乘会爆long long,因此除法用逆元实现(逆元提前求得)

#include<stdio.h>

const long long mod=19940417,inv2=9970209,inv6=3323403;

long long i,j,k,m,n,l,r,ans,tmp1,tmp2,tmp3;

inline long long min(long long a,long long b){

return a<b? a:b;

}

inline void swp(long long &a,long long &b){

a+=b,b=a-b,a-=b;

}

inline long long sum1(long long x){

return x*(x+1)%mod*inv2%mod;

}

inline long long sum2(long long x){

return x*(x+1)%mod*(2*x+1)%mod*inv6%mod;

}

int main(){

scanf("%lld%lld",&n,&m);

if(n>m)

swp(n,m);

l=1,tmp1=n*n%mod;

while(l<=n){

r=n/(n/l);

tmp1=(tmp1-(sum1(r)-sum1(l-1)+mod)%mod*(n/l)%mod+mod)%mod;

l=r+1;

}

l=1,tmp2=m*m%mod;

while(l<=m){

r=m/(m/l);

tmp2=(tmp2-(sum1(r)-sum1(l-1)+mod)%mod*(m/l)%mod+mod)%mod;

l=r+1;

}

l=1,tmp3=0;

while(l<=n){

long long a,b,c;

r=min(n/(n/l),m/(m/l));

a=(r-l+1)*n%mod*m%mod;

b=(sum1(r)-sum1(l-1)+mod)%mod*((n/l)*m%mod+(m/l)*n%mod)%mod;

c=(sum2(r)-sum2(l-1)+mod)%mod*(n/l)%mod*(m/l)%mod;

tmp3=(tmp3+a-b+c+mod)%mod;

l=r+1;

}

ans=(tmp1*tmp2%mod-tmp3+mod)%mod;

printf("%lld\n",ans);

return 0;

}

简单的证明

证明:

12+22+⋯+n2=6n(n+1)(2n+1)

(这个式子可以用简单构造法与数学归纳法证明,由于大部分题解都是用的数学归纳法,且用数学归纳法读者自证不难,因此这里使用简单构造法)

由于有这样一个式子:i2=i2−i+i(i−1)⋅i+i,因此我们可以将这个拆开:

12+22+⋯+n2=i=1∑ni2=i=1∑n(i−1)⋅i+i=1∑ni

然后乘上31:

i=1∑ni2=31i=1∑n(i−1)⋅i⋅3+i=1∑ni

构造一下:

i=1∑ni2=31i=1∑n(i−1)⋅i⋅((i+1)−(i−2))=31i=1∑n(−(i−2)⋅(i−1)⋅i+(i−1)⋅i⋅(i+1))+i=1∑ni

把它们全部展开:

i=1∑ni2=31(−(−1)⋅0⋅1+0⋅1⋅2−0⋅1⋅2+1⋅2⋅3−1⋅2⋅3+⋯(n−1)⋅n⋅(n+1))+2n⋅(n+1)

发现括号里的很多项都可以抵消:

i=1∑ni2=3(n−1)⋅n⋅(n+1)+2n⋅(n+1)=6n⋅(n+1)⋅(2(n−1)+3)=6n⋅(n+1)⋅(2n+1)

证毕。

来自洛谷

如有侵权立即删除


题目3668  [清华集训 2012] 模积和      1      评论
2025-09-28 20:59:31    
Gravatar
llbc1234
积分:44
提交:21 / 80

题意

题意:求∑i=1n∑j=1m[i=j](nmodi)⋅(mmodj)。

数据范围:1⩽n,m⩽109。

分析

这是一道简单的推式子题,但是实现比较恶心。

首先不妨设n⩽m(如果n>m交换一下就好了)

然后可以用容斥将题目拆成两个部分:

i=1∑nj=1∑m(nmodi)⋅(mmodj)−i=1∑n(nmodi)⋅(mmodi)

将两个求和拆开:

i=1∑n(nmodi)⋅j=1∑m(mmodj)−i=1∑n(nmodi)⋅(mmodi)

因为取模很难搞,所以可以用一个性质amodb=a−⌊ba⌋⋅b(取模的定义),将上式转换为:

i=1∑n(n−⌊in⌋⋅i)⋅j=1∑m(m−⌊jm⌋⋅j)−i=1∑n(n−⌊in⌋⋅i)⋅(m−⌊im⌋⋅i)

将括号拆开,可以得到:

(n2−i=1∑ni⋅⌊in⌋)⋅(m2−i=1∑mi⋅⌊im⌋)−i=1∑n(nm−mi⋅⌊in⌋−ni⋅⌊im⌋+i2⋅⌊in⌋⋅⌊im⌋)

此时我们的复杂度已经是O(n)了,然而这个复杂度仍然不足以通过本题。

要做这道题需要一个简单的技巧——整除分块。

我们很容易发现很多⌊in⌋的值是一样的,且所有的⌊in⌋为一个不下降子序列,呈块状分布

通过简单的计算可以得到,对于每个起点为i的块,它的值为⌊in⌋,终点为⌊⌊in⌋n⌋,然后我们就可以用O(n

)的算法计算∑i=1ni⋅⌊in⌋了:

主要步骤,对于每一个块[l,r],它乘号前面前缀和差分得到,即(1+2+⋯+r)−(1+2+⋯+(l−1)),乘号后面的就是⌊ln⌋。

代码:

inline long long sum1(long long x){

return x*(x+1)%mod*inv2%mod;

}

l=1,sum=n*n%mod;

while(l<=n){

r=n/(n/l);

sum=(sum-(sum1(r)-sum1(l-1)+mod)%mod*(n/l)%mod+mod)%mod;

l=r+1;

}

同样,∑i=1mi⋅⌊im⌋的值也可以用相同的方法求得。

考虑求∑i=1n(nm−mi⋅⌊in⌋−ni⋅⌊im⌋+i2⋅⌊in⌋⋅⌊im⌋),发现用整除分块完成它需要使区间[l,r]中所有的i都满足⌊in⌋与⌊im⌋相同。

可以用类似的方法,将上面代码中的r=n/(n/l);改为r=min(n/(n/l),m/(m/l));,然后就可以直接求值了!

且慢,虽然前三项nm,mi⋅⌊in⌋,ni⋅⌊im⌋都很容易求得,但是i2⋅⌊in⌋⋅⌊im⌋不是很容易求,因为(12+22+⋯+i2)不好处理。

这里有一个简单的结论:12+22+⋯+n2=6n(n+1)(2n+1),会在文后证明,现在先给出这一部分的代码:

inline long long sum2(long long x){

return x*(x+1)%mod*(2*x+1)%mod*inv6%mod;

}

l=1,tmp3=0;

while(l<=n){

long long a,b,c;

r=min(n/(n/l),m/(m/l));

a=(r-l+1)*n%mod*m%mod;

b=(sum1(r)-sum1(l-1)+mod)%mod*((n/l)*m%mod+(m/l)*n%mod)%mod;

c=(sum2(r)-sum2(l-1)+mod)%mod*(n/l)%mod*(m/l)%mod;

tmp3=(tmp3+a-b+c+mod)%mod;

l=r+1;

}

代码

记得开long long取模很恶心,如果错了多半是这种原因因为三个整数相乘会爆long long,因此除法用逆元实现(逆元提前求得)

#include<stdio.h>

const long long mod=19940417,inv2=9970209,inv6=3323403;

long long i,j,k,m,n,l,r,ans,tmp1,tmp2,tmp3;

inline long long min(long long a,long long b){

return a<b? a:b;

}

inline void swp(long long &a,long long &b){

a+=b,b=a-b,a-=b;

}

inline long long sum1(long long x){

return x*(x+1)%mod*inv2%mod;

}

inline long long sum2(long long x){

return x*(x+1)%mod*(2*x+1)%mod*inv6%mod;

}

int main(){

scanf("%lld%lld",&n,&m);

if(n>m)

swp(n,m);

l=1,tmp1=n*n%mod;

while(l<=n){

r=n/(n/l);

tmp1=(tmp1-(sum1(r)-sum1(l-1)+mod)%mod*(n/l)%mod+mod)%mod;

l=r+1;

}

l=1,tmp2=m*m%mod;

while(l<=m){

r=m/(m/l);

tmp2=(tmp2-(sum1(r)-sum1(l-1)+mod)%mod*(m/l)%mod+mod)%mod;

l=r+1;

}

l=1,tmp3=0;

while(l<=n){

long long a,b,c;

r=min(n/(n/l),m/(m/l));

a=(r-l+1)*n%mod*m%mod;

b=(sum1(r)-sum1(l-1)+mod)%mod*((n/l)*m%mod+(m/l)*n%mod)%mod;

c=(sum2(r)-sum2(l-1)+mod)%mod*(n/l)%mod*(m/l)%mod;

tmp3=(tmp3+a-b+c+mod)%mod;

l=r+1;

}

ans=(tmp1*tmp2%mod-tmp3+mod)%mod;

printf("%lld\n",ans);

return 0;

}

简单的证明

证明:

12+22+⋯+n2=6n(n+1)(2n+1)

(这个式子可以用简单构造法与数学归纳法证明,由于大部分题解都是用的数学归纳法,且用数学归纳法读者自证不难,因此这里使用简单构造法)

由于有这样一个式子:i2=i2−i+i(i−1)⋅i+i,因此我们可以将这个拆开:

12+22+⋯+n2=i=1∑ni2=i=1∑n(i−1)⋅i+i=1∑ni

然后乘上31:

i=1∑ni2=31i=1∑n(i−1)⋅i⋅3+i=1∑ni

构造一下:

i=1∑ni2=31i=1∑n(i−1)⋅i⋅((i+1)−(i−2))=31i=1∑n(−(i−2)⋅(i−1)⋅i+(i−1)⋅i⋅(i+1))+i=1∑ni

把它们全部展开:

i=1∑ni2=31(−(−1)⋅0⋅1+0⋅1⋅2−0⋅1⋅2+1⋅2⋅3−1⋅2⋅3+⋯(n−1)⋅n⋅(n+1))+2n⋅(n+1)

发现括号里的很多项都可以抵消:

i=1∑ni2=3(n−1)⋅n⋅(n+1)+2n⋅(n+1)=6n⋅(n+1)⋅(2(n−1)+3)=6n⋅(n+1)⋅(2n+1)

证毕。


题目3668  [清华集训 2012] 模积和 AAAAAAAAAA      1      评论
2025-09-28 20:51:34    
Gravatar
llbc1234
积分:44
提交:21 / 80

题目大意

按照格雷码的生成方式,输出 n 位格雷码的第 k 号二进制串。(从 0 开始编号)

思路

填完第 k 个 n 位格雷码的第 1 位后,把它转化成某一个 n−1 位格雷码继续填这个 n−1 位格雷码的第 1 位,以此类推。

如何确定转化成第几个 n−1 位格雷码:

当 k<2n−1,因为这个 n 位格雷码的前 2n−1 个二进制串的后 n−1 位是由 n−1 位格雷码正序排列而成的,所以 k 保持不变。

当 k≥2n−1,因为这个 n 位格雷码的后 2n−1 个二进制串的后 n−1 位是由 n−1 位格雷码逆序排列而成的,所以 k 要先减去 2n−1,变成 n−1 位格雷码,再翻转才能保证是逆序的,即 k=r-k+l



题目3289  [CSP 2019S]格雷码 AAAAAAAAAAAAAAAAAAAA      4      3 条 评论
2025-09-25 21:06:01    
Gravatar
hsl_beat
积分:217
提交:34 / 52

不妨设 \( n \leq m \)。 那原式:

$= \sum_{i = 1} ^ n (n \bmod i) \times \sum_{j = 1} ^ m (m \bmod j) - \sum_{i = 1} ^ n (n \bmod i) \times (m \bmod i)$


$= \sum_{i = 1} ^ n \left( n - \left\lfloor \frac{n}{i} \right\rfloor \times i \right) \times \sum_{j = 1} ^ m \left( m - \left\lfloor \frac{m}{j} \right\rfloor \times j \right) - \sum_{i = 1} ^ n \left( n - \left\lfloor \frac{n}{i} \right\rfloor \times i \right) \left( m - \left\lfloor \frac{m}{i} \right\rfloor \times i \right)$


$= \sum_{i = 1} ^ n \left( n - \left\lfloor \frac{n}{i} \right\rfloor \times i \right) \times \sum_{j = 1} ^ m \left( m - \left\lfloor \frac{m}{j} \right\rfloor \times j \right) - \sum_{i = 1} ^ n \left( nm - n \times i \times \left\lfloor \frac{m}{i} \right\rfloor - m \times i \times \left\lfloor \frac{n}{i} \right\rfloor + i ^ 2 \times \left\lfloor \frac{n}{i} \right\rfloor \times \left\lfloor \frac{m}{i} \right\rfloor \right)$


显然对这 3 坨数论分块就可以了


 https://www.luogu.me/article/v919l2og

题目3668  [清华集训 2012] 模积和 AAAAAAAAAA      7      2 条 评论
2025-09-25 20:59:41    
Gravatar
hsl_beat
积分:217
提交:34 / 52


最小割必刷题!



首先我们先把棋盘看作二分图黑白染色,比如我们可以把横纵坐标加起来为奇数的看作黑,偶数看作白。



考虑到一个黑色节点只会对它四周的节点产生影响,不难想到我们直接把把源点往所有黑点连边权为这个点在棋盘上的数的边,白点同理,只是往汇点连边,然后相邻的方格之间连无穷大的边,这样这些边就不会被算在割边中。



那我们对于舍弃一个点,就是把这个点往源点或汇点的边割掉,dinic跑最小割即可。(ISAP不会写QAQ)



题目734  [网络流24题] 方格取数问题 AAAAAAAAAAA      7      1 条 评论
2025-09-25 20:49:45