今天是7月13号,现在打算冲国银。
从现在到国赛前,每天会复习或学习知识点。
今天是复习动态规划。
状压DP
第一道,[NOI2001]炮兵阵地。很明显也很好写的状压dp。
dp[i][j][k]是第i行状态为j,第i-1行状态为k的方案数,再枚举一重状态即可。挺经典。
第二道,[SDOI2009]Bill的挑战。定义的数组稍微有点不一样。
预处理时有个辅助数组f[i][j],指第i位的字符可以为j的字符串的集合。
dp[i][j]指的是当前做到字符串第i位,满足条件的字符串的集合为j的方案数。
最后求值的时候要判断j中1的个数刚好等于k。
第三道,[NOI2015]寿司晚宴。原题可转化为“将2~n中的部分数字分为两组,一个组中的所有数的质因数集合与另一个组的质因数集合无交集”。
这样我们就可以想到设dp[i][j]指A组的质因数集合为i,B组的质因数集合为j(i&j!=0)。再遍历每个数可以放入A组或B组或不放。
然后发现每个数大于22的质因数至多一个,把它拎出并用它排序,保证大于22的质因数相同的一些数在一起。
再设fa[i][j],fb[i][j],初值设为dp[i][j],定义和dp[i][j]一样,但是一个专门放入A组,一个专门放入B组。
质因数相同的一些数弄完之后,dp[i][j]=fa[i][j]+fb[i][j]-dp[i][j]。因为fa和fb是包括了dp初值的,所以dp[i][j]=专门放入A组+专门放入B组+不放入任何一组。
具体看代码实现:
#include<bits/stdc++.h> #define ll long long using namespace std; int n,p[11]={2,3,5,7,11,13,17,19}; ll P,ans,dp[261][261],fa[261][261],fb[261][261]; struct nod{ int s,h; }a[510]; bool operator>(nod aa,nod bb){ return aa.h<bb.h; } bool operator<(nod aa,nod bb){ return aa.h>bb.h; } int main(){ int x; scanf("%d%lld",&n,&P); for(int i=2;i<=n;i++){ x=i; for(int j=0;j<8;j++) if(!(x%p[j])){ a[i].s|=(1<<j); while(!(x%p[j])) x/=p[j]; } a[i].h=x; } sort(a+2,a+1+n); dp[0][0]=1; for(int i=2;i<=n;i++){ if(a[i].h!=a[i-1].h||a[i].h==1){ memcpy(fa,dp,sizeof(dp)); memcpy(fb,dp,sizeof(dp)); } for(int j=255,t;j>=0;j--) for(int k=j;k>=0;k=(k-1)&j){ t=(j^k);//这里k是枚举j的子集,于是j^k和k无交集 if(!(a[i].s&k)) fa[t|a[i].s][k]=(fa[t|a[i].s][k]+fa[t][k])%P; if(!(a[i].s&t)) fb[t][k|a[i].s]=(fb[t][k|a[i].s]+fb[t][k])%P; if(!k) break; } if(a[i].h!=a[i+1].h||a[i].h==1) for(int j=255,t;j>=0;j--) for(int k=j;k>=0;k=(k-1)&j){ t=(j^k); dp[t][k]=(fa[t][k]+fb[t][k]-dp[t][k]+P)%P; if(!k) break; } } for(int j=255;j>=0;j--) for(int k=j;k>=0;k=(k-1)&j){ ans=(ans+dp[j^k][k])%P; if(!k) break; } printf("%lld ",ans); return 0; }
倍增优化DP
第一道,跑路。是有点水,好像和动规没关系?先把一步能到达的路连起来再floyd就完事了。
单调性优化DP
第一道,[NOI2011]NOI 嘉年华。
首先将时间离散化,并设f[i][j],g[i][j],表示1~i的时间内第一个嘉年华有j个活动时的第二个嘉年华最大活动数,i~T的时间内第一个嘉年华有j个活动时的第二个嘉年华最大活动数。
再预处理d[i][j]指i~j时间内的所有活动数,即可O(n3)dp出f[i][j],g[i][j],其中max{min(j,f[T][j])}为第一个答案。
要使第i个活动必须举办,则设dp[i][j]为i~j时间内的活动必须举办的最佳答案,第i+1个答案就是max{dp[l][r]}(1<=l<=s[i],s[i]+t[i]<=r<=T)。
接下来只要求出dp[l][r]就可以了,可知dp[l][r]=max{min(x+y+d[l][r],f[l][x]+g[r][y])}。(x指l左边的在第一个嘉年华的活动数,y指r右边的在第一个嘉年华的活动数)
乍一看要O(n4),事实上发现x越大,y的最佳值越小,所以利用单调性就可以优化一重时间复杂度。对了,要注意细节。
具体看代码实现:
#include<bits/stdc++.h> using namespace std; int n,ct,ans,s[210],t[210],b[410],d[410][410],f[410][210],g[410][210],dp[410][410]; map<int,int>q; int Max(int x,int y) {return x>y?x:y;} int Min(int x,int y) {return x<y?x:y;} int rd(){ int s=0,ff=1; char w=getchar(); while(!isdigit(w)) ff^=w=='-',w=getchar(); while(isdigit(w)) s=s*10+w-'0',w=getchar(); return ff?s:-s; } int main(){ n=rd(); int y; for(int i=1;i<=n;i++) s[i]=rd(),t[i]=s[i]+rd(),b[i*2-1]=s[i],b[i*2]=t[i]; sort(b+1,b+1+n*2); for(int i=1;i<=n*2;i++) if(b[i]!=b[i-1]||i==1) q[b[i]]=++ct; for(int i=1;i<=n;i++) s[i]=q[s[i]],t[i]=q[t[i]]; for(int i=1;i<=ct;i++) for(int j=i;j<=ct;j++) for(int k=1;k<=n;k++) if(i<=s[k]&&t[k]<=j) d[i][j]++; memset(f,-127/3,sizeof(f)); memset(g,-127/3,sizeof(g)); f[1][0]=0; for(int i=2;i<=ct;i++) for(int j=0;j<=n;j++) for(int k=1;k<i;k++) f[i][j]=Max(f[i][j],Max(f[k][Max(j-d[k][i],0)],f[k][j]+d[k][i])); for(int j=0;j<=n;j++) ans=Max(ans,Min(j,f[ct][j])); printf("%d ",ans); g[ct][0]=0; for(int i=ct-1;i>=1;i--) for(int j=0;j<=n;j++) for(int k=i+1;k<=ct;k++) g[i][j]=Max(g[i][j],Max(g[k][Max(j-d[i][k],0)],g[k][j]+d[i][k])); for(int l=1;l<=ct;l++) for(int r=l+1;r<=ct;r++){ y=n; while(y&&g[r][y]<0) y--; for(int x=0;x<=n;x++){ if(f[l][x]<0) break; while(y&&Min(x+y+d[l][r]-1,f[l][x]+g[r][y-1])>=Min(x+y+d[l][r],f[l][x]+g[r][y])) y--;//注意这里取等号 dp[l][r]=Max(dp[l][r],Min(x+y+d[l][r],f[l][x]+g[r][y])); } } for(int i=1;i<=n;i++){ ans=0; for(int l=1;l<=s[i];l++) for(int r=t[i];r<=ct;r++) ans=Max(ans,dp[l][r]); printf("%d ",ans); } return 0; }
今天就写了这么多,明天加油吧。