[置顶] 201309_The First Game[DP专题]
自己每周挂一些题目玩玩,希望自己能够快点成长起来。
这是一组关于动态规划的题目。
第1题: HDOJ-2182 Frog
题意 : 告诉你一个的序列,每一个的值得意思是在这个位置的虫子的数量。然后再告诉你两个数A、B,表示的是Frog每一次能够跳X = [A,B]的距离,然后还有一个跳的次数的限制。让你求最大可以吃掉的数量。
思路 : 典型的DP问题,首先明确一点跳K次获得最优解必定大于或等于K-1次的最优解,然后要注意有些点可能是跳不到的。
dp[i][j] : 跳j次到达i的最优解。
状态转移方程 : dp[i][j] = dp[k][j-1] + aa[i] (A<=i-k<=B) 注意一点如果dp[k][j-1]都是不可能的状态(用-1表示)那么dp[i][j] = -1;
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 105; int dp[maxn][maxn]; int aa[maxn],N,K,A,B; int solve() { memset(dp,-1,sizeof(dp)); int ans; ans = dp[0][0] = aa[0]; for (int i = 1;i <= K;i++) { for (int j = A*i;j < N;j++) { int max_n = -1; for (int t = j-B;t <= j-A;t++) if (t >= 0 && t < N && dp[t][i-1] != -1)max_n = max(max_n,dp[t][i-1] + aa[j]); //dp[j][i] = max(dp[j-1][i],max_n); dp[j][i] = max_n; ans = max(ans,dp[j][i]); } } return ans; } int main() { int T; scanf("%d",&T); for (int cas = 1;cas <= T;cas++) { scanf("%d%d%d%d",&N,&A,&B,&K); for (int i = 0;i < N;i++) scanf("%d",&aa[i]); printf("%d\n",solve()); } return 0; }
第2题 : HDOJ-2059 龟兔赛跑
题意 : 中文题,不多说了。
思路 : 其实只要想到每一个点不充电的最佳状态是从前面各个点充满电一直开过来最优的那个得到其实基本上就OK了,然后该点的充电的最佳最佳状态就是不充电的最佳状态加上充电所需要的时间。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const double DINF = 1000000000; const int maxn = 100005; double min(double a,double b) { return a < b ? a : b; } double dp[maxn][2]; int N,C,T,L,v,v1,v2; int aa[maxn]; int main() { while (scanf("%d",&L) != EOF) { int i,j; scanf("%d%d%d%d%d%d",&N,&C,&T,&v,&v1,&v2); aa[0] = 0;aa[N+1] = L; for (i = 1;i <= N;i++) scanf("%d",&aa[i]); dp[0][1] = 0; for (i = 1;i <= N+1;i++) { dp[i][0] = DINF; for (j = 0;j < i;j++) { int l = aa[i] - aa[j]; double t; if (l <= C) t = 1.0 * l / v1; else t = 1.0 * C / v1 + (l - C) * 1.0 / v2; dp[i][0] = min(dp[i][0],dp[j][1]+t); } dp[i][1] = dp[i][0] + T; } if (dp[N+1][0] > 1.0 * L / v)printf("Good job,rabbit!\n"); else printf("What a pity rabbit!\n"); } return 0; }
第4 题 : HDU - 1500 Chopsticks
题意 : 给你N非递增的数,然后让你选出(K+8)组,求所有组中较小的两个之差的平方之和为最小(有点绕)。
思路 : 首先要明白一点 : 最后所获得的总和,肯定是相邻的两个的平方之和。然后在考虑一点 : 在你加进一个数a的时候你要去选择是否将这个数a和比他大的数组成一组(暂时不用去考虑另外一个较大值),然后就可一些出状态转移方程了 : dp[i][j] = min(dp[i][j-1] , dp[i-1][j-2] + (a - b) * (a - b)) PS : 这边dp[i][j]表示的是将j个分成i组所获得的最大值而a,b是两个相邻数。 因为要保证加进去有有最大的那个存在,所以可以将原先数组大小倒一下,另外注意当你 i * 3 == j的时候就是你所有都用上的时候,这边需要特别处理下。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF = 99999999; const int MAXN = 5005; int N,K; int dp[1005][MAXN],aa[MAXN]; int calc(int x) { return (aa[x-1] - aa[x]) * (aa[x-1] - aa[x]); } int solve() { memset(dp[0],0,sizeof(dp)); for (int i = 1;i <= K;i++) { dp[i][i*3] = dp[i-1][i*3-2] + calc(i*3); for (int j = i*3 + 1;j <= N;j++) dp[i][j] = min(dp[i][j-1],dp[i-1][j-2]+calc(j)); } return dp[K][N]; } int main() { int T; scanf("%d",&T); for (int cas = 1;cas <= T;cas++) { scanf("%d%d",&K,&N); K = K + 8; for (int i = N;i >= 1;i--) scanf("%d",&aa[i]); printf("%d\n",solve()); } return 0; }
第6题 : POJ - 1160 Post Office
题意 : 选择村庄建Post Office然后问最小的距离和。
思路 : 一开始还以为是可以在村庄外建立Post Office的,然后就非常蛋疼于X<=10000这么大的范围了,后来发现Post Office只能建立在村庄里,那就简单多了。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF = 9999999; int dp[40][400],pos[400],CC[400][400]; int N,M; int get(int L,int R,int loc) { int ans = 0; for (int i = L;i <= R;i++) { ans += abs(pos[loc] - pos[i]); } return ans; } void calc(int L,int R) { int ans = 0; for (int i = L;i <= N;i++) { int temp = abs(pos[i] - pos[L]) - abs(pos[R] - pos[i]); if (temp > 0)ans += temp; } CC[L][R] = ans; } void init() { for (int i = 1;i <= N;i++) { for (int j = 1;j <= i;j++) calc(j,i); } memset(dp,-1,sizeof(dp)); for (int i = 1;i <= N;i++)dp[1][i] = get(1,N,i); } int solve() { init(); for (int i = 2;i <= M;i++) for (int j = i;j <= N;j++) { int min_n = INF; for (int k = 1;k <= j;k++)if (dp[i-1][k] != -1) { int temp = dp[i-1][k] - CC[k][j]; min_n = min(temp,min_n); } dp[i][j] = (min_n == INF ? -1 : min_n); } int ans = INF; for (int i = 1;i <= N;i++)if (dp[M][i] != -1) ans = min(ans,dp[M][i]); //debug(); return ans; } int main() { scanf("%d%d",&N,&M); for (int i = 1;i <= N;i++) scanf("%d",&pos[i]); printf("%d\n",solve()); return 0; }
第8题 : POJ - 1651 Multiplication Puzzle
题意 : 给你 N 个数,然后进行N - 2 次如下操作 : 选其中一个值a[k](1 < k < N)然后sum += a[k] * a[k-1] * a[k+1],接着删除掉 a[k],问最后的sum 最小是多少。
思路 : 开一个二维数组 dp[i][j]表示在[i,j]这一段区间里所能够获得的最优值,另外可以明确一点就是,假如最后选的是 第j个数的话,那么最终的结果是dp[1][j] + dp[j][N] + a[j-1] * a[j] + a[j+1], 然后可以用分治的策略去搞这一题。
具体看代码 :
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF = 99999999; const int MAXN = 105; int dp[MAXN][MAXN],aa[MAXN],N; int dfs(int L,int R) { if (R <= L+2) { if (R == L+2)return aa[L] * aa[L+1] * aa[L+2]; else return 0; } if (dp[L][R] != -1)return dp[L][R]; int ans = INF; for (int i = L+1;i <= R-1;i++) { int temp = dfs(L,i) + dfs(i,R) + aa[L] * aa[R] * aa[i]; ans = min(ans,temp); } return dp[L][R] = ans; } int solve() { memset(dp,-1,sizeof(dp)); return dfs(1,N); } int main() { scanf("%d",&N); for (int i = 1;i <= N;i++) scanf("%d",&aa[i]); printf("%d\n",solve()); return 0; }
第9题 : HDOJ-1080 Human Gene Functions
题意 : 告诉你一个碱基配对的法则(A,C,G,T再加一个'_'),然后再给你两个字符串a 、b(不含有 ‘_’)然后在其中插入'_'使得最后配对后所获得的总和是最大的。
思路 : 因为不可能是'_'和 '_'配对的,所以可以开一个DP[i][j]数组,来保存字符串a0~ai 和 b0~bj所获得的最优值,然后么就可以写出DP转移方程了
DP[i][j] = min ((DP[i][k1] + calc(k1 , j)),(DP[k2][j] + calc(i , k2)); PS : calc(l , r)计算的是从l 到之间都与'_'去配对。
最后我是用dfs 记忆话搜索实现这个状态转移的。(不懂的话可以看下代码,,,人搓)
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF = 9999999; const int MAXN = 105; char aa[MAXN],bb[MAXN]; int N,M,a[MAXN],b[MAXN],dp[MAXN][MAXN]; int G[5][5] = { {5,-1,-2,-1,-3}, {-1,5,-3,-2,-4}, {-2,-3,5,-2,-2}, {-1,-2,-2,5,-1}, {-3,-4,-2,-1,-INF} }; int initcalc(char ch) { if (ch == 'A')return 0; if (ch == 'C')return 1; if (ch == 'G')return 2; if (ch == 'T')return 3; } void debug() { for (int i = 0;i < N;i++)printf("%d ",a[i]);printf("\n"); for (int i = 0;i < M;i++)printf("%d ",b[i]);printf("\n"); } void init() { for (int i = 0;i < N;i++)a[i] = initcalc(aa[i]);a[N] = 4; for (int i = 0;i < M;i++)b[i] = initcalc(bb[i]);b[M] = 4; for (int i = 0;i <= N;i++) for (int j = 0;j <= M;j++) dp[i][j] = -INF; } int calc(int t1,int t2) { return G[a[t1]][b[t2]]; } int sscalc(bool s,int l,int r) { int he = 0; for (int i = l;i <= r;i++) { if (s)he += calc(i,M); else he += calc(N,i); } return he; } int dfs(int n,int m) { if (n == 0 || m == 0) { if (n == 0 && m == 0)return calc(0,0); if (n == 0) { int he = calc(0,m); for (int i = 0;i < m;i++)he += calc(N,i); return he; } else { int he = calc(n,0); for (int i = 0;i < n;i++)he += calc(i,M); return he; } } if (dp[n][m] != -INF)return dp[n][m]; int ans = -INF; for (int i = 0;i < n;i++) { int temp = dfs(i,m-1) + sscalc(1,i+1,n-1); ans = max(ans,temp); } for (int i = 0;i < m;i++) { int temp = dfs(n-1,i) + sscalc(0,i+1,m-1); ans = max(ans,temp); } return dp[n][m] = ans + calc(n,m); } int solve() { init(); //debug(); return dfs(N-1,M-1); } int main() { int T; scanf("%d",&T); for (int cas = 1;cas <= T;cas++) { scanf("%d%s%d%s",&N,aa,&M,bb); printf("%d\n",solve()); } return 0; }
第11题 : POJ-2192 Zipper
题意 : 给你3段字符串 : A,B,C.然后为C是否可以由A和B刚好组成 例如 A :catB : tree C : tcraete 这样就是可以的。
思路 : 刚拿到,,果断果断dfs去暴搜了,然后么,似乎剪枝不够犀利,TLE了把,后来看到似乎可以加一个剪纸 : 判断A和B最后是否有一个是和C最后一个相等 这一个剪枝,然后就水过了。囧。。
PS : 算了,用DP做还没去尝试,先留着。。下次补上DP的思路和代码。
第12题 : HDOJ-1158 Employment Planning
题意 : 雇佣一个员工需要雇佣费,然后每个月还要薪水,最后解雇他还需要解雇费,然后给你几个月所需要的员工数量问你最优雇佣计划是什么。
PS : 这道题目以前做过,那时候是铭铭挂的一个专题上做到的,然后那时候后不会,后来是看别人的思路,最后才尝试得A掉的。囧,,,
这次就好多了,看到之后直接上手敲了。
思路 : 因为最多是只有12个月的,然后么水水的DP过去了。。。可以DP当前月的每一种员工数量。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef __int64 LL; const int MAXN = 505; const int INF = 1 << 26; int min(int a,int b) { return a > b ? b : a; } int N,h,f,s; int dp[15][MAXN],aa[MAXN]; int Min,Max; int solve() { int i , j, k; memset(dp,-1,sizeof(dp)); for (j = aa[1];j <= Max;j++) dp[1][j] = j * (h + s); for (i = 2;i <= N;i++) for (j = aa[i];j <= Max;j++) { int temp = dp[i-1][j] == -1 ? INF : dp[i-1][j] + j * s; for (k = Min;k < j;k++)if (dp[i-1][k] != -1) { int t = (j - k) * h + j * s + dp[i-1][k]; temp = min(temp,t); } for (k = j + 1;k <= Max;k++)if (dp[i-1][k] != -1) { int t = (k - j) * f + j * s + dp[i-1][k]; temp = min(temp,t); } dp[i][j] = temp; } int ans = INF; for (i = aa[N];i <= Max;i++)if (dp[N][i] != -1) ans = min(ans,dp[N][i]); return ans; } void debug() { for (int i = 1;i <= N;i++) { for (int j = Min;j <= Max;j++) printf("%d ",dp[i][j]); printf("\n"); } } int main() { int i; while (scanf("%d",&N) && N) { scanf("%d%d%d",&h,&s,&f); Min = INF;Max = 0; for (i = 1;i <= N;i++) { scanf("%d",&aa[i]); if (Min > aa[i])Min = aa[i]; if (Max < aa[i])Max = aa[i]; } printf("%d\n",solve()); //debug(); } return 0; }
第13题 : HDOJ-1466 计算直线的交点数
题意 : 略。
思路 : 补 : 用m条直线构成交点的情况下 : 可以用r条有相交直线和m-r条平行线去构成所得解,而r条有相交直线和m-r条平行线所能够获得的交点数是P = r * (m -r) +(m-r条有相交直线自己所相交的点数),这样的话题目就简单了的、
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 210; bool dp[21][MAXN]; int n; void init() { int i , j, k; memset(dp,0,sizeof(dp)); for (i = 1;i <= 20;i++)dp[i][0] = 1; for (k = 2;k <= 20;k++) { for (i = 1;i < k;i++) { for (j = 0;j <= 200;j++)if (dp[i][j]) dp[k][i*(k-i)+j] = 1; } } } int main() { init(); while (scanf("%d",&n) != EOF) { int Max = n * (n - 1) / 2; for (int i = 0;i < Max;i++)if (dp[n][i]) printf("%d ",i); printf("%d\n",Max); } return 0; }
第14题 : POJ-1285 Combinations, Once Again
题意 : 组合数有关的一道题目,然后问你有多少种组合方式,每一种组合里面的元素可重复。
思路 : 水题 ,DP和搜索都可以做,DP的方法是先处理出有哪些不同的数字,然后处理处他们各自的数量,接着用一个DP[i][j] 保存第j个位置是第i种数字的的个数。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef __int64 LL; const int MAXN = 55; LL dp[MAXN][MAXN]; int aa[MAXN],ans[MAXN],tt[MAXN],n,m,N; void solve() { N = 0; for (int i = 1;i <= n;i++) if (tt[i])ans[++N] = tt[i]; memset(dp,0,sizeof(dp)); dp[0][0] = 1; for (int k = 0;k <= 50;k++) { for (int i = 1;i <= N;i++) for (int j = 0;j <= ans[i];j++) if (k >= j)dp[k][i] += dp[k-j][i-1]; } } int main() { int cas = 1; while (scanf("%d%d",&n,&m) && (n || m)) { memset(tt,0,sizeof(tt)); for (int i = 1;i <= n;i++) { int a; scanf("%d",&a); tt[a]++; } solve(); printf("Case %d:\n",cas++); for (int i = 1;i <= m;i++) { int r; scanf("%d",&r); printf("%I64d\n",dp[r][N]); } } return 0; }
第15题 : POJ-2904 The Mailboxes Manufacturers Problem
题意 : 蛮好玩的一道题目,问你的是用一些鞭炮测试一个maibox可以承受的最大爆炸量。。然后company给你k个mailbox,接着告诉你mailbox的最大承受是在!~m之间,然后问你最小需要多少个鞭炮,你能够自信的测出它的最大承受。
思路 : 开一个数组DP[i][j][k]表示[i,j]这个测试范围内然后有k个mialbox最优值,然后状态转移方程是 : DP[i][j][k] = max(DP[i][t-1][k-1],DP[t+1][j][k]) + t;
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef __int64 LL; const int MAXN = 105; const int INF = 99999999; int K,M; int dp[MAXN][MAXN][15]; int dfs(int L,int R,int cnt) { if (L > R)return 0; if (L == R)return L; if (cnt == 1)return (L + R) * (R - L + 1) / 2; if (dp[L][R][cnt] != -1)return dp[L][R][cnt]; int ans = INF; for (int i = L;i <= R;i++) { int temp = max(dfs(L,i-1,cnt-1),dfs(i+1,R,cnt)) + i; ans = min(ans,temp); } return dp[L][R][cnt] = ans; } int main() { int T; scanf("%d",&T); for (int cas = 1;cas <= T;cas++) { scanf("%d%d",&K,&M); memset(dp,-1,sizeof(dp)); printf("%d\n",dfs(1,M,K)); } return 0; }
题意 : 有N头牛,然后每头牛都是有一系列喜欢的坐标,每头牛选择一个点,然后用一根绳子从1 到 N围起来,最后让你求绳子最小的长度。
PS : 这道题目一开始是各种跪啊,期间还发现了我们学校的wenwen大神的题解,orz..我以开始的思路是枚举每一头为起点,,,后来发现,这真是一种相不开的思路的啊。。。囧。
思路 : 枚举第一头牛的其中一个点为起始点,在DP的过程中,最后第N牛状态转移到起始点上去,不然会出错。复杂度为 : O(N*S*S*S)
。
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; const double DINF = 100000000; const int maxn = 105; struct node { int len; int X[55],Y[55]; }q[maxn]; int n; double dp[maxn][55]; double Dis(int x,int i,int y,int j) { double a = abs(q[x].X[i] - q[y].X[j]); double b = abs(q[x].Y[i] - q[y].Y[j]); return sqrt(a * a + b * b); } int solve() { double ans = DINF; for (int t = 1;t <= q[1].len;t++) { memset(dp,0,sizeof(dp)); for (int i = 1;i <= q[2].len;i++)dp[2][i] = Dis(1,t,2,i); for (int i = 3;i <= n;i++) { int N1 = q[i].len; int N2 = q[i-1].len; for (int j = 1;j <= N1;j++) { double max_n = DINF; for (int k = 1;k <= N2;k++)max_n = min(max_n,dp[i-1][k]+Dis(i,j,i-1,k)); dp[i][j] = max_n; } } double sum = DINF; for (int i = 1;i <= q[n].len;i++)sum = min(sum,dp[n][i]+Dis(n,i,1,t)); ans = min(ans,sum); } ans *= 100; return (int)ans; } int main() { while (scanf("%d",&n) != EOF) { for (int i = 1;i <= n;i++) { scanf("%d",&q[i].len); for (int j = 1;j <= q[i].len;j++) scanf("%d%d",&q[i].X[j],&q[i].Y[j]); } printf("%d\n",solve()); } return 0; }
第16题 : POJ - 3132 Sum of Different Primes
题意 : 给你一个数N然后再给你一个数K,K个互不相同的素数加起来和为N,问你这样的组数有多少?
思路 : 我看到这题第一反应时数为DP,很快想到了一个转移方程 :[i][j][sum] += dp[i-1][k][sum-prime[j]] (首先认为这个组数都是从小到排的,然后dp[i][j][sum] :表示第的是在第i 个位置上放prime[j]从而得到总和sum的数量,prime[j]保存的是 从2开始第j个素数的值,prime[1] = 2),然后么,就是使用 dfs记忆化搜索了,不过在使用这个方法的时候,我在边界处理上出现了一些错误,然后调了半天才调好,囧。
3132 | Accepted | 38000K | 375MS | G++ | 1507B | 2013-09-07 23:11:56 |
后来默默的去看了下别人的做法 。 0-1背包!!! orz...真是背包白学了,,接着默默的敲了一个二维的0-1背包。BS下自己。。
3132 | Accepted | 464K | 16MS | G++ | 763B | 2013-09-07 23:34:09 |
贴一下用0-1背包的代码
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 1300; int dp[MAXN][15],prime[200]; int N,M,K; bool Isprime(int x) { for (int i = 2;i * i <= x;i++)if (x % i == 0) return 0; return 1; } void init() { M = 0; for (int i = 2;i <= 1120;i++)if (Isprime(i)) prime[++M] = i; memset(dp,0,sizeof(dp)); } void solve() { init(); dp[0][0] = 1; for (int i = 1;i <= M;i++) { int c = prime[i]; for (int k = 14;k >= 1;k--) for (int j = 1120;j >= c;j--) dp[j][k] += dp[j-c][k-1]; } } int main() { solve(); while (scanf("%d%d",&N,&K) && (N || K)) { printf("%d\n",dp[N][K]); } return 0; }
第2题 : 好像思路和第15题差不多,但是我开了一个1000*1000*50的数组果断MLE啊,,囧,,还在纠结如何优化中。。。
第17 和19题是状态压缩DP,表示周伟的论文还没看完,然后也米去敲(我弱啊。。囧)下次补齐!!
第3题 : 很偶然得看到似乎有大数这种神奇的东西会出现,对于还不会敲java并且有点懒的去自己写一个大数运算的我来说,就直接忽视了下这题。
第10题 : 水题。
第18题 : 水题。
第21题 : 水题。
第7题 :前一个礼拜做过。
总结 :
这次这个专题是在暑假集训结束后自己挂的,本来希望自己在一个礼拜之内搞定的,结果因为周六周日两天有比赛耽搁了下,后来又挂了两天,但结果还是没敲完(我好水啊!!!),当然可能因为要上课的缘故,影响 了刷题,不过激情不够啊!!!
恩,做了这么一个礼拜感觉DP的时候如果是递推的话,感觉自己现在代码有点伤经常跪,比较喜欢用dfs记忆话搜索来写,然后做题的时候一卡题就会时常去看dis 感觉这是一种不好的习惯 (> <),需要改正啊!!!!。
下个礼拜再挂一个专题 : 关于树形DP、状态压缩的DP和重温背包问题。。
加油!!!!!怒起啊!!!!!!
PS : 这篇文章还没完全编辑,上面有些思路比较简单,下次补充(一下子一起敲有点累啊,,,写文章累啊 ,,,还是AC 爽啊)。。。