继续复习动规。
单调队列优化DP(没错还是这个)
第一道,[SCOI2010]股票交易。看起来限制多,其实就是纸老虎。
可设f[i][j]指第i天拥有j张股票所赚的最大钱数,所以f[i][j]可为负,所以初值设为-inf。
四种情况:1.什么都不买,f[i][j]=max{f[i-1][j]};
2.第一次买,f[i][j]=max{-j*APi}(0<=j<=ASi);
3.之后的买,f[i][j]=max{f[i-W-1][k]+k*APi}-j*APi(j-ASi<=k<j);
4.卖,f[i][j]=max{f[i-W-1][k]+k*BPi}-j*BPi(j<k<=j+BSi);
前面两种直接O(n2),后面两种用单调队列优化,第三种从前往后,第四种从后往前,到这里就不难写了。
数据结构优化DP
第一道,[SCOI2014]方伯伯的玉米田。
首先得发现一个性质,每次修改的区间的右端点一定是最后一个点,证明如下:
对于这个区间外的左边的玉米,其最长不下降子序列会增大或不变。
而对于右边的玉米,其最长不下降子序列会减小或不变。故要减小右边的玉米数,当然为0最好。证明毕。
设f[i][j]指当前这个玉米被拔高后的高度为i且这个玉米被j个拔高区间覆盖的最长不下降子序列长度。
可得:f[i][j]=max{f[k][l]}+1(k<=i,l<=j)。单做要O(n4),但是前缀取最大值我们可以想到树状数组。
于是定义一个二维树状数组(就是我代码里的f数组),优化后的时间复杂度为O(n2*logn*logn);
这题想到性质很重要,数组的定义也很难想到(I am vegetable),再多写点同类型的题吧。
记得j要倒着枚举避免重复计算,代码的具体实现如下:

#include<bits/stdc++.h> #define Lowbit(i) (i&(-i)) using namespace std; const int N=1e4+1e3; int n,k,ans,d[N],f[5600][510]; int Max(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 Query(int x,int y){ int ss=0; for(int i=x;i;i-=Lowbit(i)) for(int j=y;j;j-=Lowbit(j)) ss=Max(ss,f[i][j]); return ss; } void Change(int x,int y,int ss){ for(int i=x;i<=5500;i+=Lowbit(i)) for(int j=y;j<=k+1;j+=Lowbit(j)) f[i][j]=Max(ss,f[i][j]); } int main(){ int x; n=rd(),k=rd(); for(int i=1;i<=n;i++) d[i]=rd(); for(int i=1;i<=n;i++) for(int j=k;j>=0;j--){ x=Query(d[i]+j,j+1)+1; if(x>ans) ans=x; Change(d[i]+j,j+1,x); } printf("%d ",ans); return 0; }
第二道,[BalticOI 2017] Toll。很有意思的题。
题目给出了(b/k)=1+(a/k)(括号为下取整号)。所以我们从这个方面去思考。
在思考了几个世纪后,我们发现(i/k)相同的有很多个i,将这些i当做一整块,于是n个地点就被分为ceil(n/k)个块。
然后又可以发现a->b恰好是一个块中的点到后面的一个块中的点,于是定义f[i]为i到终点的最短路,就可以用矩阵来转移状态。
| disx->a disx->b disx->c | | f[a] | | f[x] |
| disy->a disy->b disy->c | * | f[b] | = | f[y] |
| disz->a disz->b disz->c | | f[c] | | f[z] | (这里的*不是乘法,是新定义的运算C[i][j]=max{A[i][k]+B[k][j]})
然后想到了什么?没错,就是我们的动态dp。接下来就很简单了,把dis存下来放到线段树里,每次查询“l的块~r的块”的矩阵连乘。
又因为f[终点]=0而其他的f为无穷大,所以答案就是矩阵中的某个值。(由l,r决定哪行哪列)。
最后记得判断-1。代码的具体实现如下:

#include<bits/stdc++.h> using namespace std; const int N=5e4+1e3,M=7e8; int k,n,m,q,t; int Min(int x,int y){return x<y?x:y;} struct Num{ int p[6][6]; Num(){ memset(p,127/3,sizeof(p)); } inline Num operator*(Num b){ Num c; for(int i=1;i<=k;i++) for(int j=1;j<=k;j++) for(int l=1;l<=k;l++) c.p[i][j]=Min(c.p[i][j],p[i][l]+b.p[l][j]); return c; } }d[N],f[N*4]; 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; } void Build(int x,int l,int r){ if(l==r){ f[x]=d[l]; return ; } int mid=(l+r)>>1; Build(x<<1,l,mid); Build(x<<1|1,mid+1,r); f[x]=f[x<<1]*f[x<<1|1]; } Num Query(int x,int l,int r,int L,int R){ if(L<=l&&r<=R) return f[x]; int mid=(l+r)>>1; if(mid>=R) return Query(x<<1,l,mid,L,R); else if(mid+1<=L) return Query(x<<1|1,mid+1,r,L,R); else return Query(x<<1,l,mid,L,R)*Query(x<<1|1,mid+1,r,L,R); } int main(){ int x,y,z; Num A; k=rd(),n=rd(),m=rd(),q=rd();t=ceil(1.0*n/k); while(m--){ x=rd(),y=rd(),z=rd(); d[y/k].p[x%k+1][y%k+1]=z; } Build(1,1,t-1); while(q--){ x=rd(),y=rd(); if(x/k+1<=y/k) z=Query(1,1,t-1,x/k+1,y/k).p[x%k+1][y%k+1];// 别忘了这里的if else z=M+1; z>M?printf("-1 "):printf("%d ",z); } return 0; }
状压DP
第一道,花园。
由m<=5很容易想起状压dp,于是设f[i][j]为当前做到第i盆花且最后m盆花的状态为j的方案数。
这个定义非常快乐,然后我们快乐地转移。那么怎么处理环形呢?
我们只需要每次给一种状态j的f[m][j]赋值为1,并做到f[n+m][j],将答案累加即可。
再就快乐地提交,然后快乐地WA(不是。
发现n有1015,考虑快速幂。f的转移可以用邻接矩阵代替,因此需要矩阵快速幂。
到这里应该就没问题了,上代码:

#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e5+1e4,P=1e9+7; int m,k,ans,tot,f[41],g[41]; ll n; struct Num{ int p[41][41]; Num(){ memset(p,0,sizeof(p)); } }b,c; ll rd(){ ll 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; } void Dfs(int x,int s,int d){ if(x>m){ if(d<=k) f[s]=++tot,g[tot]=s; return ; } Dfs(x+1,s<<1,d); Dfs(x+1,s<<1|1,d+1); } Num Chen(Num A,Num B){ Num C; for(int i=1;i<=tot;i++) for(int j=1;j<=tot;j++) for(int k=1;k<=tot;k++) C.p[i][j]=(C.p[i][j]+(ll)A.p[i][k]*B.p[k][j]%P)%P; return C; } int main(){ int x; n=rd(); m=rd(); k=rd(); Dfs(1,0,0); for(int i=1;i<=tot;i++) c.p[i][i]=1; for(int i=1;i<=tot;i++){ x=(g[i]>>1)+(1<<(m-1)); if(f[x]) b.p[f[x]][i]=1; x=(g[i]>>1); if(f[x]) b.p[f[x]][i]=1; } while(n){ if(n&1) c=Chen(b,c); b=Chen(b,b); n>>=1; } for(int i=1;i<=tot;i++) ans=(ans+c.p[i][i])%P; printf("%d ",ans); return 0; }
最后做了道小水题[EER2]谔运算,对每个数位进行讨论就行了。行吧就这样,明天再说。拜拜。