DP杂题
①:给定序列A,求出序列A中本质不同的子序列.
sol:①考试中用了容斥,我们发现之所以会产生本质不同的子序列是因为序列中存在相同的数字.用f[i]表示A区间[1,i]本质不同子序列的个数.我们用la[i]表示上一个与val[i]相同的位置.考虑转移,f[i] = f[i-1]+f[i-1]+1.第一个f[i-1]表示继承上一个状态的本质不同的子序列.第二个f[i-1]表示当前数字作为最后一个位置与上一个状态组合个数,加这个数本身作为子序列的影响.同时我们要减去前一个'相同值'位置产生的'价值'(这里会产生本质相同的子序列),这里用数组g表示历史la[i]产生的影响.
②:设 f[i,j]表示到了第 i 位, 以数字 j 结尾的不同子序列有多少.转移:f[i,j] = Σf[i-1,k],k ≠ j.
code:
#include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define e exit(0) #define re register #define LL long long const LL mod = 1e9+7; const LL maxn = 1000010; LL n,ans,f[maxn],g[maxn],a[maxn],lapos[maxn],vis[maxn]; inline LL fd(){ LL s=1,t=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();} while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();} return s*t; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); n = fd(); for(re LL i=1;i<=n;++i){ a[i] = fd(); if(!vis[a[i]]){ lapos[i] = 0; vis[a[i]] = i; } else if(vis[a[i]]){ lapos[i] = vis[a[i]]; vis[a[i]] = i; } } f[1] = g[1] = 1; for(re LL i=2;i<=n;++i){ f[i] = (f[i-1]+f[i-1]+1)%mod; LL pos = lapos[i]; g[i] = (f[i-1]+1)%mod; f[i] = ((f[i]-g[pos])%mod+mod)%mod; } ans = ((f[n]+1)%mod+mod)%mod; printf("%lld",ans); return 0; }
②:给定只由1,2数字组成的序列,现在有一次机会任选区间[L,R]翻转,求最长不下降子序列.
sol:我们考虑在1,2组成的最长不降子序列1,1...2,2,2或1,1,...1,1,1两种形式.我们记pre[i]表示[1,i]区间1的个数,suf[i]表示[i,n]区间2的个数,f[L][R][0]表示以1结尾最长单调不减子序列长度,f[L][R][1]表示以2结尾单调不减子序列.显然答案为max{pre[L-1]+max(f[L][R][0],f[L][R][1])+suf[R+1]}.pre[i],suf[i]直接维护,f[L][R][2]显然是区间[L,R]2的个数,f[L][R][1] = max(f[L][R-1][0],f[L][R-1][2])+(val[R] == 1).
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define e exit(0) #define re register const int maxn = 3010; int n,ans,a[maxn],pre[maxn],suf[maxn],f[maxn][maxn][3]; inline int fd(){ int s=1,t=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();} while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();} return s*t; } int main() { freopen("SlayTheSparrow.in","r",stdin); freopen("SlayTheSparrow.out","w",stdout); n = fd(); for(re int i=1;i<=n;++i) a[i] = fd(); for(re int i=1;i<=n;++i) pre[i] = pre[i-1]+(a[i]==1); for(re int i=n;i>=1;--i) suf[i] = suf[i+1]+(a[i]==2); for(re int L=1;L<=n;++L) for(re int R=L;R<=n;++R){ f[L][R][2] = f[L][R-1][2]+(a[R]==2); f[L][R][1] = max(f[L][R-1][1],f[L][R-1][2])+(a[R]==1); } for(re int L=1;L<=n;++L) for(re int R=L;R<=n;++R) ans = max(ans,pre[L-1]+max(f[L][R][1],f[L][R][2])+suf[R+1]); printf("%d",ans); return 0; }
③:给定n个物品容量v,价值w.给定背包最大容量V,每个背包至多选一次.求可以组成严格第k大W.(严格定义:2,2,3,3排第2位).
sol:考虑DP方程f[v][k]表示容量为v(可以不选满),维护第k的值.这里我们不可以将f[v][k]去进行max,min之类的操作,因为我们要将每个状态的值存下来不更新,才能比出前k大.这里用Q数组,G数组储存当前i物品选与不选的状态,可以保证k增大Q,G数组一定单调不增,只要用归并排序最后merge操作去维护前k个值即可,注意要保证严格第k大,可能要将进行超过k次选择.
code:
#include<iostream> #include<cstring> #include<cstdio> using namespace std; #define e exit(0) #define re register #define LL long long LL f[3001][201]; int n,v,k,cnt1,cnt2,w[3001],cost[3001],Q[3001],G[3001]; inline int fd(){ int s=1,t=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();} while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();} return s*t; } int main() { freopen("bike.in","r",stdin); freopen("bike.out","w",stdout); n = fd(),v = fd(),k = fd(); for(re int i=1;i<=n;++i) w[i] = fd(),cost[i] = fd(); for(re int i=1;i<=n;++i){ for(re int j=v;j>=cost[i];--j){ for(re int p=1;p<=k;++p) Q[p] = f[j-cost[i]][p]+w[i],G[p] = f[j][p]; cnt1 = cnt2 = 1; for(re int p=1;p<=k;++p){ if(!Q[cnt1]&&!G[cnt2]) break; if(Q[cnt1] > G[cnt2]) f[j][p] = Q[cnt1++]; else f[j][p] = G[cnt2++]; if(f[j][p] == f[j][p-1]) --p; } } } printf("%lld",f[v][k]); return 0; }
④:给定一棵树,给定m种颜色(m≥2),要求树上每一个点都必须染色,每一种染色一定出现.并要求有一种颜色一定要染色节点P,且此颜色不多不少地出现k次.注意当相同颜色出现在同一条边时会获得这条边的权值,现在求满足上述染色获得的最小权值.
sol:显然是树形DP.f[x][y][0/1]表示x即x的子树有j个p节点的颜色且x号节点染/不染p节点的颜色获得最小权值.
f[x][y][0] = min{f[x][y][0],f[son][t][0]+f[x][y-t][0]+(m==2)*len[i].v,f[son][t][1]+f[x][y-t][0]};
f[x][y][1] = min(f[x][y][1],f[son][t][1]+f[x][y-t][1]+len[i].v,f[son][t][0]+f[x][y-t][0]);
注意x可能存在多个儿子,这里的f[x][y-t][0],f[x][y-t][1]一定是上一次儿子更新x的状态的值,需要用额外的数组存下来.
可以理解为0/1背包的物品多阶段转移,单个儿子的价值不能累加.
code:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const int maxn = 1e5+5;
int f[630][630][3],la[630][3];
int n,m,p,k,num,cnt,lx,ly,fa[maxn],head[maxn],Q[maxn];
struct bian{int to,next,v;}len[maxn<<1];
inline int fd(){
int s=1,t=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')s=-1;c=getchar();}
while(c>='0'&&c<='9'){t=t*10+c-'0';c=getchar();}
return s*t;
}
void add(int from,int to,int v){
len[++cnt].next=head[from];
len[cnt].to=to;
len[cnt].v=v;
head[from]=cnt;
}
void work1(){
LL ans = 0;
sort(Q+1,Q+1+lx);
for(re int i=1;i<=k-1;++i)
ans += Q[i];
printf("%lld",ans);
}
void work2(){
LL ans = 0;
sort(Q+1,Q+1+lx);
if(num==5){printf("0");return;}
if(num==4){
for(re int i=1;i<=n-2*k;++i)
ans += Q[i];
printf("%lld",ans);
return;
}
}
void DP(int x,int fa){
f[x][1][1]=f[x][0][0]=0;
for(re int i=head[x];i;i=len[i].next){
int to=len[i].to,v=len[i].v;
if(to==fa) continue;
DP(to,x);
memcpy(la,f[x],sizeof(la));
memset(f[x],63,sizeof(f[x]));
for(re int s=0;s<=k;++s){
for(re int t=0;t<=s;++t){
f[x][s][0]=min(f[x][s][0],min(f[to][t][1]+la[s-t][0],f[to][t][0]+(m==2)*v+la[s-t][0]));
f[x][s][1]=min(f[x][s][1],min(f[to][t][1]+la[s-t][1]+v,f[to][t][0]+la[s-t][1]));
}
}
}
}
void work3(){
memset(f,63,sizeof(f));
DP(p,0);
printf("%d",f[p][k][1]);
}
int main()
{
freopen("fish.in","r",stdin);
freopen("fish.out","w",stdout);
n = fd(),m = fd(),p = fd(),k = fd(),num = fd();
for(re int i=1;i<=n-1;++i){
int x = fd(),y = fd(),w = fd();
add(x,y,w),add(y,x,w);
Q[++lx] = w;
}
if(num==2||num==3){work1();return 0;}
else if(num==4||num==5){work2();return 0;}
else{work3();return 0;}
return 0;
}