容斥专题总结
A:How many integers can you find
过于水,但是有细节
\[\ \]
\[\ \]
B: Eddy's爱好
枚举k的值,直接开k次方根,为了防止爆精度,我手写了一下
然后就是对于因数容斥,或者是直接套莫比乌斯系数
const int N=4e5+10; const ll INF=(1ll<<62)-1; ll n; ll qsm(ll x,ll k) { ll res=1; while(k) { if(k&1) { double t=(double)res*x; if(t>INF) { res=INF; break; } else res=res*x; } double t=(double)x*x; if(t>INF) x=INF; else x=x*x; k>>=1; } return res; } ll yroot(ll x,ll y){ ll l=1,r=2e9,res=1; while(l<=r) { ll mid=(l+r)>>1; if(qsm(mid,y)<=x) res=mid,l=mid+1; else r=mid-1; } return res; } ll dp[100]; int main() { while(~scanf("%lld",&n)) { ll res=0; rep(i,2,60) dp[i]=yroot(n,i)-1; drep(i,60,2) for(int j=i+i;j<=60;j+=i) dp[i]-=dp[j];//k=i的情况会在j中被多次计算 rep(i,2,60) res+=dp[i]; printf("%lld\n",res+1); } }
\[ \ \]
\[ \ \]
C: Coprime
提出n,m的质因数,然后二分答案,直接二进制枚举来容斥
const int N=4e5+10; const ll INF=(1ll<<62)-1; int k; int fac[N],n; void Div(int x) { for(int i=2;i*i<=x;++i) if(x%i==0) { fac[n++]=i; while(x%i==0) x/=i; } if(x>1) fac[n++]=x; } ll Check(ll mid){ ll res=0; rep(S,0,(1<<n)-1) { ll t=1,cnt=0; rep(i,0,n-1) if(S&(1<<i)) cnt^=1,t=t*fac[i]; if(cnt) res-=mid/t; else res+=mid/t; } return res; } int main(){ rep(kase,1,rd()) { n=0;Div(rd()),Div(rd()); k=rd(); sort(fac,fac+n); n=unique(fac,fac+n)-fac; ll l=1,r=1e15,res=-1; while(l<=r) { ll mid=(1ll*l+1ll*r)>>1; if(Check(mid)>=k) res=mid,r=mid-1; else l=mid+1; } printf("Case %d: %lld\n",kase,res); } }
\[ \ \]
\[ \ \]
D: GCD
首先外层的区间可以容斥
然后是对于每个\(1 \leq i \leq n\)二进制枚举容斥\(1..m\)中互质的个数
const int N=1e5+10; const ll INF=(1ll<<62)-1; int a,b,c,d,k; vector <int> fac[N]; ll Solve(int n,int m,int k) { if(!k||!n||!m) return 0; n/=k,m/=k; if(n<m) swap(n,m); ll res=0; rep(i,1,n) { int t=min((int)i,m); int k=fac[i].size(); rep(j,0,(1<<k)-1){ ll x=1,cnt=0; rep(o,0,k-1) if(j&(1<<o)) x=x*fac[i][o],cnt^=1; if(cnt) res-=t/x; else res+=t/x; } } return res; } int main(){ rep(i,2,N-1) if(!fac[i].size()) for(int j=i;j<N;j+=i) fac[j].push_back((int)i); rep(kase,1,rd()) { a=rd(),b=rd(),c=rd(),d=rd(),k=rd(); ll res=Solve(b,d,k)-Solve(a-1,d,k)-Solve(b,c-1,k)+Solve(a-1,c-1,k); printf("Case %d: %lld\n",kase,res); } }
\[\ \]
\[ \ \]
E: Co-prime
基本思想之前都出现过了,跳过吧
\[ \ \]
\[ \ \]
F: Frogs
这个题还是非常有点东西的对吧
首先你要知道这样一件事
对于任何的\(gcd(n,m)=1\),$\lbrace n*i mod m|i \in Z \rbrace \(=\){0,1,..,m-1}$
然后其实对于任意\(gcd(n,m)=g\),$\lbrace ni mod m|i \in Z \rbrace \(=\){0,g,g2,..,m-1}$
所以每个青蛙能走到的地方就确定了,设这些\(gcd\)的值为\(a[1..n]\)
这时候我们模拟一下二进制枚举的过程,即取出其中的一些数,求出他们的\(lcm(lowest \ common \ multiple)\),当数的个数为奇数时加,偶数时减
即当你每次多选择一个数时,系数的符号取相反数,而\(lcm\)的情况数并不多,也就是\(m\)的因子个数
所以直接dp转移求系数即可
const int N=1e4+10; ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b); } int n,m; int a[N]; int fac[N],c; ll dp[N]; int lcm[2000][2000]; int gcd(int a,int b){ return b==0?a:gcd(b,a%b); } int main(){ rep(kase,1,rd()) { n=rd(),m=rd(); rep(i,1,n) a[i]=gcd(rd(),m); sort(a+1,a+n+1); n=unique(a+1,a+n+1)-a-1; c=0; rep(i,1,sqrt(m+0.5)) if(m%i==0) { fac[++c]=i; if(i*i!=m) fac[++c]=m/i; } sort(fac+1,fac+c+1); rep(i,1,c) dp[i]=0; rep(i,1,n) a[i]=lower_bound(fac+1,fac+c+1,a[i])-fac; rep(i,1,c) rep(j,1,c) lcm[i][j]=lower_bound(fac+1,fac+c+1,fac[i]/gcd(fac[i],fac[j])*fac[j])-fac; rep(i,1,n) { int x=a[i]; drep(j,c,1) { dp[lcm[x][j]]-=dp[j]; } dp[a[i]]++; } ll ans=0; rep(i,1,c) ans+=1ll*dp[i]*(m/fac[i])*(m-fac[i])/2; printf("Case #%d: %lld\n",kase,ans); } }
\[ \ \]
\[ \ \]
\[ \ \]
\[ \ \]
G: The Monkey King
刚开始做的时候没想到可以\(n\ ln \ n\)写,头都想破了。。。
枚举大猴子拿了\(x\)个,然后容斥其他人拿的比他多的情况
统计时候,枚举有至少\(i\)个人比他多,答案是\(C(n+m-x*(i+1)-1,m-2)\)
其实是插板法求组合,然后就可以顺利的容斥了
int n,m; ll po[N]={1},Inv[N]={1,1}; ll C(int n,int m){ if(n<0||m<0||n<m) return 0; return po[n]*Inv[m]%P*Inv[n-m]%P; } ll Solve(int x) { ll ans=C(n+m-x-2,m-2); rep(i,1,min(m-1,n/x-1)) { if(i&1) (ans=ans-C(m-1,i)*C(n+m-x*(i+1)-2,m-2))%=P; else ans=(ans+C(m-1,i)*C(n+m-x*(i+1)-2,m-2))%P; } return ans=(ans%P+P)%P; } int main(){ rep(i,1,N-1) po[i]=po[i-1]*i%P; rep(i,2,N-1) Inv[i]=(P-P/i)*Inv[P%i]%P; rep(i,1,N-1) Inv[i]=Inv[i]*Inv[i-1]%P; rep(kase,1,rd()) { n=rd(),m=rd(); if(m==1) { puts("1"); continue; } ll ans=0; rep(i,(n-1)/m+1,n) ans+=Solve(i); ans%=P; ans=(ans%P+P)%P; printf("%lld\n",ans); } }
\[ \ \]
\[ \ \]
H: Y sequence
这个题是真的没有素质
首先它和B题比就多了个二分,但如果真的写二分就TLE了! 必须写迭代
其次是不能手写开次方根了,只能用pow函数,而且还会爆精度
const ll INF=(1ll<<62)-1; ll k; int r; int w[100],notpri[100],pri[100],cp; int num[100],maxpri[100],nc; inline ll Solve(ll n) { ll res=0; rep(i,1,nc) if(maxpri[i]<=r) res+=1ll*w[i]*((ll)pow(n+0.1,1.0/num[i])-1); return n-res-1; } int main() { rep(i,2,65) { if(!notpri[i]) { pri[++cp]=i; for(int j=i+i;j<=65;j+=i) notpri[j]=1; } } rep(i,2,65) { int x=i,t=-1,ma=0; rep(j,1,cp) { int cnt=0; while(x%pri[j]==0) cnt++,x/=pri[j]; if(cnt>1) { t=0; break; } if(cnt) t=-t,ma=max(ma,pri[j]); } if(t) w[++nc]=t,num[nc]=i,maxpri[nc]=ma; } rep(kase,1,rd()){ scanf("%lld",&k);r=rd(); ll ans=k; while(1) { ll tmp=Solve(ans); if(k==tmp) break; ans+=k-tmp; } printf("%lld\n",ans); } }
\[ \ \]
\[ \ \]
I: MG loves string
目测这题做法也奇多无比,我用了矩阵乘法
首先26个字母组成多个环,环长的种类最多只有6种
所以我直接将这6种环长状压,矩阵快速幂即可
const int N=1e5+10,P=1e9+7; int n; int nxt[26],c[26]; char str[30]; int vis[27]; int A,k[N],id[N]; int cnt; ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); } struct Mat{ int a[1<<6][1<<6]; void init(){ rep(i,0,A) rep(j,0,A) a[i][j]=0; } void Get1(){ rep(i,0,A) a[i][i]=1; } Mat operator * (const Mat x) const{ Mat res; res.init(); rep(i,0,A) rep(j,0,A) rep(o,0,A) (res.a[i][o]+=1ll*a[i][j]*x.a[j][o]%P)%=P; return res; } }res,x; ll f[1][1<<6],ans[1][1<<6]; int main(){ rep(kase,1,rd()) { n=rd(); scanf("%s",str); rep(i,0,25) nxt[i]=str[i]-'a'; cnt=0; memset(vis,0,sizeof vis); rep(i,0,25) { int p=i;c[i]=0; do { p=nxt[p]; c[i]++; } while(p!=i); if(!vis[c[i]]) { vis[c[i]]=1; id[c[i]]=cnt; k[cnt++]=c[i]; } } A=(1<<cnt)-1; x.init(),res.init();res.Get1(); rep(i,0,A) { rep(j,0,25) { x.a[i][i|(1<<id[c[j]])]++; } } while(n) { if(n&1) res=res*x; x=x*x; n>>=1; } memset(f,0,sizeof f);memset(ans,0,sizeof ans); f[0][0]=1; rep(i,0,0) rep(j,0,A) rep(o,0,A) (ans[i][o]+=1ll*f[i][j]*res.a[j][o]%P)%=P; ll Ans=0; rep(S,0,A) { ll t=1; rep(i,0,cnt-1) if(S&(1<<i)) t=t*k[i]/gcd(t,k[i]); (Ans+=t*ans[0][S]%P)%=P; } printf("%lld\n",Ans); } }
\[ \ \]
\[ \ \]
J: Harry And Magic Box
dp容斥就好了,\(dp[i][j]\)表示\(i\)行\(j\)列的都放了的方案数,枚举小于\(i\)或小于\(j\)的方案减掉就好了
int n,m,q; ll Inv[N*N]={1,1},po[N*N]={1}; ll B[N][N],p2[N*N]={1}; inline ll C(int n,int m){ if(n<0||m<0||n<m) return 0; return po[n]*Inv[m]%P*Inv[n-m]%P; } int main(){ rep(i,1,N*N-1) po[i]=po[i-1]*i%P,p2[i]=p2[i-1]*2%P; rep(i,2,N*N-1) Inv[i]=(P-P/i)*Inv[P%i]%P; rep(i,1,N*N-1) Inv[i]=Inv[i]*Inv[i-1]%P; rep(i,0,50) { rep(j,0,50) { B[i][j]=p2[i*j]; rep(a,0,i) { rep(b,0,j) if(a!=i||b!=j) { (B[i][j]-=C(i,a)*C(j,b)%P*B[a][b]%P)%=P; } } B[i][j]=(B[i][j]%P+P)%P; } } while(~scanf("%d%d",&n,&m)) printf("%lld\n",B[n][m]); }
\[ \ \]
\[ \ \]
\[ \ \]
K: Count the Grid
看起来非常吓人,但其实还好
总体思想就是给定每个点一个\(lim\)值,求出矩阵在限制条件下的方案数容斥
然后就是二进制枚举,对于每个被枚举到的\(lim\)值要减一
其实就是把选出的数全部在\(lim\)值以下的方案减去,这样得到的就是最大值为\(lim\)的方案了
如何统计矩阵的\(lim\)值,直接离散然后循环赋值就好了,但是由于被卡常了所以我不得不加了一些优化
代码写的比较诡异不建议看,拿去对拍可以
#include<bits/stdc++.h> using namespace std; #define reg register typedef long long ll; #define rep(i,a,b) for(reg int i=a;i<=b;++i) #define drep(i,a,b) for(reg int i=a;i>=b;--i) char IO; int rd(){ int s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())); return f?-s:s; } const int N=110,P=1e9+7; int n,m,k,q; int hx[N],cx,hy[N],cy; struct REC{ int x1,y1,x2,y2,lim; void Get() { x1=rd(),y1=rd(); x2=rd(),y2=rd(); hx[++cx]=x1,hx[++cx]=x2; if(x1>1) hx[++cx]=x1-1; if(x1<n) hx[++cx]=x1+1; if(x2>1) hx[++cx]=x2-1; if(x2<n) hx[++cx]=x2+1; hy[++cy]=y1,hy[++cy]=y2; if(y1>1) hy[++cy]=y1-1; if(y1<m) hy[++cy]=y1+1; if(y2>1) hy[++cy]=y2-1; if(y2<m) hy[++cy]=y2+1; lim=rd(); } void Hash() { x1=lower_bound(hx+1,hx+cx+1,x1)-hx; x2=lower_bound(hx+1,hx+cx+1,x2)-hx; y1=lower_bound(hy+1,hy+cy+1,y1)-hy; y2=lower_bound(hy+1,hy+cy+1,y2)-hy; } }R[N]; inline ll qsm(reg ll x,reg int k){ reg ll res=1; for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P; return res; } int a[N][N],b[N][N],vis[N][N]; ll c[2][N][N]; ll Solve() { ll ans=1; rep(i,1,cx) rep(j,1,cy) a[i][j]=k; for(reg int i=0; i<q; ++i) rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) a[x][y]=min(a[x][y],R[i].lim); rep(i,1,cx) rep(j,1,cy) ans=ans*qsm(a[i][j],(hx[i]-hx[i-1])*(hy[j]-hy[j-1]))%P; return ans; } int main(){ int T=rd(); rep(kase,1,T){ n=rd(),m=rd(),k=rd(),q=rd(); hx[cx=1]=n,hy[cy=1]=m; hx[++cx]=1,hy[++cy]=1; rep(i,0,q-1) R[i].Get(); sort(hx+1,hx+cx+1),sort(hy+1,hy+cy+1); cx=unique(hx+1,hx+cx+1)-hx-1,cy=unique(hy+1,hy+cy+1)-hy-1; rep(i,0,q-1) R[i].Hash(); rep(i,1,cx) rep(j,1,cy) b[i][j]=k; for(reg int i=0; i<q; ++i) rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) b[x][y]=min(b[x][y],R[i].lim); rep(i,1,cx) rep(j,1,cy){ c[0][i][j]=qsm(b[i][j],(hx[i]-hx[i-1])*(hy[j]-hy[j-1]))%P; c[1][i][j]=qsm(b[i][j]-1,(hx[i]-hx[i-1])*(hy[j]-hy[j-1]))%P; a[i][j]=b[i][j]; } int A=(1<<q)-1; ll ans=0; rep(S,0,A) { int cnt=0; rep(i,0,q-1) if(S&(1<<i)) { cnt^=1; rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) if(a[x][y]==R[i].lim) a[x][y]=R[i].lim-1,vis[x][y]=R[i].lim; } ll res=1; rep(i,1,cx) rep(j,1,cy) res=res*c[b[i][j]-a[i][j]][i][j]%P; if(cnt) ans-=res; else ans+=res; //cout<<S<<" "<<Solve()<<endl; rep(i,0,q-1) if(S&(1<<i)) rep(x,R[i].x1,R[i].x2) rep(y,R[i].y1,R[i].y2) if(vis[x][y]) a[x][y]=vis[x][y],vis[x][y]=0; } ans=(ans%P+P)%P; printf("Case #%d: %lld\n",kase,ans); } }
\[ \ \]
\[\ \]
L: Visible Trees
和前面的好几道题基本同理
#include<bits/stdc++.h> using namespace std; #define reg register typedef long long ll; #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i) char IO; int rd(){ int s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())); return f?-s:s; } const int N=1e5+10,P=1e9+7; int n,m,k,q; vector <int> fac[N]; ll Solve(int n,int m) { ll res=0; rep(i,1,n) { int k=fac[i].size(); int x=min(i,m); rep(S,0,(1<<k)-1) { int cnt=0,t=1; rep(j,0,k-1) if(S&(1<<j)) t*=fac[i][j],cnt^=1; if(cnt) res-=x/t; else res+=x/t; } } //cout<<res<<endl; return res; } int main(){ rep(i,2,N-1) if(!fac[i].size()) for(int j=i;j<N;j+=i) fac[j].push_back((int)i); rep(kase,1,rd()){ n=rd(),m=rd(); ll ans=Solve(n,m)+Solve(m,n)-1; printf("%lld\n",ans); } }
\[ \ \]
\[ \ \]
M: A Simple Chess
跳马?
首先我们讨论在空的\(n,m\)棋盘上跳的方案数
由于必须是前向跳,所以每次坐标都会增加,设横着跳\(a\)次,纵着跳\(b\)次
则有
\[2*a+b=n\]
\[2*b+a=m\]
解得方程的两根即可得到a,b的值,若不是整数则无解
然后其实方案数就是\(C(a+b,a)\)
容斥的过程依旧是dp转移,每次转移时会多经过一个障碍点,容斥系数取反
但是由于这题范围较大,组合数不好求,鉴于模数只有110119,所以可以用\(Lucas\)定理
即\(C(n,m) mod \ p =C(n \ mod \ p,m \ mod \ p)*(n/p,m/p) mod \ p\)
预处理后直接做
#include<bits/stdc++.h> using namespace std; #define reg register typedef long long ll; #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i) char IO; ll rd(){ ll s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())); return f?-s:s; } const int N=110,P=110119; ll n,m; int r; struct NODE { ll x,y; bool operator < (const NODE __ )const{ return x<__.x; } bool operator == (const NODE __) const{ return x==__.x&&y==__.y; } }A[N]; int k; ll po[P]={1},Inv[P]={1,1}; ll C(ll n,ll m){ if(n<0||m<0||n<m) return 0; if(n<P&&m<P) return po[n]*Inv[m]%P*Inv[n-m]%P; return C(n%P,m%P)*C(n/P,m/P)%P; } ll Calc(ll n,ll m) { if((n+m-2)%3!=0) return 0; if((2*n-m-1)%3!=0) return 0; ll a=(n+m-2)/3,b=(2*n-m-1)/3; return C(a,b); } //(i+j-2)/3 //(2*i-j-1)/3 ll dp[N]; int kase; int main(){ rep(i,1,P-1) po[i]=po[i-1]*i%P; rep(i,2,P-1) Inv[i]=(P-P/i)*Inv[P%i]%P; rep(i,1,P-1) Inv[i]=Inv[i-1]*Inv[i]%P; while(~scanf("%lld%lld%d",&n,&m,&r)) { k=0; A[++k]=(NODE){1ll,1ll}; int f=0; rep(i,1,r) { ll x=rd(),y=rd(); A[++k]=(NODE){x,y}; if((x==n&&y==m)||(x==1&&y==1)) f=1; } if(f) { printf("Case #%d: 0\n",++kase); continue; } if(n==1&&m==1) { printf("Case #%d: 1\n",++kase); continue; } A[++k]=(NODE){n,m}; sort(A+1,A+k+1); k=unique(A+1,A+k+1)-A-1; memset(dp,0,sizeof dp); dp[1]=1; rep(i,1,k) rep(j,i+1,k) if(A[j].x>A[i].x&&A[j].y>A[i].y) (dp[j]-=dp[i]*Calc(A[j].x-A[i].x+1,A[j].y-A[i].y+1)%P)%=P; ll ans=dp[k]; ans=(P-ans)%P; ans=(ans%P+P)%P; printf("Case #%d: %lld\n",++kase,ans); } }
\[ \ \]
\[ \ \]
N: TrickGCD
看到这个题就想到对于整个序列的\(gcd\)的值容斥
如何快速求出某个值限制下的序列方案数呢?
直接前缀和,\(n \ln n\)枚举,快速幂即可,最后乘一个莫比乌斯系数
#include<cstdio> #include<cctype> #include<algorithm> #include<iostream> using namespace std; #define reg register typedef long long ll; #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) char IO; int rd(){ int s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())) ; return f?-s:s; } const int N=2e5+10,P=1e9+7; int n; int a[N]; inline ll qsm(reg ll x,reg int k) { if(x==1||k<=0) return 1; reg ll res=1; for(;k;k>>=1,x=x*x%P) ((k&1)&&(res=res*x%P)); return res; } int pri[N],cp; int notpri[N]; int mo[N]; int main(){ rep(i,2,N-1) { if(!notpri[i]) pri[++cp]=i,mo[i]=1; rep(j,1,cp){ int t=i*pri[j]; if(t>=N) break; notpri[t]=1; if(i%pri[j]==0) { mo[t]=0; break; } mo[t]=-mo[i]; } } int T=rd(); rep(kase,1,T) { int Up=1e9,ma=0; scanf("%d",&n); rep(i,1,n) { int x; scanf("%d",&x); a[x]++; Up=min(Up,x); ma=max(ma,x); } rep(i,Up,N-1) a[i]+=a[i-1]; ll ans=0; for(reg int i=2;i<=Up;++i) if(mo[i]) { ll res=1; rep(j,1,ma/i) { res=res*qsm(j,a[(j+1)*i-1]-a[j*i-1])%P; } ans+=mo[i]*res; } rep(i,Up,N-1) a[i]=0; ans=(ans%P+P)%P; printf("Case #%d: %lld\n",kase,ans); } }
\[ \ \]
\[ \ \]
O: Puzzled Elena
大水题
#include<cstdio> #include<cctype> #include<algorithm> #include<iostream> #include<vector> #include<cstring> using namespace std; #define reg register typedef long long ll; #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) char IO; int rd(){ int s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())) ; return f?-s:s; } const int N=1e5+10,P=1e9+7; int n; struct Edge{ int to,nxt; }e[N<<1]; int head[N],ecnt; void AddEdge(int u,int v){ e[++ecnt]=(Edge){v,head[u]}; head[u]=ecnt; } vector <int> fac[N]; int a[N],ans[N]; int c[N]; void dfs(int u,int f) { int n=fac[a[u]].size(); ans[u]=0; rep(S,0,(1<<n)-1) { int t=1,cnt=0; rep(i,0,n-1) if(S&(1<<i)) cnt^=1,t*=fac[a[u]][i]; if(cnt) ans[u]+=c[t]; else ans[u]-=c[t]; } rep(S,0,(1<<n)-1) { int t=1; rep(i,0,n-1) if(S&(1<<i)) t*=fac[a[u]][i]; c[t]++; } for(int i=head[u];i;i=e[i].nxt) { int v=e[i].to; if(v==f) continue; dfs(v,u); } rep(S,0,(1<<n)-1) { int t=1,cnt=0; rep(i,0,n-1) if(S&(1<<i)) cnt^=1,t*=fac[a[u]][i]; if(cnt) ans[u]-=c[t]; else ans[u]+=c[t]; } } int kase; int main(){ rep(i,2,N-1) if(!fac[i].size()) for(int j=i;j<N;j+=i) fac[j].push_back((int)i); while(~scanf("%d",&n)) { memset(c,0,sizeof c);memset(head,0,sizeof head);ecnt=0; rep(i,2,n) { int u=rd(),v=rd(); AddEdge(u,v); AddEdge(v,u); } rep(i,1,n) a[i]=rd(); dfs(1,0); printf("Case #%d:",++kase); rep(i,1,n) printf(" %d",ans[i]); puts(""); } }
\[ \ \]
\[ \ \]
P: Just Random
\(O(1)\) 的题为甚么在最后一题
对于两个数\(n,m(n \leq m)\),它们累和为\(x\)的方案数其实是有一定规律的
当\(x\leq n\)时,方案数为\(x+1\)
当\(n\leq x \leq m\)时,方案数为\(n+1\)
当\(m\leq x\)时,方案数为\(max\{0,n+m-x+1\}\)
然后就ojbk了
#include<cstdio> #include<cctype> #include<algorithm> #include<iostream> #include<cstring> using namespace std; #define reg register typedef long long ll; #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i) #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i) char IO; int rd(){ int s=0,f=0; while(!isdigit(IO=getchar())) if(IO=='-') f=1; do s=(s<<1)+(s<<3)+(IO^'0'); while(isdigit(IO=getchar())) ; return f?-s:s; } const int N=1e5+10; ll a,b,c,d; ll p,m; ll Solve(ll a,ll b) { if(a<0||b<0) return 0; if(a>b) swap(a,b); ll ans=0,t; if(a>=m) { t=1ll*(a-m)/p*p+m; ans+=1ll*(m+t+2)*((t-m)/p+1)/2; } t=0; if(b>=m) t=(b-m)/p+1; if(a>=m) t-=(a-m)/p+1; ans+=t*(a+1); if(b>=m) { t=1ll*(b-m)/p*p+m; ans-=1ll*(2*a+2*b-m-t+2)*((t-m)/p+1)/2; } if(a+b>=m) { t=1ll*(a+b-m)/p*p+m; ans+=1ll*(2*a+2*b-m-t+2)*((t-m)/p+1)/2; } return ans; } ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); } int main(){ int cnt=0; rep(i,0,0) rep(j,0,5) if((i+j)%3==2) cnt++; rep(kase,1,rd()) { a=rd(),b=rd(),c=rd(),d=rd(),p=rd(),m=rd(); ll ans=Solve(b,d)-Solve(a-1,d)-Solve(b,c-1)+Solve(a-1,c-1); printf("Case #%d: ",kase); if(!ans) { puts("0/1"); continue; } ll t=1ll*(b-a+1)*(d-c+1); ll g=gcd(t,ans); ans/=g,t/=g; printf("%lld/%lld\n",ans,t); } }