留两个坑:1、测试51or 2、杂题3最大前缀和 (3、测试48group)
树DP感觉不像传统DP,所以分着写了。详见树总结。
欢迎大家在评论区提供新的方法!
FIR:优化
一、决策点信息维护
1:单调性:单调队列、栈。
常见斜率,凸壳。单调性可以二分。
T:1、测试50施工。单调栈。
#include<bits/stdc++.h> #define F(i,a,b) for(rg int i=a;i<=b;++i) #define rg register #define LL long long #define il inline #define pf(a) printf("%d ",a) #define PF(a) printf("%lld ",a) #define phn puts("") using namespace std; #define int LL int read(); /* 之前是(double)(b/(a*2))+0.5; 改成(double)b/(a*2)+0.5 或者1.0*b/(a*2)就A了。 */ #define N 1000010 int n;LL c; int h[N]; LL f[N]; const LL inf=1e15; LL s1[N],s2[N]; int sta[N],top; il LL cal(int i,int j,int mh){ LL a=(i-j-1),b=2ll*(s1[i-1]-s1[j]),u=s2[i-1]-s2[j]; if(j!=0){ b+=c;u+=c*h[j]; } if(i!=n+1){ b+=c;u+=c*h[i]; } LL t=1.0*b/(a*2)+0.5; t=max(1ll*mh,t); t=min(t,1ll*min(h[i],h[j])); return f[j]+t*t*a-t*b+u; } signed main(){ n=read();c=read(); F(i,1,n)h[i]=read(); h[0]=h[n+1]=2e6; F(i,1,n){ s1[i]=s1[i-1]+h[i]; s2[i]=s2[i-1]+1ll*h[i]*h[i]; } sta[top=1]=0; f[1]=0;sta[++top]=1; LL w; //PF(cal(4,2,h[3]));phn;return 0; F(i,2,n+1){ f[i]=i==n+1?f[i-1]:f[i-1]+c*abs(h[i]-h[i-1]); while(top>1&&h[sta[top]]<=h[i]){ w=cal(i,sta[top-1],h[sta[top]]); f[i]=min(f[i],w); --top; } sta[++top]=i; } // F(i,1,n+1)PF(f[i]);phn; printf("%lld\n",f[n+1]); } il int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return f?-s:s; } /* g++ 1.cpp -g ./a.out 4 2 1 3 2 4 */
2、测试51function。离线,单调栈维护斜率下凸壳,二分。
#include<bits/stdc++.h> #define F(i,a,b) for(rg int i=a;i<=b;++i) #define rg register #define LL long long #define il inline #define pf(a) printf("%lld ",a) #define phn puts("") using namespace std; #define int LL #define N 500010 int read(); int n,que; int s[N],a[N],c[N]; int min(int x,int y){return x<y?x:y;} int max(int x,int y){return x>y?x:y;} struct Q{ int x,y,id; friend bool operator <(const Q& a,const Q& b){return a.y<b.y||(a.y==b.y&&a.x<b.x);} }b[N]; int sta[N],top,ans[N]; double get(int i,int j){ return 1.0*(c[j]-c[i])/(a[i]-a[j]); } void push(int x){ while(top&&a[sta[top]]>=a[x])--top; while(top>1&&get(x,sta[top])>=get(sta[top],sta[top-1]))--top; /** 这里是凸包的维护。第二句:使交点在栈内单调递减。因为画图发现交点递减才成为凸包,否则上一条线可以去掉 如果不去掉,决策会使用上一条线,导致挡住原本更优的决策 所以下凸包的维护:1、斜率递增,pop掉斜率比他大的 2、交点递减,防止不优的线影响决策 */ sta[++top]=x; } /** 可以证明,超过边界的不合法的决策点不优*/ int solve(int x,int y){ int l=1,r=top,mid; while(l<r){ mid=l+r>>1; // if(y-sta[mid]+1>x){l=mid+1;continue;} if(get(sta[mid],sta[mid+1])>x-y)l=mid+1; else r=mid; } l=sta[l]; // pf(l);pf(x);pf(y);phn; /*if(x==100&&y==7){ F(i,1,top)pf(a[sta[i]]);phn;while(1); }*/ return s[y]-s[l]+(x-y+l)*a[l]; } signed main(){ // freopen("function2.in","r",stdin); freopen("1.out","w",stdout); n=read(); F(i,1,n)s[i]=s[i-1]+(a[i]=read()),c[i]=i*a[i]-s[i]; que=read(); F(i,1,que)b[i]=(Q){read(),read(),i}; sort(b+1,b+que+1); int p=1; while(b[p].y==1){ ans[b[p].id]=a[1]*b[p].x;++p; } sta[top=1]=1; /** s[y]0-s[i]+(x-y+i)*a[i] 先判两个端点。 */ int i,x,y; F(k,2,n){ push(k); if(top==1){ i=sta[1]; while(b[p].y==k){ ans[b[p].id]=a[k]*b[p].x; ++p; } continue; } while(b[p].y==k){ ans[b[p].id]=solve(b[p].x,k); ++p; } // push(k); } F(i,1,que)printf("%lld\n",ans[i]); } int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return f?-s:s; } /* g++ 1.cpp -g ./a.out g++ dp.cpp -g ./a.out 10 10 9 1 7 4 6 8 5 2 3 10 4 2 100 2 6 3 1 4 3 5 1 7 100 7 5 8 2 9 100 10 */
2:数据结构。线段树。用于维护决策点/dp值。维护若干相同的操作。
进阶,可以线段树合并维护DP。
二、数学优化
1、NTT
2、矩阵,结合律,快速幂性质。
T:测试52涂色游戏 组合+矩阵快速幂
3、数论性质优化
如:原根优化为循环矩阵
三、改变状态定义
1、之前一道求数列乘积取模方案数:gcd(等等)相同函数值相同,减少状态数。
2、测试33赤壁情:绝对位置变为相对位置
#include<bits/stdc++.h> #define rg register #define F(i,a,b) for(rg int i=a;i<=b;++i) #define LL long long #define il inline #define pf(a) printf("%d ",a) #define pd(a) printf("%.3lf ",a) #define phn puts("") using namespace std; int read(); /* Lxt */ int n,m,bmw; il void out(double ans){ int k=bmw; if(k==0)printf("%.0f\n",ans); else if(k==1)printf("%.1lf\n",ans); else if(k==2)printf("%.2lf\n",ans); else if(k==3)printf("%.3lf\n",ans); else if(k==4)printf("%.4lf\n",ans); else if(k==5)printf("%.5lf\n",ans); else if(k==6)printf("%.6lf\n",ans); else if(k==7)printf("%.7lf\n",ans); else if(k==8)printf("%.8lf\n",ans); } double f[2][65][8005][4]; #define dp f[p][j][k][l] il void work1(){ f[n&1][0][0][0]=1; rg int p=0; //递推 for(int i=n;i>=1;--i){ p=i&1; F(j,0,52){ F(k,0,7505){ F(l,0,2){ f[!p][j+1][k+i*2][l]+=dp*(j+1-l); f[!p][j][k][l]+=dp*(j*2-l); if(l<2){ f[!p][j+1][k+i][l+1]+=dp*(2-l); if(k>=i)f[!p][j][k-i][l+1]+=dp*(2-l); } if(j>1&&k>=i*2)f[!p][j-1][k-i*2][l]+=dp*(j-1); /*if(dp>1e-8){ if(j>1&&k>=i*2)printf("%d %d %d %d %d %.3f\n",i,!p,j-1,k-i*2,l,f[!p][j-1][k-i*2][l]); }*/ /* 与其他段交点:0:新段 1:延长 2:连接 0和1特殊讨论边界点 */ dp=0; } } } } double ans=0;p=0; const int lxt=n*n/2; F(k,m,lxt){ ans+=f[0][1][k][2]; } // F(k,2,3)pd(f[0][1][k][2]); F(i,2,n)ans/=i; out(ans); } int floor(__float128 x){ for(int i=9;i>=0;--i)if(x>=i)return i; } void print__float128(__float128 x,int ws){ int sta[55];sta[0]=0; for(int i=1;i<=ws;++i)x*=10,sta[i]=floor(x),x-=floor(x); x*=10;if(floor(x)>=5)sta[ws]++; for(int i=ws;i;--i)if(sta[i]==10)sta[i]=0,sta[i-1]++; printf("%d.",sta[0]); for(int i=1;i<=ws;++i)putchar(sta[i]+48); puts(""); } __float128 g[2][28][2502][4]; #define hp g[p][j][k][l] il void work_30(){ g[n&1][0][0][0]=1; rg int p=0; //递推 for(int i=n;i>=1;--i){ p=i&1; F(j,0,26){ F(k,0,2002){ F(l,0,2){ g[!p][j+1][k+i*2][l]+=hp*(j+1-l); g[!p][j][k][l]+=hp*(j*2-l); if(l<2){ g[!p][j+1][k+i][l+1]+=hp*(2-l); if(k>=i)g[!p][j][k-i][l+1]+=hp*(2-l); } if(j>1&&k>=i*2)g[!p][j-1][k-i*2][l]+=hp*(j-1); hp=0; } } } } __float128 ans=0;p=0; const int lxt=n*n/2; F(k,m,lxt){ ans+=g[0][1][k][2]; } // F(k,2,3)pd(g[0][1][k][2]); F(i,2,n)ans/=i; print__float128(ans,bmw); } int main(){ n=read();m=read();bmw=read(); if(bmw<=8)work1(); else work_30(); } il int read(){ int s=0,f=0;rg char ch; while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return f?-s:s; } /* g++ 1.cpp -g time ./a.out 50 200 30 */ /* */
3、测试49折射:按照另一种顺序,时间复杂转移简单变为时间简单转移复杂
#include<bits/stdc++.h> #define F(i,a,b) for(rg int i=a;i<=b;++i) #define rg register #define LL long lnog #define il inline #define pf(a) printf("%d ",a) #define phn puts("") using namespace std; int read(); /* 数组尽量开大 5000~5400有3个点 */ int n; const int mod=1e9+7; #define N 6001 struct node{ int x,y; friend bool operator<(const node &a,const node &b){ return a.x<b.x; } }s[N]; int b[N]; il int MO(const int x){return x<mod?x:x-mod;} int f[6002][2]; int main(){ n=read(); F(i,1,n){ s[i].x=read(); s[i].y=read(); } sort(s+1,s+n+1); int ans=0; rg int y,w; F(i,1,n){ f[i][0]=f[i][1]=1; for(int j=i-1;j>=1;--j){ if(s[j].y<s[i].y){ f[i][0]=MO(f[i][0]+f[j][1]); } else{ f[j][1]=MO(f[j][1]+f[i][0]); } } } F(i,1,n){ ans=MO(ans+f[i][0]);ans=MO(ans+f[i][1]); // printf("%d %d\n",f[i][0],f[i][1]); } ans=(ans-n+mod)%mod; printf("%d\n",ans); } il int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return f?-s:s; } /* g++ 2.cpp -g ./a.out 4 2 2 3 1 1 4 4 3 */
四、网格/二维
1、网格DP
插头DP是一类网格DP。
网格DP的一般做法是枚举n*m,扫一行,继承上一行信息。
复杂度n*m*状态数。
一般搭配状压使用。
T:测试59的部分分(全场两人拿到这个子任务,另外一个是正解)
#include<bits/stdc++.h> #define F(i,a,b) for(int i=a;i<=b;++i) #define LL long long #define pf(a) printf("%d ",a) #define phn puts("") using namespace std; int n,all; #define N 100010 int a[N],b[N]; LL f[2][2][1000010]; const int mod=1e9+7; void cal(int x,int y){ int p=((x-1)*n+y)&1,lm=min(a[x],b[y]); int u,v; for(int i=all;i>=0;--i){ (f[p][0][i]+=f[!p][0][i]*lm)%=mod; (f[p][1][i]+=f[!p][1][i]*lm)%=mod; if(a[x]==b[y]){ (f[p][1][i|(1<<(y-1))]+=f[!p][0][i]+f[!p][1][i])%=mod; } else if(a[x]<b[y]){ (f[p][1][i]+=f[!p][0][i]+f[!p][1][i])%=mod; } else if(a[x]>b[y]){ (f[p][0][i|(1<<(y-1))]+=f[!p][0][i])%=mod; (f[p][1][i|(1<<(y-1))]+=f[!p][1][i])%=mod; } f[!p][0][i]=f[!p][1][i]=0; } } void work1(){ f[0][1][0]=1;all=(1<<n)-1; int p; F(i,1,n){ p=((i-1)*n)&1; F(j,0,all){ f[p][0][j]=f[p][1][j];f[p][1][j]=0; } F(j,1,n){ cal(i,j); } } p=(n*n)&1; printf("%lld\n",f[p][1][all]); } int qpow(LL x,int k){LL s=1;for(;k;x=x*x%mod,k>>=1)if(k&1)s=s*x%mod;return s;} int main(){ scanf("%d",&n);F(i,1,n)scanf("%d",&a[i]);F(i,1,n)scanf("%d",&b[i]); if(n<=16)work1(); else{ LL ans=1; F(i,1,n){ LL w=(qpow(i+1,n-i)-qpow(i,n-i)+mod)%mod; w=w*w%mod; ans=(w*i+qpow(i+1,2*(n-i)))%mod*ans%mod; } printf("%lld\n",ans); } } /* g++ 2.cpp -g ./a.out 3 1 2 3 1 2 3 */
2、插头,轮廓线
插头DP是网格+轮廓线。
而轮廓线本身是一个独立的技巧。
轮廓线可用Hash表优化状态数。
来自学长的两道插头DP题的模板:
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define F(i,a,b) for(int i=a;i<=b;++i) #define LL long long #define rg register #define phn printf("\n") #define pf(a) printf("%d ",a) #define PF(a) printf("%lld ",a) #define il inline using namespace std; int read(); int n,m; const int cube=(int)1e9,mod=10007; #define me(a) memset(a,0,sizeof(a)) struct data{ int bit[6]; il void Clear(){bit[0]=bit[1]=bit[2]=bit[3]=bit[4]=bit[5]=0;} data(){Clear();} il void Set(int x){Clear();while(x){bit[++bit[0]]=x%cube;x/=cube;}} il int &operator [](int x){return bit[x];} il void print(){ printf("%d",bit[bit[0]]); for(int i=bit[0]-1;i>=1;--i)printf("%09d",bit[i]);phn; } il data operator + (data b){ data c;c.Clear(); c[0]=max(bit[0],b[0])+1; F(i,1,c[0]){ c[i]+=bit[i]+b[i];c[i+1]=c[i]/cube;c[i]%=cube; } while(!c[c[0]])--c[0]; return c; } il void operator +=(data b){*this=*this+b;} il void operator = (int x){Set(x);} }ANS; struct hash{ data val[mod]; int head[mod],size,sta[mod],fir[mod]; il void init(){ me(val);me(sta);me(fir);me(head);size=0; } il data &operator [] (const int &state){ int pos=state%mod,i; for(i=head[pos];i&&sta[i]!=state;i=fir[i]); if(!i){sta[++size]=state;fir[size]=head[pos];head[pos]=size;i=size;} return val[i]; } }f[2]; il int find(int sta,int id){return (sta>>((id-1)<<1))&3;} il void set(int &sta,int id,int val){ id=(id-1)<<1;sta|=3<<id;sta^=3<<id;sta|=val<<id; } il int link(int sta,int pos){ int cnt=0,g=(find(sta,pos)==1)?1:-1; for(int i=pos;i&&i<=m+1;i+=g){ //??m+1 int plug=find(sta,i); if(plug==1)++cnt; else if(plug==2)--cnt; if(cnt==0)return i; } return -1; } il void cal(int x,int y){ int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; f[now].init(); F(i,1,tot){ int sta=f[last].sta[i];data val=f[last].val[i]; int p1=find(sta,y),p2=find(sta,y+1); if(link(sta,y)==-1||link(sta,y+1)==-1)continue; // pf(x);pf(y);phn; if(!p1&&!p2){ if(x!=n&&y!=m){ set(sta,y,1);set(sta,y+1,2);f[now][sta]+=val; } } else if(p1&&!p2){ if(x!=n)f[now][sta]+=val; if(y!=m)set(sta,y,0),set(sta,y+1,p1),f[now][sta]+=val; } else if(p2&&!p1){ if(y!=m)f[now][sta]+=val; if(x!=n)set(sta,y+1,0),set(sta,y,p2),f[now][sta]+=val; } else { if(p1==1&&p2==2){if(x==n&&y==m){ANS+=val;}} else if(p1==2&&p2==1){ set(sta,y,0);set(sta,y+1,0);f[now][sta]+=val; } else if(p1==1&&p2==1){ int pos=link(sta,y+1); set(sta,y,0);set(sta,y+1,0);set(sta,pos,1); f[now][sta]+=val; } else if(p1==2&&p2==2){ int pos=link(sta,y); set(sta,y,0);set(sta,y+1,0);set(sta,pos,2); f[now][sta]+=val; } } } } int main(){ n=read();m=read(); if(n==1||m==1){puts("1");return 0;} if(m>n){m^=n^=m^=n;}// f[0].init();f[0][0]=1; F(i,1,n){ F(j,1,m)cal(i,j); if(i!=n){ int now=(i*m)&1,tot=f[now].size; F(j,1,tot)f[now].sta[j]<<=2; } } ANS+=ANS;ANS.print(); } il int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,ch<'0'||ch>'9'); while(ch>='0'&&ch<='9'){s=s*10+(ch^48);ch=getchar();} return s; } /* g++ 1.cpp -g ./a.out 2 2 */
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define F(i,a,b) for(int i=a;i<=b;++i) #define LL long long #define rg register #define cri const rg int #define phn printf("\n") #define pf(a) printf("%d ",a) #define PF(a) printf("%lld ",a) #define il inline using namespace std; int read(); int n,m; //易错索引:h58,i<=m+1(轮廓线是m+1) const int cube=(int)1e9,mod=30007,NX=120007; #define ME(a) memset(a,0,sizeof(a)) LL ans; LL valu[2][NX]; int head[2][mod],size[2],state[2][NX],fir[2][NX]; il void init(int g){ memset(head[g],0,sizeof(head[g]));size[g]=0;//me(sta);me(fir); } il void add (const int &sta,const LL w,const int g){ int pos=sta%mod,i; for(i=head[g][pos];i;i=fir[g][i]){ if(state[g][i]==sta){valu[g][i]+=w;return;} } state[g][++size[g]]=sta;fir[g][size[g]]=head[g][pos];head[g][pos]=i=size[g];valu[g][i]=w; } il int find(rg int sta,cri id){return (sta>>((id-1)<<1))&3;} il void set(rg int &sta,rg int id,int val){ id=(id-1)<<1;sta|=3<<id;sta^=3<<id;sta|=val<<id; } il int link(cri sta,cri pos){ int cnt=0,g=(find(sta,pos)==1)?1:-1; for(int i=pos;i&&i<=m+1;i+=g){ //m+1: int plug=find(sta,i); if(plug==1)++cnt; else if(plug==2)--cnt; if(cnt==0)return i; } return -1; } int a[42][42],endx,endy; bool comp; il void cal(const int x,const int y){ int now=((x-1)*m+y)&1,last=now^1,tot=size[last]; init(now); F(i,1,tot){ rg int sta=state[last][i];LL val=valu[last][i]; cri p1=find(sta,y),p2=find(sta,y+1); // if(link(sta,y)==-1||link(sta,y+1)==-1)continue; // pf(x);pf(y);phn; if(a[x][y]){ if(!p1&&!p2)add(sta,val,now); continue; } if(!p1&&!p2){ if(x!=n&&y!=m&&!a[x+1][y]&&!a[x][y+1]){ set(sta,y,1);set(sta,y+1,2);add(sta,val,now); } } else if(p1&&!p2){ if(x!=n&&!a[x+1][y])add(sta,val,now); if(y!=m&&!a[x][y+1])set(sta,y,0),set(sta,y+1,p1),add(sta,val,now); } else if(p2&&!p1){ if(y!=m&&!a[x][y+1])add(sta,val,now); if(x!=n&&!a[x+1][y])set(sta,y+1,0),set(sta,y,p2),add(sta,val,now); } else { if(p1==1&&p2==2){if(x==endx&&y==endy){ans+=val;comp=1;}} else if(p1==2&&p2==1){ set(sta,y,0);set(sta,y+1,0);add(sta,val,now); } else if(p1==1&&p2==1){ set(sta,link(sta,y+1),1);set(sta,y,0);set(sta,y+1,0); add(sta,val,now); } else if(p1==2&&p2==2){ set(sta,link(sta,y),2);set(sta,y,0);set(sta,y+1,0); add(sta,val,now); } } } } int main(){ n=read();m=read(); // if(n==1||m==1){puts("1");return 0;} // if(m>n){m^=n^=m^=n;}// char ch[20]; F(i,1,n){scanf("%s",ch+1);F(j,1,m){if(ch[j]=='*')a[i][j]=1;else {endx=i;endy=j;}}} init(0);add(0,1,0); F(i,1,n){ F(j,1,m){ cal(i,j); if(comp==1){ printf("%lld",ans);return 0; } } if(i!=n){ int now=(i*m)&1,tot=size[now]; F(j,1,tot)state[now][j]<<=2; } } printf("%lld",ans); } il int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,ch<'0'||ch>'9'); while(ch>='0'&&ch<='9'){s=s*10+(ch^48);ch=getchar();} return s; } /* g++ 1.cpp -g ./a.out 4 4 **.. .... .... ..** */
五、小技巧
比较重要的:
1、哈希表。对于可证明的状态数比较小,但是范围比较大的。
DP可优化时间空间。记忆化dfs可优化空间。
常用于状压、轮廓线。
T:测试53 T2 状压期望
(dfs写在hash里超帅的)
#include<bits/stdc++.h> #define F(i,a,b) for(int i=a;i<=b;++i) #define LL long long #define PF(a) printf("%.6lf ",a) #define pf(a) printf("%d ",a) #define phn puts("") using namespace std; int read(); int n,m; char c[50]; const int mod=533317; inline double max(const double&a,const double&b){return a>b?a:b;} struct Hash{ #define M 20000010 double f[M];int vis[M]; int head[32][mod],fir[M],to[M],cnt; inline int get(const int &x,const int &y){ int t=y%mod; for(int i=head[x][t];i;i=fir[i]) if(to[i]==y)return i; to[++cnt]=y;fir[cnt]=head[x][t]; return head[x][t]=cnt; } int chg(int j,int pos){ int z=j&((1<<pos)-1); return (j>>(pos+1)<<pos)|z; } double dfs(int i,int j){ if(i==n-m)return 0; int t=get(i,j); if(vis[t])return f[t]; vis[t]=1; F(k,0,i-1){ f[t]+=max(dfs(i-1,chg(j,k))+((j>>k)&1), dfs(i-1,chg(j,i-1-k))+((j>>(i-1-k))&1)); } f[t]/=i; return f[t]; } }q; int main(){ //pf(chg((1<<5)-1-(1<<3),2)); n=read();m=read(); scanf("%s",c+1); int b=0; F(i,1,n)c[i]=='W'?(c[i]=1,b|=(1<<(i-1))):c[i]=0; double ans=q.dfs(n,b); printf("%.6lf\n",ans); } int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return f?-s:s; } /* g++ 2.cpp -g ./a.out 4 2 WBWB */
2、拆式子。如拆abs(讨论大小关系)、min
不很重要的:1、前缀和
2、倒着扫
SEC:写法
一、概率期望类
一般倒着转移。因为一般初状态一定,末状态有很多
1、最优性期望:
T:测试53T2。最优策略,直接在dfs时取两个下一步状态的max
2、期望性质:
测试32 chemistry: (osu树上高次版)
E(x+y)=E(x)+E(y)
E(x&&y)=E(x)*E(y)
E((x+y)^j)=sigma C(j,k)*E(x^k)*E(y^(j-k))
把多次方的期望拆分。考虑两两合并产生的贡献。
详见之前写的题解。
#include<bits/stdc++.h> #define F(i,a,b) for(rg int i=a;i<=b;++i) #define rg register #define pf(a) printf("%lld ",a) #define phn puts("") #define LL long long #define il inline using namespace std; #define int LL int read(); #define N 200010 int n,m,p,q; const int mod=1e9+7; int a[N],f[N][12],g[N][12],C[15][15]; int to[N<<1],fir[N<<1],head[N<<1],cnt; il int qpow(int x,int k){int s=1;for(;k;x=x*x%mod,k>>=1)if(k&1)s=s*x%mod;return s;} il void add(int x,int y){ to[++cnt]=y;fir[cnt]=head[x];head[x]=cnt; } void dfs(int x,int fa){ g[x][0]=1;f[x][0]=1; g[x][1]=p*a[x]%mod;f[x][1]=p*a[x]%mod; F(i,2,m){ g[x][i]=g[x][i-1]*a[x]%mod; f[x][i]=f[x][i-1]*a[x]%mod; } for(int i=head[x];i;i=fir[i]){ int v=to[i]; if(v==fa)continue; dfs(v,x); for(int j=m;j>=1;--j){ (f[x][j]+=f[v][j])%=mod; F(k,1,j-1){ (f[x][j]+=C[j][k]*g[x][k]%mod*g[v][j-k]%mod)%=mod; } //f[x][j]%=mod; } for(int j=m;j>=1;--j){ (g[x][j]+=p*g[v][j])%=mod; F(k,1,j-1){ (g[x][j]+=C[j][k]*g[x][k]%mod*g[v][j-k]%mod)%=mod; } //g[x][j]%=mod; } } } signed main(){ C[0][0]=1; F(i,1,12){ C[i][0]=1; F(j,1,i)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } n=read();m=read();p=read();q=read(); p=p*qpow(q,mod-2)%mod; int x,y; F(i,1,n)a[i]=read(); F(i,2,n){ x=read();y=read();add(x,y);add(y,x); } dfs(1,0); printf("%lld\n",f[1][m]); } il int read(){ int s=0;rg char ch; while(ch=getchar(),!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return s; } /* g++ 1.cpp -g time ./a.out 3 2 1 2 1 2 4 1 2 1 3 */ 复制代码
二、组合类
T:测试52 涂色游戏 组合+矩阵
这里主要想说其中一个系数。我用容斥做的,但是也可以递推求。两种写法。
另附:对于一类容斥系数:
要求恰好j种,容斥减去子集。
系数:j:1 (C(j,j))
j-1:-1*C(j,j-1)
j-2: 考虑上两个的贡献
-1*C(j,j-2)+C(j,j-1)*C(j-1,j-2)
化简后是 C(j,j-2) ,含义没有弄清楚。感性上似乎可以理解。还是感性理解吧。
j-k:经过归纳,发现是-1^(j-k)*C(j,j-k)
(19,10,4)add:组合数部分可以看成不是容斥系数,而是表达式一部分。
这样容斥系数就是+-1了。
#include<bits/stdc++.h> #define F(i,a,b) for(int i=a;i<=b;++i) #define LL long long #define pf(a) printf("%lld ",a) #define PF(a) printf("%lld ",a) #define phn puts("") using namespace std; #define int LL int read(); /** 为什么可以矩阵 取模优化错了。 有一个地方有减法,忘了+mod,出负数了。 有减法的地方一定要+mod 取模优化很快,几乎快了二倍,但是减法不加modu会出bug */ int n,m,p,q; const int mod=998244353; int f[105],c[105][105],s[105][105],a[105]; int max(int x,int y){return x>y?x:y;} int MO(const int x){return x<mod?x:x-mod;} int qpow(int x,int k){int s=1;for(;k;x=x*x%mod,k>>=1)if(k&1)s=s*x%mod;return s;} /* int D[105][105]; int ans; int T[100]; void dfs(int y,int x){ if(y==m+1){++ans;return ;} int tx=x==n?1:x+1,ty=x==n?y+1:y; F(i,1,p){ D[x][y]=i; if(x==n&y>1){ int cnt=0; F(i,1,n)F(j,y-1,y)if(D[i][j]&&(!T[D[i][j]]))T[D[i][j]]=1,++cnt; F(i,1,n)F(j,y-1,y)T[D[i][j]]=0; if(cnt<q)continue; } dfs(ty,tx); } }*/ void jzchs(){ const int ed=min(p,n); int g[105][105]={0}; F(k,1,ed){ F(i,1,ed){ F(j,1,ed){ g[i][j]=MO(g[i][j]+s[i][k]*s[k][j]%mod); } } } F(i,1,ed)F(j,1,ed)s[i][j]=g[i][j]; } void jzcc(){ const int ed=min(p,n); int g[105]={0}; F(k,1,ed){ F(j,1,ed){ g[j]=MO(g[j]+f[k]*s[k][j]%mod); } } F(j,1,ed)f[j]=g[j]; } signed main(){ n=read();m=read();p=read();q=read(); // dfs(1,1);printf("%lld\n",ans); c[0][0]=s[0][0]=1; F(i,1,100){ c[i][0]=1; F(j,1,i)c[i][j]=MO(c[i-1][j-1]+c[i-1][j]); } F(j,0,100){ F(k,1,j)a[j]=MO(a[j]+c[j][k]*qpow(k,n)*((j-k)&1?-1:1)%mod+mod); } const int ed=min(p,n); F(k,1,ed){ F(j,max(1,q-k),ed){ F(t,0,j+k-q)s[k][j]=MO(s[k][j]+c[p-k][j-t]*c[k][t]%mod); s[k][j]=s[k][j]*a[j]%mod; /** */ } } F(j,1,ed)f[j]=c[p][j]*a[j]%mod; for(int k=m-1;k;k>>=1,jzchs())if(k&1)jzcc(); /** 这里是m-1.*/ /* F(i,2,m){ F(j,1,ed){ F(k,max(1,q-j),ed){ f[i][j]=MO(f[i][j]+f[i-1][k]*s[k][j]%mod); } // f[i][j]=f[i][j]*a[j]%mod; } } // F(i,1,m){ F(j,1,ed)pf(f[i][j]);phn; }/** */ int ans=0; F(j,1,ed)ans=MO(ans+f[j]); ans=(ans+mod)%mod; printf("%lld\n",ans); } int read(){ int s=0,f=0;char ch; while(ch=getchar(),ch=='-'?f=1:0,!isdigit(ch)); for(;isdigit(ch);s=s*10+(ch^48),ch=getchar()); return f?-s:s; } /* g++ 2.cpp -g ./a.out 3 4 4 4 */
#include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; const int N=105; const int mod=998244353; int n,m,p,q; ll C[N][N],f[N][N],dp[N][N];//i种颜色 填入j个空位 struct Mat{ ll s[N][N]; friend Mat operator *(const Mat &a,const Mat &b){ Mat ans; for(int i=1;i<=p;++i) for(int j=1;j<=p;++j){ ans.s[i][j]=0; for(int k=1;k<=p;++k) ans.s[i][j]=(ans.s[i][j]+a.s[i][k]*b.s[k][j])%mod; } return ans; } friend Mat operator ^(Mat base,int k){ Mat ans; memset(ans.s,0,sizeof(ans.s)); for(int i=1;i<=p;++i) ans.s[i][i]=1; while(k){ if(k&1) ans=ans*base; base=base*base; k>>=1; } return ans; } }g,t; int main(){ scanf("%d%d%d%d",&n,&m,&p,&q); for(int i=0;i<=p;++i){ C[i][0]=1; for(int j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } f[0][0]=1; for(int i=1;i<=p;++i) for(int j=i;j<=n;++j) f[i][j]=(f[i][j-1]+f[i-1][j-1])*i%mod; for(int i=1;i<=p;++i) t.s[1][i]=C[p][i]*f[i][n]%mod; for(int j=1;j<=p;++j) for(int k=1;k<=p;++k){ ll tot=0; for(int d=max(q-k,0);d<=j;++d) tot+=C[p-k][d]*C[k][j-d]%mod; tot%=mod; g.s[k][j]=tot*f[j][n]%mod; } t=t*(g^(m-1)); ll ans=0; for(int i=1;i<=p;++i) ans+=t.s[1][i]; printf("%lld\n",ans%mod); return 0; }