容斥
bzoj2440 完全平方数
题目大意:求第k个不是完全平方数倍数的数(1不算完全平方数)。
思路:二分+容斥+莫比乌斯函数。我们可以二分x,看1~x中有几个不是完全平方数倍数的数。求这个的过程是容斥原理,我们可以用所有的数-有1个质数(分解质因数后的,下同)平方了的+有2个的-有3个的...,然后我们可以穷举一些数,他们的平方一定是完全平方数,而他们有几个质数就是之前的那个1、2、3...,而这个系数又恰好和莫比乌斯函数吻合。这里的容斥(!!!)用到很多技巧,但代码很简单。(关于二分的上界好像是2k,但我设了一个比较大的上界1e10)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 100005 #define LL long long using namespace std; int mu[maxnode]={0},prime[maxnode]={0}; bool flag[maxnode]={false}; void shai(int n) { int i,j; mu[1]=1; for (i=2;i<=n;++i) { if (!flag[i]) { prime[++prime[0]]=i;mu[i]=-1; } for (j=1;j<=prime[0]&&prime[j]*i<=n;++j) { flag[i*prime[j]]=true; if (i%prime[j]) mu[i*prime[j]]=-mu[i]; else {mu[i*prime[j]]=0;break;} } } } LL calc(LL n) { LL i;LL ans=n; for (i=2;i*i<=n;++i) ans+=mu[(int)i]*(n/(i*i)); return ans; } int main() { int t;LL l,r,mid,n; shai(100000);scanf("%d",&t); while(t--) { scanf("%I64d",&n); l=1;r=1e10; while(l!=r) { mid=(l+r)/2; if (calc(mid)<n) l=mid+1; else r=mid; } printf("%I64d\n",l); } }
bzoj1042 硬币购物
题目大意:给定4种硬币的货值和询问次数,每次询问给定4中硬币的个数和要买的物品价值,求购买的方案数。
思路:dp预处理+容斥。首先不考虑硬币数量的限制,无限背包预处理出f[i](表示价值为i的方案数有多少)(注意无限背包先循环物品,如果循环顺序反了,就会对同一种方案统计多次(有点像排列数和组合数的关系))。然后对每一个询问都用f[s]-1种硬币超过的+2种的-3种的+4种的,这里统计一种超的可以强制着一种取要求数di+1个,那么方案数就是f[s-(di+1)*ci]种了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define maxnode 100005 using namespace std; LL f[maxnode]={0}; int ci[5]={0},di[5]={0},s; LL calc(int a,int b,int c,int d) { int sum=a*ci[1]+b*ci[2]+c*ci[3]+d*ci[4]; return (sum>s ? 0 : f[s-sum]); } int main() { int tot,i,j;LL ans; for (i=1;i<=4;++i) scanf("%d",&ci[i]); scanf("%d",&tot);f[0]=1; for (j=1;j<=4;++j) for (i=ci[j];i<=100000;++i) f[i]+=f[i-ci[j]]; for (i=1;i<=tot;++i) { for (j=1;j<=4;++j) scanf("%d",&di[j]); scanf("%d",&s); ans=f[s]-calc(di[1]+1,0,0,0)-calc(0,di[2]+1,0,0)-calc(0,0,di[3]+1,0)-calc(0,0,0,di[4]+1) +calc(di[1]+1,di[2]+1,0,0)+calc(di[1]+1,0,di[3]+1,0)+calc(di[1]+1,0,0,di[4]+1) +calc(0,di[2]+1,di[3]+1,0)+calc(0,di[2]+1,0,di[4]+1)+calc(0,0,di[3]+1,di[4]+1) -calc(di[1]+1,di[2]+1,di[3]+1,0)-calc(di[1]+1,di[2]+1,0,di[4]+1) -calc(di[1]+1,0,di[3]+1,di[4]+1)-calc(0,di[2]+1,di[3]+1,di[4]+1) +calc(di[1]+1,di[2]+1,di[3]+1,di[4]+1); printf("%I64d\n",ans); } }
bzoj4086 travel
题目大意:给定一张无向图,求路径上经过k个点的点对,输出矩阵ij表示ij间有无道路。
思路:k<=7所以可以分情况+容斥。
k=2的时候,枚举边;
k=3的时候,枚举边、第二条边;
k=4的时候,枚举两边的边,判断中间是否连通;
k=5的时候,枚举第2、4个点,求出所有的次数和每个点在中间出现的次数,然后找两边的点,tot-cntx-cnty>0就可以;
k=6的时候,同5,只是要判断两个点是否能组成四元组;
k=7的时候,先找出所有的三元组保存下来,枚举2、6个点,预处理所有能组成五元组的三元组的点分别的个数、总的个数、xy同时出现在边上的、xy一边一中的,然后判断的时候tot-cntx-cnty+calxy+c1xy+c1yx>0就可以。(注意这里不能清数组,用一个数组保存这个位置的值有无更新过,同时判断的时候也要判断是否更新过。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define N 1005 #define M 10005 using namespace std; int point[N]={0},next[M]={0},en[M]={0},tot=0,n,cnt[N],cal[N][N],c1[N][N],pal[N][N],p1[N][N]; bool li[N][N],mi[N][N]; vector<int> ci[N][N]; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} void add(int u,int v){ if (u==v) return; next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u; mi[u][v]=mi[v][u]=true;} void work2(){ for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) if (mi[i][j]) li[i][j]=true;} void work3(){ int i,j,t,u; for (i=1;i<=n;++i) for (j=point[i];j;j=next[j]) for (t=point[u=en[j]];t;t=next[t]){ if (en[t]==i) continue; li[i][en[t]]=true; } } void work4(){ int i,j,a,b,u,v; for (i=1;i<n;++i) for (j=i+1;j<=n;++j) for (a=point[i];a;a=next[a]){ if ((u=en[a])==j) continue; for (b=point[j];b;b=next[b]){ if ((v=en[b])==u||v==i) continue; if (mi[u][v]) li[i][j]=li[j][i]=true; } }} void work5(){ int i,j,a,b,u,v,tot; for (i=1;i<n;++i) for (j=i+1;j<=n;++j){ memset(cnt,0,sizeof(cnt));tot=0; for (a=point[i];a;a=next[a]){ if ((u=en[a])==j||!mi[u][j]) continue; ++cnt[u];++tot; }for (a=point[i];a;a=next[a]){ if ((u=en[a])==j) continue; for (b=point[j];b;b=next[b]){ if ((v=en[b])==i||v==u) continue; if (tot-cnt[u]-cnt[v]>0) li[u][v]=li[v][u]=true; } } }} void work6(){ int i,j,a,b,u,v,tot; for (i=1;i<n;++i) for (j=i+1;j<=n;++j){ memset(cnt,0,sizeof(cnt));tot=0; for (a=point[i];a;a=next[a]){ if ((u=en[a])==j) continue; for (b=point[j];b;b=next[b]){ if ((v=en[b])==i||v==u) continue; if (mi[u][v]){++cnt[u];++cnt[v];++tot;}} }for (a=point[i];a;a=next[a]){ if ((u=en[a])==j) continue; for (b=point[j];b;b=next[b]){ if ((v=en[b])==i||v==u) continue; if (tot-cnt[u]-cnt[v]+mi[u][v]>0) li[u][v]=li[v][u]=true; } } }} void work7(){ int i,j,k,a,b,u,v,t,tot,cur=0; for (i=1;i<=n;++i) for (j=1;j<=n;++j) ci[i][j].clear(); for (i=1;i<=n;++i) for (a=point[i];a;a=next[a]) for (b=point[u=en[a]];b;b=next[b]){ if ((v=en[b])==i) continue; ci[i][v].push_back(u);} memset(pal,0,sizeof(pal)); memset(p1,0,sizeof(p1)); for (i=1;i<=n;++i) for (j=i+1;j<=n;++j){ memset(cnt,0,sizeof(cnt));++cur;tot=0; for (a=point[i];a;a=next[a]){ if ((u=en[a])==j) continue; for (b=point[j];b;b=next[b]){ if ((v=en[b])==u||v==i) continue; for (k=ci[u][v].size()-1;k>=0;--k){ if ((t=ci[u][v][k])==i||t==j) continue; if (pal[u][v]==cur)++cal[u][v]; else{cal[u][v]=1;pal[u][v]=cur;} if (p1[u][t]==cur) ++c1[u][t]; else{c1[u][t]=1;p1[u][t]=cur;} if (p1[v][t]==cur) ++c1[v][t]; else{c1[v][t]=1;p1[v][t]=cur;} ++cnt[u];++cnt[v];++cnt[t];++tot; } } }for (a=point[i];a;a=next[a]){ if ((u=en[a])==j) continue; for (b=point[j];b;b=next[b]){ if ((v=en[b])==u||v==i) continue; if (tot-cnt[u]-cnt[v]+(pal[u][v]==cur ? cal[u][v] : 0)+ (p1[u][v]==cur ? c1[u][v] : 0)+(p1[v][u]==cur ? c1[v][u] : 0)>0) li[u][v]=li[v][u]=true; } } } } void work(int k){ if (k==2) work2(); if (k==3) work3(); if (k==4) work4(); if (k==5) work5(); if (k==6) work6(); if (k==7) work7();} int main(){ int t,i,j,m,u,v,k;t=in(); while(t--){ memset(point,0,sizeof(point)); memset(mi,0,sizeof(mi)); memset(li,0,sizeof(li)); n=in();m=in();k=in();tot=0; for (i=1;i<=m;++i){u=in();v=in();add(u,v);} for (work(k),i=1;i<=n;++i){ for (j=1;j<=n;++j) putchar(li[i][j] ? 'Y' : 'N'); printf("\n"); } } }
bzoj1567 Blue Mary的战役地图
题目大意:求两个矩阵的最大公共正方形的边长。
思路:二分+hash。用容斥求出一个矩阵的hash,用map存一下hash值(可以用unsigned long long,自然溢出)就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<map> #include<cstdlib> #define N 55 #define LL long long #define UL unsigned long long using namespace std; int n;LL aa[N][N],bb[N][N]; UL ai[N][N],bi[N][N],ma[N],mb[N]; map<UL,int> cnt; bool judge(int x){ int i,j,a,b;cnt.clear(); for (i=x;i<=n;++i) for (j=x;j<=n;++j){ a=i-x+1;b=j-x+1; cnt[ma[n-i+1]*mb[n-j+1]*(ai[i][j]-ai[i][b-1]-ai[a-1][j]+ai[a-1][b-1])]=1; } for (i=x;i<=n;++i) for (j=x;j<=n;++j){ a=i-x+1;b=j-x+1; if (cnt[ma[n-i+1]*mb[n-j+1]*(bi[i][j]-bi[i][b-1]-bi[a-1][j]+bi[a-1][b-1])]) return true; }return false;} int main(){ int i,j,l,r,mid,ans=0; UL a,b;scanf("%d",&n); a=(UL)3931;b=(UL)9907;ma[0]=mb[0]=1LL; for (i=1;i<=n;++i){ma[i]=ma[i-1]*a;mb[i]=mb[i-1]*b;} for (i=1;i<=n;++i) for (j=1;j<=n;++j){ scanf("%I64d",&aa[i][j]); ai[i][j]=(UL)aa[i][j]*ma[i]*mb[j]; ai[i][j]+=ai[i-1][j]+ai[i][j-1]-ai[i-1][j-1];} for (i=1;i<=n;++i) for (j=1;j<=n;++j){ scanf("%I64d",&bb[i][j]); bi[i][j]=(UL)bb[i][j]*ma[i]*mb[j]; bi[i][j]+=bi[i-1][j]+bi[i][j-1]-bi[i-1][j-1];} l=0;r=n; while(l<=r){ mid=l+r>>1; if (judge(mid)){ans=mid;l=mid+1;} else r=mid-1; }printf("%d\n",ans); }
乞巧
题目大意:求n个字符串中恰好有k个字母不同的字符串对(字符串长度为4)。
思路:hash一下,然后容斥。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 500005 #define p 99991 #define LL unsigned long long #define ll long long using namespace std; struct use{ LL ha[20]; }ai[maxnode]={0}; char ss[maxnode][10]; LL mi[10]={0}; ll ans[5]={0},get[5]={0}; int n,k,k1,k2; int cmp(const use &x,const use &y){return x.ha[k1]<y.ha[k1];} int idx(char ch) { if (ch>='0'&&ch<='9') return ch-'0'; else return ch-'a'+10; } void pre() { int i,j; for (i=1;i<=n;++i) { for (j=0;j<4;++j) ai[i].ha[0]+=mi[j]*idx(ss[i][j]); ai[i].ha[1]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][1])+mi[2]*idx(ss[i][2]); ai[i].ha[2]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][1])+mi[2]*idx(ss[i][3]); ai[i].ha[3]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][2])+mi[2]*idx(ss[i][3]); ai[i].ha[4]=mi[0]*idx(ss[i][1])+mi[1]*idx(ss[i][2])+mi[2]*idx(ss[i][3]); ai[i].ha[5]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][1]); ai[i].ha[6]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][2]); ai[i].ha[7]=mi[0]*idx(ss[i][0])+mi[1]*idx(ss[i][3]); ai[i].ha[8]=mi[0]*idx(ss[i][1])+mi[1]*idx(ss[i][2]); ai[i].ha[9]=mi[0]*idx(ss[i][1])+mi[1]*idx(ss[i][3]); ai[i].ha[10]=mi[0]*idx(ss[i][2])+mi[1]*idx(ss[i][3]); for (j=0;j<4;++j) ai[i].ha[j+11]=idx(ss[i][j]); } } int main() { freopen("b.in","r",stdin); freopen("b.out","w",stdout); int i,j,t; scanf("%d%d",&n,&k);mi[0]=1; for (i=1;i<=4;++i) mi[i]=mi[i-1]*p; for (i=1;i<=n;++i) scanf("%s",&ss[i]); pre();k1=0;i=1; sort(ai+1,ai+n+1,cmp); while(i<=n) { j=i; while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j; get[0]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1; } ans[0]=get[0]; for (k2=1;k2<=4;++k2) { ++k1;sort(ai+1,ai+n+1,cmp);i=1; while(i<=n) { j=i; while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j; get[1]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1; } } ans[1]=get[1]-4*ans[0]; if (k>=2) { for (k2=1;k2<=6;++k2) { ++k1;sort(ai+1,ai+n+1,cmp);i=1; while(i<=n) { j=i; while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j; get[2]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1; } } ans[2]=get[2]-3*ans[1]-6*ans[0]; if (k>=3) { for (k2=1;k2<=4;++k2) { ++k1;sort(ai+1,ai+n+1,cmp);i=1; while(i<=n) { j=i; while(ai[j].ha[k1]==ai[j+1].ha[k1]&&j<n) ++j; get[3]+=(ll)(j-i+1)*(ll)(j-i)/2;i=j+1; } } ans[3]=get[3]-2*ans[2]-3*ans[1]-4*ans[0]; get[4]=(ll)n*(ll)(n-1)/2; ans[4]=get[4]-ans[0]-ans[1]-ans[2]-ans[3]; } } printf("%I64d\n",ans[k]); fclose(stdin); fclose(stdout); }
bzoj1879 Bill的挑战
题目大意:给定n个字符串(带有通配符?,且只有小写字母和?),求恰好和k个字符串匹配(两字符串相同,通配符可以代替任意一个字符)且没有?的字符串的个数。(n<=15,lenth<=50)
思路:dfs+容斥。如果dfs,搜出k个之后计数原理算一下可能的字符串种数,会有很多字符串算很多遍,所以容斥一下,系数可以通过组合数之类的算出来。
这个容斥的系数比较复杂,所以可以先把每一次dfs的答案的表达式写出来,然后用相应的数学技巧算出系数,可以当作一类方法。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 20 #define L 55 #define p 1000003 #define msz 26 using namespace std; char ss[N][L]; int n,k,kk,l,ci[N],ans=0,c[N][N]; int calc(){ int i,j,cc,cnt=1;char ch; for (i=0;i<l;++i){ cc=0;ch='?'; for (j=1;j<=k;++j) if (ss[ci[j]][i]!=ch&&ss[ci[j]][i]!='?'){ ch=ss[ci[j]][i];++cc;} if (cc>1) return 0; if (!cc) cnt=cnt*26%p; }return cnt%p;} void dfs(int ii,int la,int kk){ if (ii>k){ans=((ans+kk*calc())%p+p)%p;return;} for (int i=la+1;i+k-ii<=n;++i) { ci[ii]=i;dfs(ii+1,i,kk); }} int main(){ int i,j,t,cc;scanf("%d",&t); for (i=1;i<=15;++i){ c[i][0]=c[i][i]=1; for (j=1;j<i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j]; }while(t--){ scanf("%d%d",&n,&k);ans=0; for (i=1;i<=n;++i) scanf("%s",ss[i]); l=strlen(ss[1]);kk=k; for(j=1;k<=n;++k,j=-j) dfs(1,0,j*c[k][kk]); printf("%d\n",ans); } }
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 20 #define L 55 #define p 1000003 #define msz 26 using namespace std; char ss[N][L]; int n,k,kk,l,ci[N],ans=0,c[N][N],ki[N]; int calc(){ int i,j,cc,cnt=1;char ch; for (i=0;i<l;++i){ cc=0;ch='?'; for (j=1;j<=k;++j) if (ss[ci[j]][i]!=ch&&ss[ci[j]][i]!='?'){ ch=ss[ci[j]][i];++cc;} if (cc>1) return 0; if (!cc) cnt=cnt*26%p; }return cnt%p;} void dfs(int ii,int la,int kk){ if (ii>k){ans=((ans+kk*calc())%p+p)%p;return;} for (int i=la+1;i+k-ii<=n;++i) { ci[ii]=i;dfs(ii+1,i,kk); }} int main(){ int i,j,t,cc;scanf("%d",&t); for (i=1;i<=15;++i){ c[i][0]=c[i][i]=1; for (j=1;j<i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j]; }while(t--){ scanf("%d%d",&n,&k);ans=0; for (i=1;i<=n;++i) scanf("%s",ss[i]); l=strlen(ss[1]);kk=k; for (i=k;i<=n;++i) ki[i]=c[i][k]; for (i=k+1;i<=n;++i){ for (cc=-ki[i],j=i+1;j<=n;++j) ki[j]+=cc*c[j][i]; ki[i]=-ki[i]; }for(;k<=n;++k) dfs(1,0,ki[k]); printf("%d\n",ans); } }
看网上大多数人都写的dp,fi[i][j]表示前i位匹配状态为j(二进制)的有多少种,转移一下就行了。
bzoj3930 选数
题目大意:从[L,R]中选出N个数(可重复排列),问N个数的最大公约数是K的方案数。
思路:[L,R]/k之后,就是统计N个数互质的情况,类似bzoj2440,-1个质数的+2个质数的-...,这里的R-L<=10^5,所以循环到10^5之后的数就会使之前的区间中只有1个数,所以可以循环统计之前的数,后面的数是mu的一段和的形式,可以用杜教筛。也可以稍微变化一下,每次累加答案的时候不考虑全选相同的数(!!!),最后如果能全选k的话,再+1。这样对于只有一个数的区间就不用统计答案了,其他区间-(R-L+1),省去了求前缀和的部分。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define LL long long #define p 1000000007LL using namespace std; int prime[N]={0},mu[N]; LL n,k,l,r; bool flag[N]={false}; inline void shai(int m){ int i,j;mu[1]=1; for (i=2;i<=m;++i){ if (!flag[i]) mu[prime[++prime[0]]=i]=-1; for (j=1;j<=prime[0]&&i*prime[j]<=m;++j){ flag[i*prime[j]]=true; if (i%prime[j]) mu[i*prime[j]]=-mu[i]; else{mu[i*prime[j]]=0;break;} } } } inline LL mi(LL x,int y){ LL a=1LL; for (;y;y>>=1){ if (y&1) a=a*x%p; x=x*x%p; }return a;} inline LL calc(int m){ int i,ll,rr;LL ans=0LL; l=(l+k-1)/k;r=r/k; for (i=1;i<=m;++i){ ll=(l-1)/i;rr=r/i; ans=((ans+(LL)mu[i]*(mi((LL)(rr-ll),n)-(LL)(rr-ll)))%p+p)%p; }return (ans+(l==1LL))%p;} int main(){ scanf("%d%d%d%d",&n,&k,&l,&r); shai(r-l+1);printf("%I64d\n",calc(r-l+1)); }
bzoj2669 局部最小值
题目大意:给定一个n*m的网格,格子中的数是一个1~n*m的排列,其中有一些位置是极小值点(这个点的权值是它周围八个格子中最小的),求满足条件的网格种数。(n<=4,m<=7)
思路:因为网格中最多有8个极小值点,所以可以状压。从小到大填数,预处理gi[i]表示极小值点填的状态是i的时候能填的位置(非极小值点)的个数。fi[i][j]表示填到第i个数、极小值点填的状况是j,fi[i+1][j]+=fi[i][j]*(gi[j]-(i-cc))(cc表示j中1的个数),fi[i+1][j|(1<<k)]+=fi[i][j]。但这样可能会使一些点成为极小值点,所以可以容斥:预处理出所有可能成为极小值点的位置,然后dfs,再dp,算答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 10 #define up 30 #define LL long long #define p 12345678LL using namespace std; struct use{int x,y;}zh[N]; int map[N][N],ci[N][N],n,m,zt=0,st=0,dx[8]={-1,-1,-1,0,0,1,1,1},dy[8]={-1,0,1,-1,1,-1,0,1}; LL ans=0LL,fi[up][1<<N],gi[1<<N]; bool cg[N][N]={false}; int in(){ char ch=getchar(); while(ch!='X'&&ch!='.') ch=getchar(); return (ch=='X');} void find(){ int i,j,k,x,y; for (i=1;i<=n;++i) for (j=1;j<=m;++j){ cg[i][j]=!map[i][j]; for (k=0;k<8&&cg[i][j];++k){ x=i+dx[k];y=j+dy[k]; if (x<=0||x>n||y<=0||y>m) continue; if (map[x][y]) cg[i][j]=false; } } } void add(LL &x,LL y){x=((x+y)%p+p)%p;} void dp(int cnt){ int i,j,k,xx,yy,uu,cc;LL ff=(LL)(cnt%2 ? -1 : 1); memset(fi,0,sizeof(fi)); memset(gi,0,sizeof(gi)); for (i=0;i<(1<<zt);++i){ memset(ci,0,sizeof(ci)); for (j=0;j<zt;++j){ ci[zh[j].x][zh[j].y]=1; if (i&(1<<j)) continue; for (k=0;k<8;++k){ xx=zh[j].x+dx[k];yy=zh[j].y+dy[k]; if (xx<=0||xx>n||yy<=0||yy>m) continue; if (map[xx][yy]) return; ci[xx][yy]=1; } }for (j=1;j<=n;++j) for (k=1;k<=m;++k) if (!ci[j][k]) ++gi[i]; }fi[0][0]=1LL; for (uu=n*m,i=0;i<uu;++i){ for (j=0;j<(1<<zt);++j){ if (!fi[i][j]) continue; for (cc=k=0;k<zt;++k){ if (!(j&(1<<k))) add(fi[i+1][j|(1<<k)],fi[i][j]); else ++cc; }if (i+1<cc) continue; if (gi[j]-(LL)(i-cc)<=0) continue; add(fi[i+1][j],fi[i][j]*(gi[j]-(LL)(i-cc))%p); } }add(ans,ff*fi[uu][(1<<zt)-1]); } void dfs(int x,int y,int cnt){ if (x==n+1){dp(cnt);return;} int xx,yy;xx=x;yy=y+1; if (yy>m){++xx;yy=1;} dfs(xx,yy,cnt); if (cg[x][y]){ map[x][y]=1; zh[zt++]=(use){x,y}; dfs(xx,yy,cnt+1); map[x][y]=0;--zt; } } int main(){ int i,j,tt=0;scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=m;++j){ map[i][j]=in(); if (map[i][j]) zh[zt++]=(use){i,j}; } find();dfs(1,1,0); printf("%I64d\n",ans); }
bzoj4455 小星星(!!!)
题目大意:给定n个点,已知这n个点原来的连接状态和现在的(现在的是一棵树),问有多少种重新编号的方式使现在的符合原来的(即现在的边在原来的中都能找到)。
思路:考虑n^3dp,fi[i][j]表示i和j对应,暴力转移。这样可能出现多个点和一个对应的情况,所以应该容斥一下,每次dp时对应相应的子集,然后更新答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 18 #define M 500 #define LL long long using namespace std; int ai[N][N],ci[N],n,point[N]={0},next[M],en[M],tot=0; LL fi[N][N]; void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u;} void dp(int u,int ff){ int i,j,k,v;LL sm,cc; for (i=point[u];i;i=next[i]){ if ((v=en[i])==ff) continue; dp(v,u); }for (i=1;i<=ci[0];++i){ for (sm=1LL,j=point[u];j;j=next[j]){ if ((v=en[j])==ff) continue; for (cc=0LL,k=1;k<=ci[0];++k){ if (!ai[ci[i]][ci[k]]) continue; cc+=fi[v][ci[k]]; }sm*=cc; }fi[u][ci[i]]=sm; } } int main(){ int m,i,j,u,v;LL ans=0LL; scanf("%d%d",&n,&m); memset(ai,0,sizeof(ai)); for (i=1;i<=m;++i){ scanf("%d%d",&u,&v); ai[u][v]=ai[v][u]=1; }for (i=1;i<n;++i){ scanf("%d%d",&u,&v); add(u,v); }for (i=0;i<(1<<n);++i){ memset(fi,0,sizeof(fi)); for (ci[0]=j=0;j<n;++j) if ((1<<j)&i) ci[++ci[0]]=j+1; dp(1,0); for (j=1;j<=ci[0];++j) ans+=(LL)(((n-ci[0])&1)? -1 : 1)*fi[1][ci[j]]; }printf("%I64d\n",ans); }
bzoj4596 黑暗前的幻想乡
题目大意:给出一张无向图,有n-1种边,求每种边出现一次的树的个数。
思路:类似上一题,容斥之后就是求只用j(二进制状态)的边的生成树的个数,可以用matrix-tree定理,关于取模,在原来/的时候用逆元就可以了。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #define N 18 #define LL long long #define p 1000000007LL using namespace std; struct use{int u,v;}ed[N][N*N]; LL ai[N][N]; int n,ci[N],cho[N]; LL mi(LL x,LL y){ LL a=1LL; for (;y;y>>=1LL){ if (y&1LL) a=a*x%p; x=x*x%p; }return a;} LL calc(){ int i,j,k,u,v;LL kk,cc=1LL; memset(ai,0,sizeof(ai)); for (i=1;i<=cho[0];++i) for (j=1;j<=ci[cho[i]];++j){ u=ed[cho[i]][j].u;v=ed[cho[i]][j].v; ++ai[u][u];++ai[v][v]; --ai[u][v];--ai[v][u]; } for (i=1;i<n;++i){ if (ai[i][i]==0){ for (j=i+1;j<n;++j) if (ai[j][i]!=0){ for (k=i;k<n;++k) swap(ai[i][k],ai[j][k]); break; } if (!ai[i][i]) return 0LL; }for (j=i+1;j<n;++j){ kk=ai[j][i]*mi(ai[i][i],p-2LL)%p; for (k=i;k<n;++k) ai[j][k]=((ai[j][k]-ai[i][k]*kk)%p+p)%p; } }for (i=1;i<n;++i) cc=cc*ai[i][i]%p; return (cc%p+p)%p; } int main(){ int i,j,u,v;LL ans=0LL;scanf("%d",&n); for (i=1;i<n;++i){ scanf("%d",&ci[i]); for (j=1;j<=ci[i];++j){ scanf("%d%d",&u,&v); ed[i][j]=(use){u,v}; } }for (i=1;i<(1<<(n-1));++i){ cho[0]=0; for (j=1;j<n;++j) if ((i>>(j-1))&1) cho[++cho[0]]=j; if ((n-cho[0])%2==0) ans=((ans-calc())%p+p)%p; else ans=(ans+calc())%p; }printf("%I64d\n",ans); }
bzoj4635 数论小测验
题目大意:已知一个长度为n的数列a,1<=ai<=m,有两问:1)求gcd(a1,...,an)=k的数列个数;2)求k|lcm(a1,...,an)的数列个数。其中k∈[l,r]。
思路:第一问比较简单,用mu的容斥,ans=sigma(k=l~r)sigma(i=1~m/k)mu[i]*(m/k/i)^n,两个simga都可以√n的统计;第二问对k和ai分解质因数,k=∏pi^yi,ai=∏pi^xi,max(xi)>=yi。比较好求的是max(xi)<yi(!!),可以预处理gi[i][j]表示1~i中和j互质的数的个数,令x=∏pi,满足条件的数列个数是(sigma(d|(k/x))gi[m/d][x])^n,这里的gi并不会重复统计,因为gi中的数都没有x的质因子,而乘上k/x的约数d之后是互不相同的。可以枚举k,dfs哪些pi是<的,算出kk=∏pi^xi的答案,然后通过容斥算出总答案。注意到m、n和kk确定的时候,满足max(xi)<yi的数列是可以预处理出来的bi。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long #define N 10000005 #define M 1005 #define p 1000000007LL using namespace std; int prime[N]={0},mu[N],sm[N]={0},gi[M][M],pr[M][2],pt; LL bi[M]; bool flag[N]={false}; void shai(int up){ int i,j;mu[1]=1; for (i=2;i<up;++i){ if (!flag[i]){prime[++prime[0]]=i;mu[i]=-1;} for (j=1;j<=prime[0]&&i*prime[j]<up;++j){ flag[i*prime[j]]=true; if (i%prime[j]) mu[i*prime[j]]=-mu[i]; else{mu[i*prime[j]]=0;break;} } }for (i=1;i<up;++i) sm[i]=sm[i-1]+mu[i]; } LL mi(LL x,LL y){ LL a=1LL; for (;y;y>>=1){ if (y&1LL) a=a*x%p; x=x*x%p; }return a;} void add(LL &x,LL y){x+=(y%p+p)%p;if (x>=p) x-=p;} LL calc1(){ LL n,m,ll,rr,i,li,j,lj,up,ans=0LL,ci; scanf("%I64d%I64d%I64d%I64d",&n,&m,&ll,&rr); for (i=ll;i<=rr;i=li+1){ li=min(rr,m/(m/i));ci=0LL; for (up=m/i,j=1LL;j<=up;j=lj+1){ lj=up/(up/j); add(ci,mi(up/j,n)*(sm[lj]-sm[j-1])); }add(ans,ci*(li-i+1)); }return ans; } int gcd(int a,int b){return (!b ? a : gcd(b,a%b));} LL ans; int n,m; void dfs(int i,int x,int y,int z){ if (i>pt){ add(ans,bi[z]*x); return; }dfs(i+1,x,y,z); dfs(i+1,-x,y*pr[i][0],z*pr[i][1]); } LL calc2(){ int ll,rr,i,j,k,up;ans=0LL; scanf("%d%d%d%d",&n,&m,&ll,&rr); for (i=1;i<=m;++i){ bi[i]=0LL; for (k=1,j=1,up=i;j<=prime[0]&&prime[j]*prime[j]<=i;++j) if (up%prime[j]==0){ k*=prime[j]; for (;up%prime[j]==0;up/=prime[j]); } if (up!=1) k*=up; for (up=i/k,j=1;j*j<=up;++j){ if (up%j) continue; add(bi[i],gi[m/j][k]); if (j*j!=up) add(bi[i],gi[m/(up/j)][k]); }bi[i]=mi(bi[i],(LL)n); }for (i=ll;i<=rr;++i){ for (k=i,pt=0,j=1;j<=prime[0]&&i!=1;++j) if (k%prime[j]==0){ pr[++pt][0]=prime[j]; pr[pt][1]=1; for (;k%prime[j]==0;k/=prime[j],pr[pt][1]*=prime[j]); } dfs(1,1,1,1); }return ans; } int main(){ int t,ty,i,j; scanf("%d%d",&t,&ty); if (ty==1) shai(N); else{ shai(M); memset(gi,0,sizeof(gi)); for (i=1;i<M;++i) for (j=1;j<M;++j) gi[i][j]=gi[i-1][j]+(gcd(i,j)==1); }while(t--){ if (ty==1) printf("%I64d\n",calc1()); else printf("%I64d\n",calc2()); } }