DP总结(优化等)
留两个坑:1、测试51or(Get) 2、杂题3最大前缀和 (3、测试48group)4、相逢是问候(这个非DP),分手是祝愿。测试53,55。
树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 */
3、测试88T1维护身高分成k段。斜率优化式子。
` 1.5:关于一类四边形不等式:
定义比较复杂,但只需记住比较重要一点的是:
二维DP中
定义S(i,j)为F(i,j)最优决策点。
当S(i,j-1)<=S(i,j)<=S(i+1,j)时,可以记录决策点,在S(i,j-1)到S(i+1,j)之间找S(i,j).
把F(i,1,n)F(j,i,n)F(k,i,j)的n^3优化为n^2.
T:1、石子合并 2、测试93二叉搜索树。
同样适用于一维DP。一维则要维护单调队列,记录三元组(j,l,r)表示决策点j的适用区间。
弹队首,弹队尾,放队尾,更新队尾范围。在队尾区间二分当前点适用区间的l。
2:数据结构。线段树。用于维护决策点/dp值。维护若干相同的操作。
进阶,可以线段树合并维护DP。
T:一类线段树优化DP:1、队长快跑。2、测试92数对:a,b两个数组,有aj<=bii限制。
讨论决策点j<=min(a,b),和a<j<=b;线段树实现区间查询最大,单点修改,区间加。
3、测试87 bird:vector差分线段。线段树动态维护决策点最优。
4、测试90 角度斜率离散话,线段树区间最值。注意两个0特判。
一二进制,状压
fr :测试109 T1:转化。压缩。预处理。递推。继承。
K条路压缩为二进制数,枚举当前状态每一位,&1位直接异或。k^2->k
2进制减lowbit递推每个状态(仅&1位作用)。k->1.
详见B&哥做法
一数学优化
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 */
一枚举方式。
背包类一维范围小,一维范围极大,f数值小时,调换大范围一维和小范围结果。
T:测试92口胡题T3。(折半思想。这题折半可以10+20,而非15+15)
一网格/二维
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 **.. .... .... ..** */
考场想出的8进制压位记录各位置所属集合:
#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 pf(a) printf("%lld ",a) #define phn puts("") using namespace std; #define int LL #define N 600010 LL n; int prm[8],cnt,a[8]; int c[N],bin[N],bl[N]; const int mod=1e9+7; int ask(int s,int pos){ pos=(pos-1)*3; return (s>>pos)&7; } void sit(int &x,int pos,int w){ pos=(pos-1)*3; x=((x|(7<<pos))^(7<<pos))|(w<<pos); } void MO(int &x){x<mod?x:x-=mod;} const int HP=233713; struct nd{ LL val[N]; int head[HP],to[N],fir[N],cnt; void clear(){ cnt=0;memset(head,0,sizeof(head)); } inline LL &operator[](const int s){ int t=s%HP; for(int i=head[t];i;i=fir[i])if(to[i]==s)return val[i]; to[++cnt]=s;fir[cnt]=head[t]; return val[head[t]=cnt]=0; } }f[2]; signed main(){ scanf("%lld",&n); int x=n; for(int i=2,ed=sqrt(n);i<=ed;++i){ if(x%i==0){ ++cnt; while(x%i==0){ x/=i,++a[cnt]; } } } if(x>1)a[++cnt]=1; const int mx=(1<<(cnt*3))-1,ed=(1<<cnt)-1; c[0]=1; for(int i=1,j=1;j<=cnt;++j,i<<=1)bin[i]=j; F(i,1,ed){ c[i]=c[i^(i&-i)]*a[bin[i&-i]]%mod; // bl[i]=bin[i&-i]; } f[0].clear(); f[0][0]=1;rg int p=0;/** */ LL ans=0; F(i,1,cnt*2){ p=!p; f[p].clear(); for(int u=1,id,ok,t,fir,s;u<=f[!p].cnt;++u){ s=f[!p].to[u]; MO(ans+=f[!p].val[u]); F(j,1,ed){ id=0,ok=1;t=s;fir=0; F(k,1,cnt){ if((j>>(k-1))&1){ if(ask(s,k)==7){ok=0;break;} if(ask(s,k)==0&&!fir)fir=k; if(id==0){ id=ask(s,k); if(id)sit(t,k,7); } else{ if(ask(s,k)>0&&id!=ask(s,k)){ok=0;break;} else{ if(ask(s,k)>0)sit(t,k,7); } } } } if(ok){ if(fir)F(k,1,cnt){ if((j>>(k-1))&1){ if(!ask(t,k))sit(t,k,fir); } } f[p][t]=(f[p][t]+f[!p].val[u]*c[j])%mod; // if(f[!p][s]){ // pf(i);pf(s);pf(j);pf(t);pf(f[p][t]);phn; } } } } } for(int i=1;i<=f[p].cnt;++i)MO(ans+=f[p].val[i]); MO(ans=ans-1+mod); printf("%lld\n",ans); } /* g++ 2.cpp -g -O2 time ./a.out 12156144 */
3、差分思想。维护线段(一行信息)时只记录左端点。转移到下一行时看两行对应段划分方式,相同可以合并。
#include<bits/stdc++.h> #define F(i,a,b) for(int i=a;i<=b;++i) #define pf(a) printf("%d ",a) #define phn puts("") using namespace std; int read(); int n,m; #define N 105 int a[105][105]; int f[105][1<<8]; const int inf=1e8; int main(){ n=read();m=read(); F(i,1,n)F(j,1,m)a[i][j]=read(); int ed=(1<<m)-1; memset(f,0x3f,sizeof(f)); int t,x; int c[10]={0}; f[0][0]=0; F(i,0,n-1){ for(int s=0,ok;s<=ed;++s){ ok=1; F(j,1,m){ if(a[i][j]==0){ if((s>>(j-1))&1){ ok=0;break; } c[j]=0; } else if(a[i][j]==1){ if((s>>(j-1))&1){ c[j]=j; } else if(c[j-1])c[j]=c[j-1]; else {ok=0;break;} } } if(!ok)continue; for(int t=0,w,las;t<=ed;++t){ w=0;las=0; F(j,1,m){ if(a[i+1][j]){//判结尾 if((t>>(j-1))&1){ ++w; las=j; } else if(!las){ w=inf;break; } if(a[i+1][j+1]==0||((t>>(j+1-1))&1)){ if(las==c[j]&&c[j+1]!=c[j])--w; } } else { las=0; if((t>>(j-1))&1){w=inf;break;} } } f[i+1][t]=min(f[i+1][t],f[i][s]+w); } } } int ans=inf; F(s,0,ed){ ans=min(ans,f[n][s]); } printf("%d\n",ans); } int read(){ int s=0,f=0;char ch=getchar(); while(!isdigit(ch))f=ch=='-',ch=getchar(); while(isdigit(ch))s=s*10+(ch^48),ch=getchar(); return f?-s:s; } /* g++ 2.cpp ./a.out 4 4 1 1 1 0 1 1 1 1 0 0 1 1 0 0 1 1 */
一小技巧:
比较重要的:
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:写法
a:
思路导引:
猜测约束(条件)用途。根据约束发现性质,使状态可以被简化。(构造计数类)
根据数据范围考虑状态框架。
一、概率期望类
一般倒着转移。因为一般初状态一定,末状态有很多
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; }
三、树相关DP
1、二叉树先序中序遍历性质。
规定X序遍历序列,可知树性质。
例:
测试76C:计数
先序,编号为 i的节点在二叉树的前序遍历中恰好是第 i个出现.
则:1、子树编号>u.
2、若有左儿子,编号u+1.
3、一棵子树中编号连续。
4、左树编号<右树。
本题另一个约束:m个中序遍历下u,v编号先后关系。(2)
统计合法带标号二叉树的个数。
通过限制v在u左右子树或不在同一子树可满足约束(2).
归纳为v是否在u左子树。
又,约束(1)提供子树编号连续,及子节点id较大性质。
则可知u左子树sz范围。
F[i][j],i为根子树,大小为j方案数。
简单DP即可。
N<=400,m<=1000,O(n^3)
回过来看m这个条件的用法。
1、状态不能准确描述树的具体形态,
2、约束2不能准确找到,且一个点所受约束个数不定,0~多个,无法一对一。则考虑约束2限定一些范围。而不是在转移时u->v一对一。
(猜测用途)
3、N^3,考虑二维DP状态f[i][j].
(根据数据范围考虑状态框架)
4、最重要的是约束1,使id连续,且深度深id大。
使可以根据左子树大小范围确定两点关系。
(根据约束发现性质)
Code:
#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 phn puts("") using namespace std; #define int LL int read(); #define N 505 int n,m; int f[N][N]; const int mod=1e9+7; int top[N],lea[N];//左子树 void work(){ n=read();m=read(); int u,v; F(i,1,n){ lea[i]=0;top[i]=n-i; } memset(f,0,sizeof(f)); F(i,1,m){ u=read();v=read(); if(u<v){ top[u]=min(top[u],v-1-u); } else{ lea[v]=max(lea[v],u-v); } } // F(i,1,n){ pf(lea[i]);pf(top[i]);phn; } f[n][1]=1;f[n][0]=1;f[n+1][0]=1; for(int i=n-1,ed;i>0;--i){ f[i][0]=1; if(lea[i]>top[i]){ puts("0");return ; } F(j,lea[i]+1,n-i+1){ ed=min(j-1,top[i]); F(k,lea[i],ed){ f[i][j]=(f[i][j]+f[i+1][k]*f[i+1+k][j-1-k])%mod; } } } printf("%lld\n",f[1][n]); } signed main(){ int T=read(); while(T--)work(); } int read(){ int s=0,f=0;char ch=getchar(); while(!isdigit(ch))f=ch=='-',ch=getchar(); while(isdigit(ch))s=s*10+(ch^48),ch=getchar(); return f?-s:s; } /* g++ 3.cpp ./a.out 3 5 0 3 2 1 2 2 3 3 3 1 2 2 3 3 1 */
2、树形背包,子树信息合并。
3、考虑每条边贡献。
四、其它一些
g[i][j][0/1],i:a->len; j:b->len.0:a,1:b;每次递归时重新求。数据小复杂度正确。不必苛求预处理。
#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 PF(a) printf("%lld ",a) #define pdb(a) printf("%.3lf ",a) #define phn puts("") using namespace std; #define int LL #define N 505 int read(); const int mod=998244353,inv2=499122177; int n,a[N]; int f[12][N][N],dp[N];//相对位置。 int g[N][N][2]; int qpow(int x,int k){int s=1;for(;k;k>>=1,x=x*x%mod)if(k&1)s=s*x%mod;return s;} struct nd{ int w,pos; friend int operator <(nd x,nd y){ return x.w<y.w||(x.w==y.w&&x.pos<y.pos); } }b[N]; void solve(int o,int l,int r){ if(l==r){ f[o][b[l].pos][1]=1;return ; } int mid=l+r>>1; solve(o-1,l,mid);solve(o-1,mid+1,r); sort(b+l,b+mid+1);sort(b+mid+1,b+r+1); int p1=l-1,p2=mid,l1,l2,s1=0,s2=0; F(w,1,1000){ s1=s2=0; l1=p1,l2=p2; while(p1<mid&&b[p1+1].w==w)++p1,++s1; while(p2<r&&b[p2+1].w==w)++p2,++s2; if(!s1&&!s2)continue; F(i,0,s1)F(j,0,s2)g[i][j][0]=g[i][j][1]=0; g[0][0][0]=1; F(i,0,s1){ F(j,0,s2){ if(i<s1&&j<s2){ (g[i+1][j][0]+=(g[i][j][0]+g[i][j][1])*inv2)%=mod; (g[i][j+1][1]+=(g[i][j][0]+g[i][j][1])*inv2)%=mod; } else if(i<s1)(g[i+1][j][0]+=(g[i][j][0]+g[i][j][1]))%=mod; else if(j<s2)(g[i][j+1][1]+=(g[i][j][0]+g[i][j][1]))%=mod; } } /* if(o==1){ PF(l);PF(r);puts("a"); F(i,0,1){F(j,0,1)pdb(g[i][j][0]);phn;}phn; F(i,0,1){F(j,0,1)pdb(g[i][j][1]);phn;}phn; }*/ F(i,l1+1,p1){ F(x,1,s1){ F(y,0,s2){ (f[o][b[i].pos][x+y]+=f[o-1][b[i].pos][x]*g[x][y][0])%=mod; } } } F(i,l2+1,p2){ F(y,1,s2){ F(x,0,s1){ (f[o][b[i].pos][x+y]+=f[o-1][b[i].pos][y]*g[x][y][1])%=mod; } } } /* if(o==1&&w==2){ puts("b"); pdb(g[0][1][1]);pdb(f[o][2][1]);phn; }*/ } } signed main(){ n=read();F(i,1,n)a[i]=read(); F(i,1,n)b[i]=(nd){a[i],i}; int ed=0; while((1<<ed)<n)++ed; solve(ed,1,n); sort(b+1,b+n+1); int p=0,s=0,las=0; /* F(o,0,3){ F(i,1,n){ F(j,1,n)pdb(f[o][i][j]);phn;} phn;}*/ F(w,1,1000){ s=0; while(p<n&&b[p+1].w==w)++p,++s; if(!s)continue; F(i,p-s+1,p){ F(j,1,s){ (dp[b[i].pos]+=f[ed][b[i].pos][j]*(las+j))%=mod; } } las=p; } F(i,1,n){ printf("%lld ",dp[i]); }phn; } int read(){ int s=0,f=0;char ch=getchar(); while(!isdigit(ch))f=ch=='-',ch=getchar(); while(isdigit(ch))s=s*10+(ch^48),ch=getchar(); return f?-s:s; } /* g++ 2.cpp ./a.out 5 5 2 1 2 2 */