动态规划总专题
LCS。
View Code
#include <stdio.h> #include <string.h> #define N 1001 char a[N], b[N]; int dp[N][N]; int max(int x, int y){return x > y ? x : y;} int solve() { int n = strlen(a), m = strlen(b); int i, j; for(i = 0; i <= n; i ++) dp[i][0] = 0; for(j = 0; j <= m; j ++) dp[0][j] = 0; for(i = 1; i <= n; i ++) for(j = 1; j <= m; j ++) if(a[i - 1] == b[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); return dp[n][m]; } int main() { while(scanf("%s%s", a, b) != EOF) printf("%d\n", solve()); return 0; }
LCS,只不过字母比较换成了字符串比较,输出要花点时间,算法导论上有很详细的讲解。
View Code
#include<iostream> #include<string> #include<vector> using namespace std; vector<string> a,b; int c[101][101]; short d[101][101]; int lena,lenb; void run() { lena = a.size(); lenb = b.size(); for(int i=0;i<101;i++) c[i][0] = c[0][i] = 0; for(int i=1;i<=lena;i++) { for(int j=1;j<=lenb;j++) { if(a[i-1]==b[j-1]) { c[i][j]=c[i-1][j-1]+1; d[i-1][j-1]=0; } else if(c[i-1][j]>c[i][j-1]) { c[i][j]=c[i-1][j]; d[i-1][j-1]=1; } else { c[i][j]=c[i][j-1]; d[i-1][j-1]= -1; } } } } void print_lcs(int x,int y) { if(x<0||y<0)return; if(d[x][y]==0) { print_lcs(x-1,y-1); if(x==0||y==0) cout<<a[x]; else cout<<" "<<a[x]; } else if(d[x][y]==1) print_lcs(x-1,y); else print_lcs(x,y-1); } int main() { string tmp; while(cin >> tmp) { a.clear(); b.clear(); do { a.push_back(tmp); }while(cin >> tmp&&tmp!="#"); while(cin>>tmp&&tmp!="#") { b.push_back(tmp); } run(); print_lcs(lena-1,lenb-1); cout<<endl; } return 0; }
将字符串反转后和原字符串做LCS就差不多了。
View Code
#include<iostream> #include<string> #include<algorithm> using namespace std; const int maxn = 5001; short c[maxn][maxn]; int n; string a,b; void lcs() { for(int i=0;i<n;i++) c[i][0]=c[0][i]=0; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(a[i-1]==b[j-1]) c[i][j]=c[i-1][j-1]+1; else if(c[i-1][j]>c[i][j-1]) c[i][j] = c[i-1][j]; else c[i][j] = c[i][j-1]; } } } int main() { while(cin >> n) { cin >> a; b = a; reverse(b.begin(),b.end()); lcs(); cout<<n-c[n][n]<<endl; } return 0; }
最长下降子序列。
转移方程:dp[i] = max(dp[i], dp[j] + 1)(j = 0...i-1 && v[i] <= v[j]),时间复杂度O(n^2)。
View Code
#include <cstdio> #include <cstring> int n, a[100001], dp[100001]; inline int max(int x, int y) { return x > y ? x : y; } int solve() { int i, j, m = 1; dp[0] = 1; for(i = 1; i < n; i ++) { dp[i] = 1; for(j = 0; j < i; j ++) { if(a[i] <= a[j]) dp[i] = max(dp[i], dp[j] + 1); } if(dp[i] > m) m = dp[i]; } return m; } int main() { int v, cas = 1; while(scanf("%d", &v), v != -1) { n = 0; do { a[n ++] = v; }while(scanf("%d", &v), v != -1); if(cas != 1) printf("\n"); printf("Test #%d:\n", cas ++); printf(" maximum possible interceptions: %d\n", solve()); } return 0; }
用“贪心 + 二分”可以达到nlogn,维护dp为递减序列,不断更新dp序列的某个位置为在该位置可能出现的最大值,如果要插入的v值小于该递减序列最小值,则将v插入到队尾。
View Code
#include <cstdio> #define N 50001 int n, m, dp[N]; int bs(int v) { int l = 0, r = m; while(l < r) { int mid = (l + r) >> 1; if(dp[mid] >= v) l = mid + 1; else r = mid; } return l; } int main() { int v, cas = 1; while(scanf("%d", &v), v != -1) { m = 0; do { if(m == 0) dp[m ++] = v; else { int k = bs(v); if(k == m) dp[m ++] = v; else dp[k] = v; } }while(scanf("%d", &v), v != -1); if(cas != 1) putchar('\n'); printf("Test #%d:\n", cas ++); printf(" maximum possible interceptions: %d\n", m); } return 0; }
看出是LIS差不多已经赢了,而此题基数大必须nlogn,把上面POJ1887题的nlogn代码复制过来稍微改改就行了。
View Code
#include <cstdio> #define N 40001 int m, q[N]; int bs(int v) { int l = 0, r = m; while(l < r) { int mid = (l + r) >> 1; if(q[mid] < v) l = mid + 1; else r = mid; } return r; } int main() { int n, p, v; scanf("%d", &n); while(n --) { scanf("%d", &p); m = 0; while(p --) { scanf("%d", &v); if(m == 0) q[m ++] = v; else { int k = bs(v); if(k == m) q[m ++] = v; else q[k] = v; } } printf("%d\n", m); } return 0; }
没什么好讲的,01背包。
View Code
#include <cstdio> #include <cstring> #define MAXN 13000 int W[MAXN], D[MAXN], dp[MAXN]; int N, M; int max(int a, int b) { return a > b ? a : b; } int CharmBracelet() { memset(dp, 0, sizeof(dp)); for(int i = 0; i < N; i ++) { for(int j = M; j >= W[i]; j --) { dp[j] = max(dp[j], dp[j - W[i]] + D[i]); } } return dp[M]; } int main() { while(scanf("%d%d", &N, &M) != EOF) { for(int i = 0; i < N; i ++) { scanf("%d%d", &W[i], &D[i]); } printf("%d\n", CharmBracelet()); } return 0; }
看str1的前i个字母和str2的前j个字母能否组成str的前i+j个字母。
View Code
#include <stdio.h> #include <string.h> #define maxn 205 char a[maxn], b[maxn], c[maxn << 1]; bool dp[maxn][maxn]; bool solve() { int i, j, n = strlen(a), m = strlen(b); for(i = 0; i < n; i ++) if(a[i] == c[i]) dp[i + 1][0] = true; for(i = 0; i < m; i ++) if(b[i] == c[i]) dp[0][i + 1] = true; dp[0][0] = true; for(i = 1; i <= n; i ++) { for(j = 1; j <= m; j ++) { if((dp[i - 1][j] && a[i - 1] == c[i + j - 1]) || (dp[i][j - 1] && b[j - 1] == c[i + j - 1])) dp[i][j] = true; else dp[i][j] = false; } } return dp[n][m]; } int main() { int n, i; scanf("%d", &n); for(i = 1; i <= n; i ++) { scanf("%s%s%s", a, b, c); if(solve()) printf("Data set %d: yes\n", i); else printf("Data set %d: no\n", i); } return 0; }
这题由于基数M很大,无法用传统意义上的背包,但总体思想还是背包,实现可以用dfs+剪枝。
View Code
#include <cstdio> #include <cstring> #include <algorithm> #define N 10000001 int a[31], ans; int n, m; int max(int x, int y){return x > y ? x : y;} bool cmp(int x, int y){return x > y;} void dfs(int d, int s) { if(ans == m) return; if(s > m) return ; if(d >= n) { ans = max(ans, s); return ; } int sum = s; for(int i = d; i < n; i ++) // 剪枝 sum += a[i]; if(sum < ans) return ; dfs(d + 1, s + a[d]); dfs(d + 1, s); } int main() { int i; while(scanf("%d%d", &n, &m) != EOF) { int sum = 0; for(i = 0; i < n; i ++) { scanf("%d", &a[i]); sum += a[i]; } if(sum <= m) { printf("%d\n", sum); continue; } ans = 0; std::sort(a, a + n, cmp); dfs(0, 0); printf("%d\n", ans); } return 0; }
多重背包。
View Code
#include <stdio.h> #include <string.h> #define N 60010 int f[N], V; inline int max(int x, int y){return x > y ? x : y;} void ZeroOnePack(int c, int w) { int i; for(i = V; i >= c; i --) f[i] = max(f[i], f[i - c] + w); } void CompletePack(int c, int w) { int i; for(i = c; i <= V; i ++) f[i] = max(f[i], f[i - c] + w); } void MultiplePack(int c, int w, int n) { if(c * n >= V) { CompletePack(c, w); return ; } int k = 1; while(k <= n) { ZeroOnePack(k * c, k * w); n -= k; k *= 23; } ZeroOnePack(n * c, n * w); } int main() { int i, sum, n[6], cas = 1; while(1) { sum = 0; for(i = 0; i < 6; i ++) { scanf("%d", &n[i]); sum += (i + 1) * n[i]; } if(sum == 0) break; printf("Collection #%d:\n", cas ++); if(sum & 1) { printf("Can't be divided.\n\n"); continue; } V = sum / 2; memset(f, 0, sizeof(f)); for(i = 0; i < 6; i ++) if(n[i]) MultiplePack(i + 1, i + 1, n[i]); if(f[V] == V) printf("Can be divided.\n\n"); else printf("Can't be divided.\n\n"); } return 0; }
HDU 2955 Robberies
一开始傻逼了,居然直接把概率进行相加,用转移方程f[i] = max(f[i], f[i-v[j].money] + v[j].p],这是错的。
正确解法是:设f[i]为得到总钱数为 i 的最大逃脱概率,那么有转移方程 f[i] = max(f[i], f[i-v[j].money] * v[j].p),逃脱概率v[j].p = 1.- 被抓捕概率。那么最后结果就是大于题目给定最大逃脱概率前提下的最多钱数。
1 #include <stdio.h> 2 #include <string.h> 3 #include <algorithm> 4 #define N 10001 5 6 double f[N]; 7 8 int main() 9 { 10 int T, n, mm; 11 double p, pp; 12 scanf("%d", &T); 13 while(T--) 14 { 15 scanf("%lf%d", &p, &n); 16 p = 1 - p; 17 for(int i = 0; i < N; i++) 18 f[i] = 0.0; 19 f[0] = 1.0; 20 for(int i = 0; i < n; i++) 21 { 22 scanf("%d%lf", &mm, &pp); 23 pp = 1.0 - pp; 24 for(int j = N-1; j >= mm; j--) 25 f[j] = std::max(f[j], f[j - mm] * pp); 26 } 27 int i; 28 for(i = N-1; i >= 0; i--) 29 if(f[i] >= p) 30 break; 31 printf("%d\n", i); 32 } 33 return 0; 34 }
努力更新中……