2017 UESTC Training for Dynamic Programming
A 思维, 或 dp, 很有意思
方法1:
构造法:蛇形安排赛程表
算法复杂度:O(N^2)
将1-N排成两竖列,每一轮同一行的为对手
保持1的位置不变,其他位置按顺(逆)时方向依次旋转
1 6 1 2 1 3 1 4 1 5
2 5 3 6 4 2 5 3 6 4
3 4 4 5 5 6 6 2 2 3
1 N
2 N-1
3 N-2
. .
. .
. .
N/2 N/2+1
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 200005; int n, a[N]; int main() { scanf("%d", &n); rep(i,2,n) a[i]=i; rep(i,n+1,2*n-1) a[i]=a[i-(n-1)]; rep(i,2*n,3*n-2) a[i]=a[i-(n-1)]; rep(i,n+1,2*n-1) { printf("1 %d ", a[i]); rep(ca,1,(n-2)/2) { printf("%d %d ", a[i-ca], a[i+ca]); } puts(""); } return 0; }
方法2:
DP做法
算法复杂度:O(N^3)
如果我们知道4支队伍的赛程表,就可以推出8支队伍的赛程表
1-2 3-4
1-3 2-4
1-4 2-3
第一部分:将8支队伍分成两组进行组内对抗
1-2 3-4 5-6 7-8
1-3 2-4 5-7 6-8
1-4 2-3 5-8 6-7
第二部分:两组队伍进行组间对抗
1-5 2-6 3-7 4-8
1-6 2-7 3-8 4-5
1-7 2-8 3-5 4-6
1-8 2-5 3-6 4-7
所以,我们如果能知道N(N为偶数)支球队的赛程表,就可以推出2*N支球队的赛程表
如果N为奇数,N支球队的赛程表和2*N支球队的赛程表该怎么推呢?
N为奇数的话,赛程表也得进行N轮,每轮会有一支球队轮空
比如说N=3(可假想N=4,多一个对手0,与0对阵算作轮空)
第一轮:1-2 3(轮空)
第二轮:1-3 2(轮空)
第三轮:2-3 1(轮空)
我们知道了三支球队的赛程表,六支球队的赛程表该这么转移
6支球队分成两组 先进行组内对抗
1-2 3(轮空) 4-5 6(轮空)
1-3 2(轮空) 4-6 5(轮空)
2-3 1(轮空) 5-6 4(轮空)
但是,我们不允许两支球队轮空,就得把分别在组内对抗按道理应轮空的两支球队对阵
1-2 4-5 3-6
1-3 4-6 2-5
2-3 5-6 1-4
再进行组间对抗
1-4 2-5 3-6 (这里的对阵在组内对抗进行过了,应去掉)
1-5 2-6 3-4
1-6 2-4 3-5
dp解法待补
D 01、完全 混合背包 可做板子
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 20005; int t[N], a[N], b[N]; int n, volume, dp[N]; //memset(dp, t, sizeof(dp)); dp[0] = 0; //恰好装满:t=128,最大:t=0 void ZeroonePack(int wi, int vi) { for(int i=volume; i>=vi; --i) dp[i] = max(dp[i], dp[i-vi]+wi); } void CompletePack(int wi, int vi) { for(int i=vi; i<=volume; ++i) dp[i] = max(dp[i], dp[i-vi]+wi); } void MultiplePack(int wi, int vi, int mi) { if(mi*vi>volume) { CompletePack(wi, vi); return ; } for(int i=1; i<mi; mi-=i, i<<=1) ZeroonePack(wi*i, vi*i); ZeroonePack(wi*mi, vi*mi); } int main() { scanf("%d %d", &n, &volume); rep(i,1,n) scanf("%d %d %d", &t[i], &a[i], &b[i]); mes(dp, 0); rep(i,1,n) { if(t[i]==1) CompletePack(a[i], b[i]); else ZeroonePack(a[i], b[i]); //MultiplePack(a[i], b[i], t[i]==1 ? 1e4+10 : 1); } printf("%d\n", dp[volume]); return 0; }
F 递推dp,水题
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 1005; int n, m, a[N][N], dp[N][N]; bool check(int x, int y) { return (x>0 && y>0); } int main() { scanf("%d %d", &n, &m); rep(i,1,n) rep(j,1,m) scanf("%d", &a[i][j]), dp[i][j]=-INF; dp[1][1]=a[1][1]; int ans=-INF; rep(i,1,n) rep(j,1,m) { if(check(i-1, j) && dp[i-1][j]>0) dp[i][j]=max(dp[i][j], dp[i-1][j]+a[i][j]); if(check(i, j-1) && dp[i][j-1]>0) dp[i][j]=max(dp[i][j], dp[i][j-1]+a[i][j]); if(check(i-1, j-2) && dp[i-1][j-2]>0) dp[i][j]=max(dp[i][j], dp[i-1][j-2]+a[i][j]); if(check(i-2, j-1) && dp[i-2][j-1]>0) dp[i][j]=max(dp[i][j], dp[i-2][j-1]+a[i][j]); ans=max(dp[i][j], ans); } printf("%d\n", ans); return 0; }
G 最长上升子序列,水,要打印路径,只会O(n^2)的。。
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 200005; int n, a[N], dp[N], path[N], ans[N]; int main() { int T; scanf("%d", &T); while(T--) { scanf("%d", &n); rep(i,1,n) scanf("%d", &a[i]); rep(i,1,n) { dp[i]=1, path[i]=0; rep(j,1,i-1) if(a[j]<a[i]) { if(dp[i] <= dp[j]+1) { dp[i]=dp[j]+1; path[i]=j; } } } int now=1, len=1; rep(i,1,n) if(len<dp[i]) len=dp[i], now=i; else if(len==dp[i] && a[now]>a[i]) now=i; printf("%d ", len); int k=0; while(now!=0) ans[++k]=a[now], now=path[now]; per(i,k,1) printf("%d ",ans[i]); puts(""); } return 0; }
L 轮廓线动态规划(插头dp)
题意:给定一个n*m的矩阵,使用1*2的小长方形覆盖矩阵,要求完全覆盖的同时不出现重合,也不允许超出边界,问有多少种可能的覆盖方法,方案数对1e9+7取模 ( 2<=n<=1000, 3<=m<=5 )。
tags: 好难,看了题解。。
方法1 : 传统方法,以整行整列为状态。
x,y表示当前需要考虑放小长方形的位置,st1,st2分别是当前行和下一行的情况,dp[x][y][st1][st2]表示当前要考虑(x,y),当前行状态是st1,下一行状态是st2,可以有多少种合法的放置方法。 然后类似于记忆化dp。
状态转移:
if(y+1<=m && !(st1 & (1<<y)) && !(st1 & 1<<(y+1))) //横放,那么(x,y),(x,y+1)两个位置不能已经被覆盖 { res=(res+DP(x,y+2,(st1|(1<<(y)|(1<<(y+1)))),st2))%MOD; //如果状态转移合法,接下来就考虑(x,y+2),并且更新st1 } if(x+1<=n && !(st1 & (1<<y))) //同上,竖放 { res=(res+DP(x,y+1,(st1|(1<<y)),(st2|(1<<y))))%MOD; }
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 1005, mod = 1e9+7; int n, m; ll dp[N][7][1<<6][1<<6]; inline ll DP(int x, int y, int lin1, int lin2) { if(x==n+1) return 1; if(y==m+1) return DP(x+1, 1, lin2, 0); if(dp[x][y][lin1][lin2]) return dp[x][y][lin1][lin2]; if((lin1>>(y-1))&1) return DP(x, y+1, lin1, lin2); ll res=0; if(y+1<=m && !((lin1>>(y-1))&1) && !((lin1>>(y))&1) ) { res += DP(x, y+2, (lin1|(1<<(y-1))|(1<<(y))) , lin2); res %= mod; } if(x+1<=n && !((lin1>>(y-1))&1) && !((lin2>>(y-1))&1) ) { res += DP(x, y+1, (lin1|(1<<(y-1))), (lin2|(1<<(y-1))) ); res %= mod; } return dp[x][y][lin1][lin2]=res; } int main() { scanf("%d %d", &n, &m); if(n*m&1) puts("0"); else printf("%lld\n", (DP(1, 1, 0, 0)+mod)%mod); return 0; }
方法2 : 插头dp,利用 “窄” 的特点,把参差不齐的 “轮廓线” 作为状态的一部分。
参考大白书 384页
伪代码(模板):
int dp[2][N], cur = 0; 所有d[0][j]初始化为1; 从小到大枚举每个要算的阶段 { cul ^= 1; 所有dp[cul][j]初始化为0; //只能在这里这样做,因为现在dp[cul]存着以前某阶段的值 for 上个阶段的每个结点j for j的每个后继结点k d[cul][k] += d[1-cul][j]; }
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 1005, mod = 1e9+7; int n, m, cur; ll dp[2][N]; void update(int a, int b) { if(b&(1<<m)) dp[cur][b^(1<<m)] += dp[cur^1][a]%mod; } int main() { scanf("%d %d", &n, &m); cur=0; rep(i,0,N-1) dp[cur][i]=1; rep(i,1,n) rep(j,1,m) //枚举当前要算的阶段 { cur^=1; mes(dp[cur], 0); for(int k=0; k<(1<<m); ++k) //枚举上个阶段的状态 { update(k, k<<1); //不放 if(i>1 && !(k&(1<<m-1))) update(k, (k<<1)|(1<<m)|1); //向上放 if(j>1 && !(k&1)) update(k, (k<<1)|3); //向左放 } } printf("%lld\n", (dp[cur][(1<<m)-1]+mod)%mod); return 0; }
M 多重背包,和D题差不多
N 多位费用完全背包
题意:已知 n个参赛队员,对于第 i个队员,每写一行代码,就会留下 ai个bug。最后一题需要写 m行代码,请安排各个队员写的代码行数(显然要非负),使得整个代码的bug数不超过 b个。然后,在ACM玄学之神的保佑下,这份不超过b个bug的代码就能AC了。问你有多少种不同的安排方案可以写出一份AC代码,要求方案数对mod取模。
tags:
1、dp[i][j][k],已经考虑了前i个队员,写了j行代码,存在k个Bug的方案数,通过递推顺序,第一维可以直接省略。
2、if (k>=arr[i]) dp[j][k]=(dp[j][k]+dp[j-1][k-arr[i]])%mod; // 实际含义是dp[i][j][k]=(dp[i-1][j][k]+dp[i][j-1][k-arr[i]]), 这里的递推顺序实际上是一个无穷背包的思想
#include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;++i) #define per(i,b,a) for (int i=b;i>=a;--i) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f #define MP make_pair #define PB push_back #define fi first #define se second typedef long long ll; const int N = 505; int n, m, b, mod, a[N]; ll dp[N][N]; void CompletePack(int ai, int vi) { rep(i,ai,b) rep(j,vi,m) { dp[i][j] += dp[i-ai][j-vi]; dp[i][j] %= mod; } } int main() { scanf("%d %d %d %d", &n, &m, &b, &mod); rep(i,1,n) scanf("%d", &a[i]); dp[0][0]=1; rep(i,1,n) { CompletePack(a[i], 1); } ll ans=0; rep(i,0,b) ans = (ans+dp[i][m])%mod; printf("%lld\n", ans); return 0; }