dp专练
dp练习。
codevs 1048 石子归并
区间dp
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<cmath> 5 #include<iostream> 6 7 using namespace std; 8 9 int f[110][110],w[110],sum[110]; 10 11 inline int read() { 12 int x = 0,f = 1;char ch = getchar(); 13 for (; ch<'0'||ch>'9'; ch = getchar()) if (ch=='-') f = -1; 14 for (; ch>='0'&&ch<='9'; ch = getchar()) x = x * 10 + ch - '0'; 15 return x * f; 16 } 17 18 int main() { 19 memset(f,0x3f,sizeof(f)); 20 int n = read(); 21 for (int i=1; i<=n; ++i) w[i] = read(); 22 for (int i=1; i<=n; ++i) sum[i] = sum[i-1] + w[i]; 23 for (int i=1; i<=n; ++i) f[i][i] = 0; 24 for (int i=n; i>=1; --i) // 枚举顺序:确保下次计算的状态中需要用到的状态已经计算出了。 25 for (int j=i+1; j<=n; ++j) 26 for (int k=i; k<j; ++k) 27 f[i][j] = min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]); 28 printf("%d\n",f[1][n]); 29 return 0; 30 }
codevs 1090 加分二叉树
区间dp
1 #include<cstdio> 2 3 int f[110][110],w[110],sum[110]; 4 int rt[110][110]; 5 6 inline int read() { 7 int x = 0,f = 1;char ch = getchar(); 8 for (; ch<'0'||ch>'9'; ch = getchar()) if (ch=='-') f = -1; 9 for (; ch>='0'&&ch<='9'; ch = getchar()) x = x * 10 + ch - '0'; 10 return x * f; 11 } 12 void dfs(int l,int r) { 13 if (l > r) return ; 14 printf("%d ",rt[l][r]); 15 dfs(l,rt[l][r]-1); 16 dfs(rt[l][r]+1,r); 17 } 18 int main() { 19 int n = read(); 20 for (int i=1; i<=n; ++i) w[i] = read(); 21 for (int i=1; i<=n; ++i) 22 f[i][i] = w[i],rt[i][i] = i; 23 for (int i=n; i>=1; --i) 24 for (int j=i+1; j<=n; ++j) // 中序遍历i~j的子树 25 for (int t,k=i; k<=j; ++k) { // 枚举根为k 26 if (k==i) t = f[k+1][j] + w[k]; 27 else if (k==j) t = f[i][k-1] + w[k]; 28 else t = f[i][k-1]*f[k+1][j]+w[k]; 29 if (t > f[i][j]) f[i][j] = t,rt[i][j] = k; 30 } 31 printf("%d",f[1][n]); 32 puts(""); 33 dfs(1,n); 34 return 0; 35 }
openjudge 1768 最大子矩阵
n^3求最大子矩阵
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 5 using namespace std; 6 7 int a[110][110],s[110][110],f[110]; 8 9 int main () { 10 int n; 11 scanf("%d",&n); 12 for (int i=1; i<=n; ++i) 13 for (int j=1; j<=n; ++j) 14 scanf("%d",&a[i][j]); 15 for (int j=1; j<=n; ++j) // lie 16 for (int i=1; i<=n; ++i) // hang 17 s[j][i] = s[j][i-1] + a[i][j]; 18 int ans = 0; 19 for (int i=1; i<=n; ++i) { // hang1 20 for (int j=i; j<=n; ++j) { // hang2 21 memset(f,0,sizeof(f)); 22 for (int k=1; k<=n; ++k) { // lie 23 f[k] = max(0,f[k-1])+s[k][j]-s[k][i-1]; 24 ans = max(ans,f[k]); 25 } 26 } 27 } 28 printf("%d",ans); 29 return 0; 30 }
openjudge 8462 大盗阿福
1 //f[i]表示到第i家店,的最大价值 2 //f[i] = max(f[i=1...i-2]) + a[i] 3 //第i次转移与第i+1次转移,只多了f[i-1],所以记一个变量取出最大值即可 4 //网上的另一种转移方程,f数组的意思不变 5 //f[i] = max(f[i-1], f[i-2]+a[i]); 6 #include<cstdio> 7 #include<cstring> 8 #include<cmath> 9 #include<algorithm> 10 #include<iostream> 11 12 using namespace std; 13 14 int a[100100],f[100100]; 15 16 int main () { 17 int T,n; 18 scanf("%d",&T); 19 while (T--) { 20 scanf("%d",&n); 21 for (int i=1; i<=n; ++i) scanf("%d",&a[i]); 22 if (n==1) {printf("%d\n",a[1]);continue;} 23 if (n==2) {printf("%d\n",max(a[1],a[2]));continue;} 24 25 memset(f,0,sizeof(f)); 26 int mx = a[1]; 27 f[1] = a[1]; 28 f[2] = max(a[1],a[2]); 29 for (int i=3; i<=n; ++i) { 30 f[i] = mx + a[i]; 31 mx = max(f[i-1],mx); 32 } 33 int ans = -1; 34 for (int i=1; i<=n; ++i) 35 ans = max(ans,f[i]); 36 printf("%d\n",ans); 37 } 38 return 0; 39 }
openjudge 8464 股票买卖
1 //读懂题目,只有两次买卖 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<iostream> 7 8 using namespace std; 9 10 int a[100100],f1[100100],f2[100100]; 11 12 int main () { 13 int T,n; 14 scanf("%d",&T); 15 while (T--) { 16 scanf("%d",&n); 17 for (int i=1; i<=n; ++i) scanf("%d",&a[i]); 18 memset(f1,0,sizeof(f1)); 19 memset(f2,0,sizeof(f2)); 20 int mn = a[1];f1[1] = 0; 21 for (int i=2; i<=n; ++i) { 22 f1[i] = a[i] - mn; 23 f1[i] = max(f1[i],f1[i-1]); 24 mn = min(a[i],mn); 25 } 26 int mx = a[n];f2[n] = 0; 27 for (int i=n-1; i>=1; --i) { 28 f2[i] = mx - a[i]; 29 f2[i] = max(f2[i],f2[i+1]); 30 mx = max(a[i],mx); 31 } 32 int ans = -1; 33 for (int i=2; i<n; ++i) { 34 ans = max(ans,f1[i-1]+f2[i]); 35 } 36 printf("%d\n",ans); 37 } 38 return 0; 39 }
openjudge 9268 酒鬼
1 #include<cstdio> 2 #include<cstring> 3 #include<cmath> 4 #include<algorithm> 5 #include<iostream> 6 7 using namespace std; 8 9 int a[1010],f[1010]; 10 11 int main () { 12 int n; 13 scanf("%d",&n); 14 for (int i=1; i<=n; ++i) 15 scanf("%d",&a[i]); 16 f[1] = a[1]; 17 f[2] = a[1] + a[2]; 18 //当前酒可以不喝,可以喝一瓶,连续两瓶 19 for (int i=3; i<=n; ++i) 20 f[i] = max(f[i-1],max(f[i-2]+a[i],f[i-3]+a[i-1]+a[i])); 21 printf("%d",f[n]); 22 return 0; 23 }
openjudge 7614 最低通行费
1 //时间是2n-1,所以只能往右或往下走 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<iostream> 7 8 using namespace std; 9 10 int a[110][110],f[110][110]; 11 12 int main () { 13 int n; 14 scanf("%d",&n); 15 for (int i=1; i<=n; ++i) 16 for (int j=1; j<=n; ++j) 17 scanf("%d",&a[i][j]); 18 memset(f,0x3f,sizeof(f)); 19 f[1][0] = f[0][1] = 0; 20 for (int i=1; i<=n; ++i) { 21 for (int j=1; j<=n; ++j) { 22 int shang = f[i-1][j],zuo = f[i][j-1],aa = a[i][j]; 23 f[i][j] = min(f[i-1][j],f[i][j-1]) + a[i][j]; 24 int ff = f[i][j]; 25 ff = f[i][j]; 26 } 27 } 28 printf("%d",f[n][n]); 29 return 0; 30 }
bzoj 2748 音量调节
1 /* 2 每个点可以增减一个数,求到最后一个数时的最大价值。 3 那么有2^n个选择,2^n显然是超时的。 4 但是n<=50, 最大音量也不超过1000,所以可以转化为判定性dp来做。 5 到每个点f[i][j]=0/1表示到第i个点j的音量能否达到。 6 O(n*MaxLevel) dp 7 8 以空间换时间 9 */ 10 #include<cstdio> 11 #include<cstring> 12 #include<algorithm> 13 #include<iostream> 14 15 using namespace std; 16 17 bool f[1010][1010]; 18 int a[1010]; 19 20 inline int read() { 21 int x = 0,f = 1;char ch = getchar(); 22 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 23 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 24 return x * f; 25 } 26 27 int main() { 28 int n = read(),s = read(),t = read(); 29 for (int i=1; i<=n; ++i) a[i] = read(); 30 memset(f,false,sizeof(f)); 31 f[0][s] = true; 32 for (int i=1; i<=n; ++i) { 33 for (int j=0; j<=t; ++j) { 34 if (j-a[i] >= 0) f[i][j] |= f[i-1][j-a[i]]; 35 if (j+a[i] <= t) f[i][j] |= f[i-1][j+a[i]]; 36 } 37 } 38 int ans=-1; 39 for (int i=1; i<=t; ++i) 40 if (f[n][i]) ans = i; 41 cout << ans; 42 return 0; 43 }
luogu 1052 过河
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<iostream> 5 6 using namespace std; 7 8 int a[110],d[110],f[300100],v[300100]; 9 10 inline int read() { 11 int x = 0,f = 1;char ch = getchar(); 12 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 13 for (; isdigit(ch); ch=getchar()) x = x*10+ch-'0'; 14 return x * f; 15 } 16 17 int main() { 18 int n = read(); 19 int s = read(),t = read(),m = read(); 20 for (int i=1; i<=m; ++i) a[i] = read(); 21 sort(a+1,a+m+1); 22 for (int i=1; i<=m; ++i) 23 d[i] = (a[i]-a[i-1])%2520; 24 for (int i=1; i<=m; ++i) { 25 a[i] = a[i-1] + d[i]; 26 v[a[i]] = 1; 27 } 28 int L = a[m]; 29 memset(f,0x3f,sizeof(f)); 30 f[0] = 0; 31 for (int i=1; i<=L+t; ++i) { 32 for (int j=s; j<=t; ++j) { 33 if (i-j>=0) f[i] = min(f[i],f[i-j]); 34 f[i] += v[i]; 35 } 36 } 37 int ans = 1e9; 38 for (int i=L; i<=L+t; ++i) ans = min(ans,f[i]); 39 cout << ans; 40 return 0; 41 }
bzoj 4300 绝世好题
1 /* 2 题意:在序列a中求以子序列(不要求连续),满足相邻两位and后不为0,b_i & b_i-1 != 0 3 f[i]表示到第i位时的最长长度。f[i] = max(f[j]+1,f[i]),a_i & a_j !=0 4 复杂度O(n*n),超时。 5 6 换状态表示。 7 到第i位的最长长度换一种求法。 8 将i化成二进制思考,考虑可以转移到i的j,j必须满足它的二进制有一位和i的二进制同为1 9 那么可以dp[x],表示i以前第x位上为1的所有数中,最长的长度。 10 每次用dp[]数组来计算到i的最长长度,然后以i来更新。 11 复杂度O(30*n) 12 13 */ 14 #include<cstdio> 15 #include<algorithm> 16 #include<cstring> 17 #include<iostream> 18 19 using namespace std; 20 21 int b[100100],f[50]; 22 23 inline int read() { 24 int x = 0,f = 1;char ch = getchar(); 25 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 26 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 27 return x * f; 28 } 29 30 int main() { 31 int n = read(); 32 for (int i=1; i<=n; ++i) b[i] = read(); 33 for (int i=1; i<=n; ++i) { 34 int mx = 0; 35 for (int j=0; j<=30; ++j) 36 if (b[i] & (1<<j)) mx = max(mx,f[j]); 37 for (int j=0; j<=30; ++j) 38 if (b[i] & (1<<j)) f[j] = max(f[j],mx+1); 39 } 40 int ans = 0; 41 for (int i=0; i<=30; ++i) ans = max(ans,f[i]); 42 cout << ans; 43 return 0; 44 }
bzoj 1261 zh_tree
1 /* 2 因为是中序遍历,中序遍历的性质:任何点都可以做根节点。 3 所以枚举根节点即可。 4 处理l-r区间,枚举根节点是谁。 5 6 f[l][r][dep]表示l,r区间,根节点的深度是dep,构造成树的最小代价 7 那么f[l][r][dep] = min(f[l][k][dep+1]+f[k+1][r][dep+1]+(k*(dep+1)+c)*d[i]/sum) 8 转移到两个深度+1的子节点中。 9 10 另一种方法: 11 f[l][r]表示l,r区间,构造成树的最小代价。默认根节点的位置是1 12 但是转移到的两个子节点的根节点也是1,所以对这两颗子树中的所有点的深度都加1。 13 f[l][r] = min(f[l][k]+f[k+1][r]+(c*d[i])/sum) // 子树中的c被子树加上了。 14 f[l][r] += k*d[l]/sum + k*d[l+1]/sum +...+ k*d[r]/sum 15 16 17 */ 18 #include<cstdio> 19 #include<algorithm> 20 #include<cstring> 21 #include<iostream> 22 23 using namespace std; 24 25 const int N = 110; 26 int n; 27 double f[N][N],k,c; 28 int sum[N]; 29 30 double dfs(int l,int r) { 31 if (l > r) return 0; 32 if (f[l][r] != -1) return f[l][r]; 33 double &ans = f[l][r]; 34 ans = 1e18; 35 for (int i=l; i<=r; ++i) 36 ans = min(ans,dfs(l,i-1)+dfs(i+1,r)+(1.0*c*(sum[i]-sum[i-1]))/(1.0*sum[n])); 37 ans += (1.0*k*(sum[r]-sum[l-1]))/(1.0*sum[n]); 38 return ans; 39 } 40 int main() { 41 scanf("%d%lf%lf",&n,&k,&c); 42 for (int a,i=1; i<=n; ++i) { 43 scanf("%d",&sum[i]);sum[i] += sum[i-1]; 44 } 45 for (int i=1; i<=35; ++i) 46 for (int j=1; j<=35; ++j) 47 f[i][j] = -1; 48 printf("%.3lf",dfs(1,n)); 49 return 0; 50 }
luogu P1970 花匠
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<iostream> 5 #include<cmath> 6 7 using namespace std; 8 9 const int N = 100100; 10 int a[N],f[N][2]; 11 12 inline int read() { 13 int x = 0,f = 1;char ch = getchar(); 14 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 15 for (; isdigit(ch); ch=getchar()) x = x*10+ch-'0'; 16 return x * f; 17 } 18 19 int main() { 20 int n = read(); 21 for (int i=1; i<=n; ++i) a[i] = read(); 22 f[1][0] = f[1][1] = 1; 23 for (int i=2; i<=n; ++i) { 24 if (a[i] > a[i-1]) { 25 f[i][1] = f[i-1][0] + 1; 26 f[i][0] = f[i-1][0]; 27 } else if (a[i-1] > a[i]) { 28 f[i][0] = f[i-1][1] + 1; 29 f[i][1] = f[i-1][1]; 30 } else { 31 f[i][0] = f[i-1][0]; 32 f[i][1] = f[i-1][1]; 33 } 34 } 35 cout << max(f[n][0],f[n][1]); 36 return 0; 37 }
贪心的思想:每次取的花都应当在上升序列和下降序列的转折点上,加上首尾的花。
bzoj 1260: [CQOI2007] 涂色paint
1 /* 2 区间dp,f[i][j]表示区间i到j的答案。 3 转移时枚举中间点即可。 4 但是此题不同于一般dp之处在于,可以首先覆盖一部分,然后在覆盖这一部分其他的地方。 5 这样或许是可以节省次数的。 6 7 AAABA 例如对于这样的例子,直接合并找中间点是无法做到最优的,3步。 8 而首先覆盖[1,5],然后覆盖[4,4]只需要两步。 9 10 所以转移时应该特判,可以在很早时,已经可以顺便把这个点的情况。 11 */ 12 #include<cstdio> 13 #include<cstring> 14 #include<iostream> 15 16 const int N = 55; 17 int f[N][N]; 18 char s[N]; 19 20 int main() { 21 scanf("%s",s+1); 22 int n = strlen(s+1); 23 memset(f,0x3f,sizeof(f)); 24 for (int i=1; i<=n; ++i) f[i][i] = 1; 25 for (int k=2; k<=n; ++k) { 26 for (int i=1; (i+k-1)<=n; ++i) { 27 int j = i + k - 1; 28 if (s[i]==s[i+1]) f[i][j] = f[i+1][j]; 29 else if (s[j-1]==s[j]) f[i][j] = f[i][j-1]; 30 else if (s[i]==s[j]) f[i][j] = std::min(f[i][j-1],f[i+1][j]); 31 else { 32 for (int m=i; m<j; ++m) 33 f[i][j] = std::min(f[i][j],f[i][m]+f[m+1][j]); 34 } 35 } 36 } 37 std::cout<<f[1][n]; 38 return 0; 39 }
bzoj 4247 挂饰
1 /* 2 考虑状态的表示f[i][j]表示到第i个物品,剩余j个挂钩,最大的价值。 3 然后发现j无法转移,因为挂饰的顺序可以交换,可以在后面拿一个挂钩很多的,然后在前面选一些挂钩较少的。 4 那么当前j是负数也无所谓了,只要后面有一个挂钩很多的,把前面的负数补上就可以了。 5 6 如何处理, 7 1.允许负数,f[i][j],j可以是负数。 (没写) 8 2.改变枚举顺序,从挂钩多的到挂钩少的枚举。(排序) 9 10 如果用“拉”的写法:f[i][j] <- f[i][j-a[i]+1] (+1,表示一个挂钩挂第i个物品) 11 然后,j-a[i]+1可能小于0,那么我们只要让他从f[i-1][1]转移就好了,表示只有一个挂钩挂第i个物品。 12 13 如果用“推”的写法:f[i][j] -> f[i+1][j+a[i]-1] ,j+a[i]-1>n的时候,强制为n即可,大于n的没有用。 14 15 */ 16 #include<cstdio> 17 #include<algorithm> 18 #include<cstring> 19 #include<iostream> 20 #include<cctype> 21 22 const int N = 2010; 23 24 struct Node{ 25 int a,b; 26 bool operator < (const Node &A) const { 27 return a > A.a; 28 } 29 }d[N]; 30 int f[N][N]; 31 32 inline int read() { 33 int x = 0,f = 1;char ch=getchar(); 34 for (; !isdigit(ch); ch=getchar()) if(ch=='-')f=-1; 35 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 36 return x*f; 37 } 38 int main() { 39 int n = read(); 40 for (int i=1; i<=n; ++i) 41 d[i].a = read(),d[i].b = read(); 42 std::sort(d+1,d+n+1); 43 memset(f,-0x3f,sizeof(f)); 44 f[0][1] = 0; 45 for (int i=1; i<=n; ++i) 46 for (int j=0; j<=n; ++j) 47 f[i][j] = std::max(f[i-1][j],f[i-1][std::max(j-d[i].a,0)+1]+d[i].b); 48 int ans = 0; 49 for (int i=0; i<=n; ++i) 50 ans = std::max(ans,f[n][i]); 51 std::cout<<ans; 52 return 0; 53 }
bzoj 1296 [SCOI2009]粉刷匠
1 /* 2 给n个木板,一共可以刷T次,每次将可以将一段连续的格子刷成0/1 ,求最多可以刷多大的面积。 3 4 n个木板是相互不干涉的,(不能跨木板刷,刚开始以为给了一个矩阵,然后以为可以竖着刷。。。) 5 所以对于每个木板求出刷了1,2,3...T次的最大价值。 6 然后背包合并即可。 7 8 f[i][j]表示到第i个格子,刷了j次的最大价值。 9 dp[i]表示一共刷了i次的最大价值。 10 */ 11 #include<cstdio> 12 #include<algorithm> 13 #include<cstring> 14 #include<cmath> 15 #include<iostream> 16 #include<cctype> 17 using namespace std; 18 19 int f[55][55],dp[2500],sum[55]; 20 char s[55]; 21 22 23 int main() { 24 int n,m,t; 25 cin >> n >> m >> t; 26 for (int T=1; T<=n; ++T) { 27 scanf("%s",s+1); 28 for (int i=1; i<=m; ++i) sum[i] = sum[i-1] + (s[i]=='1'); 29 30 for (int i=1; i<=m; ++i) { 31 for (int j=1; j<=i; ++j) { 32 f[i][j] = 1; // 到第i个位置,用了j次。 33 for (int k=0; k<i; ++k) { // 到第k个位置,用了j-1次 ,k=0表示从头开始涂。 34 f[i][j] = max(f[i][j],f[k][j-1]+max(sum[i]-sum[k],i-k-sum[i]+sum[k])); 35 } 36 } 37 } 38 39 for (int i=t; i>=0; --i) { 40 for (int j=1,lim=min(i,m); j<=lim; ++j) 41 dp[i] = max(dp[i],dp[i-j]+f[m][j]); 42 } 43 } 44 45 int ans = 0; 46 for (int i=1; i<=t; ++i) ans = max(ans,dp[i]); 47 cout << ans; 48 49 return 0; 50 }
bzoj 1025 [SCOI2009]游戏
1 /* 2 思路题。 3 首先将置换编程循环节的形式,那么答案为lcm(L1,L2,L3...Lk),Li为第i个循环节的长度。 4 (理解:假设每个置换是一个环,那么为了让所有的环走回起点,那么走的长度就是所有环的长度的最小公倍数) 5 6 那么就是求和为n的序列的最小公倍数的种数。 7 8 构造一个答案(即构造最小公倍数),判断是否可行。 9 10 设构造的最小公倍数为A=p1^a1 * p2^a2 * p3^a3... 11 那么假设每个循环节为A1=p1^a1, A2=p2^a2, A3=p3^a3 12 因为这些循环节(Ai)之间没有共同的质因子,所以它们的lcm就是乘起来。 13 对于这些循环节长度,如果它们的和<=n那么说明对于最小公倍数x是可行的(其它的循环节补1)。 14 15 那么就是求有多少种(a1,a2,a3,...am),满足p1^a1+p2^a2+p3^a3+...+pm^am<=n 16 备注:每一种(a1,a2,a3...am)对应得p1^a1+p2^2+p3^a3 都是不同的。 17 18 f[i][j]表示前i个质数,和为j的方案数。f[i][j] += f[i-1][j-k] k=pri[i]^0,1,2,... 19 20 */ 21 22 #include<cstdio> 23 #include<iostream> 24 #define LL long long 25 #define N 1010 26 27 LL f[N][N]; 28 int pri[N],n,tot; 29 bool nopri[N]; 30 31 void getpri() { 32 for (int i=2; i<=n; ++i) { 33 if (!nopri[i]) pri[++tot] = i; 34 for (int j=1; j<=tot&&i*pri[j]<=n; ++j) { 35 nopri[i*pri[j]] = true; 36 if (i % pri[j] == 0) break; 37 } 38 } 39 } 40 void solve() { 41 f[0][0] = 1; 42 // f[i][j] 前i个质数,和为j的排数。 43 for (int i=1; i<=tot; ++i) { 44 for (int j=0; j<=n; ++j) { 45 f[i][j] = f[i-1][j]; // 第i个质数不用,(即0次方) 46 for (int k=pri[i]; k<=j; k*=pri[i]) { // 第i个质数的1,2,3...次方 47 f[i][j] += f[i-1][j-k]; 48 } 49 } 50 } 51 LL ans = 0; 52 for (int i=0; i<=n; ++i) ans += f[tot][i]; 53 std::cout << ans; 54 } 55 int main() { 56 std::cin >> n; 57 getpri(); 58 solve(); 59 return 0; 60 }
bzoj 1055 [HAOI2008]玩具取名
1 /* 2 给定W,I,N,G转换为两个字母的规则。 3 W -> IN ... 4 求一个最终的字符串可以由哪个字符转移而来,可能有1,2,3,4个。 5 6 开始是想的是树的做法(推回去),发现连边太多。。。 7 区间dp,f[i][j][k]=0/1 区间[l,r]能否有字符k转移而来(k=W,I,N,G) 8 枚举左右起点,中间点,枚举转移规则,转移。 9 10 开始时想的是,把每一个字符可以转移到的两个字符记下来。枚举每个字符转移。即枚举f[i][j][k],k也枚举出来。 11 后来在网上发现更简单的写法,直接把所有转移的规则记录下来,枚举所有的转移规则即可。 12 13 由于有先后的顺序(IN->W ,NI-!>W),所以所有转移规则枚举一次即可,不需要反过来在枚举一次。 14 即f[i][j][z] <- f[i][k][x] & f[k+1][j][y] 15 不需要 f[i][j][z] <- f[i][k][y] & f[k+1][j][x],而且这样是不对的。 16 17 */ 18 #include<cstdio> 19 #include<cstring> 20 #define N 210 21 22 struct Str{ 23 int x,y,z; 24 }g[N]; 25 bool f[N][N][5]; 26 int c[5],p[N]; 27 char s[N]; 28 29 int main() { 30 p['W'] = 1;p['I'] = 2;p['N'] = 3;p['G'] = 4; 31 scanf("%d%d%d%d",&c[1],&c[2],&c[3],&c[4]); 32 int cnt = 0; 33 for (int i=1; i<=4; ++i) { 34 for (int j=1; j<=c[i]; ++j) { 35 scanf("%s",s); 36 g[++cnt].x = p[s[0]]; g[cnt].y = p[s[1]]; g[cnt].z = i; 37 } 38 } 39 scanf("%s",s+1); 40 int n = strlen(s+1); 41 for (int i=1; i<=n; ++i) f[i][i][p[s[i]]] = true; 42 43 for (int len=2; len<=n; ++len) { 44 for (int r,l=1; (r=l+len-1)<=n; ++l) { 45 for (int k=l; k<=r; ++k) { 46 for (int i=1; i<=cnt; ++i) { 47 f[l][r][g[i].z] |= (f[l][k][g[i].x] & f[k+1][r][g[i].y]); // &比&&快好多 48 } 49 } 50 } 51 } 52 53 bool flag = false; 54 if (f[1][n][1]) printf("W"),flag = true; 55 if (f[1][n][2]) printf("I"),flag = true; 56 if (f[1][n][3]) printf("N"),flag = true; 57 if (f[1][n][4]) printf("G"),flag = true; 58 if (!flag) printf("The name is wrong!"); 59 return 0; 60 }
bzoj 1084 [SCOI2005]最大子矩阵
1 /* 2 n*2的方格,选出k个矩阵。 3 4 m=1时,经典dp,O(nnk)。f[i][j]到底i个位置,选了j个矩阵。转移:不选i或者枚举转移点k。 5 6 m=2时,转移时维护三维 dp[i][j][k],左边一列到第i行,右边一列到底j行,选了k个矩阵。 7 转移:不选:可以从左边和右边转移。 8 选: 在左边枚举转移点,在右边枚举转移点,i=j时,可以左右边一起枚举转移点。 9 10 注意:转移时,不管到什么位置,必须要更新选了0个的情况(等于0),初始时更新一下行为0的情况。代码36,54行。 11 */ 12 13 #include<cstdio> 14 #include<algorithm> 15 #include<cstring> 16 #include<iostream> 17 #include<cctype> 18 19 using namespace std; 20 21 const int N = 105; 22 int dp[N][N][12],sum[N][2]; 23 int f[N][12],s[N]; 24 int n,m,K; 25 26 inline int read() { 27 int x = 0,f = 1;char ch=getchar(); 28 for (; !isdigit(ch); ch=getchar()) if(ch=='-')f=-1; 29 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 30 return x*f; 31 } 32 33 void solve1() { 34 for (int i=1; i<=n; ++i) s[i] = s[i-1] + read(); 35 memset(f,-0x3f,sizeof(f)); 36 f[0][0] = 0; 37 for (int i=1; i<=n; ++i) { // 到第i个位置 38 f[i][0] = 0; 39 for (int j=1; j<=K; ++j) { // 选了j个矩阵 40 f[i][j] = f[i-1][j]; // 不选i 41 for (int k=0; k<i; ++k) { // 选i,那么i在的矩阵(当前矩阵)为[k+1~i]行。 42 f[i][j] = max(f[i][j],f[k][j-1]+s[i]-s[k]); 43 } 44 } 45 } 46 cout << f[n][K]; 47 } 48 void solve2() { 49 for (int i=1; i<=n; ++i) 50 for (int j=1; j<=m; ++j) 51 sum[i][j] = sum[i-1][j] + read(); 52 memset(dp,-0x3f,sizeof(dp)); 53 dp[0][0][0] = 0; 54 for (int i=0; i<=n; ++i) dp[i][0][0] = dp[0][i][0] = 0; 55 56 for (int i=1; i<=n; ++i) { // 左边一列 57 for (int j=1; j<=n; ++j) { // 右边一列 58 dp[i][j][0] = 0; 59 for (int k=1; k<=K; ++k) { // 选了k个矩阵 60 dp[i][j][k] = max(dp[i-1][j][k],dp[i][j-1][k]); // 当前这个位置不选 61 for (int p=0; p<i; ++p) // 选i,其构成的当前矩阵是左边一列[p+1~i]行 62 dp[i][j][k] = max(dp[i][j][k],dp[p][j][k-1]+sum[i][1]-sum[p][1]); 63 for (int p=0; p<j; ++p) // 当前矩阵是右边一列[p+1~i]行 64 dp[i][j][k] = max(dp[i][j][k],dp[i][p][k-1]+sum[j][2]-sum[p][2]); 65 if (i==j) { 66 for (int p=0; p<i; ++p) // 当前矩阵是左右两列的 [p+1~i]行 67 dp[i][j][k] = max(dp[i][j][k],dp[p][p][k-1]+sum[i][1]-sum[p][1]+sum[j][2]-sum[p][2]); 68 } 69 } 70 } 71 } 72 cout << dp[n][n][K]; 73 } 74 int main() { 75 n = read(),m = read(),K = read(); 76 if (m==1) solve1(); 77 else solve2(); 78 return 0; 79 } 80 /* 81 5 1 2 82 -9 10 2 -1 3 83 */
bzoj 1304 [CQOI2009]叶子的染色
1 #include<cstdio> 2 #include<iostream> 3 #include<cctype> 4 5 using namespace std; 6 7 const int INF = 1e9; 8 const int N = 20010; 9 int head[N],nxt[N<<1],to[N<<1],dp[N][2]; 10 int n,m,Enum; 11 12 inline int read() { 13 int x = 0,f = 1;char ch = getchar(); 14 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 15 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 16 return x * f; 17 } 18 void add_edge(int u,int v) { 19 ++Enum; to[Enum] = v; nxt[Enum] = head[u]; head[u] = Enum; 20 ++Enum; to[Enum] = u; nxt[Enum] = head[v]; head[v] = Enum; 21 } 22 void DP(int u,int fa) { 23 if (u <= m) return ; 24 dp[u][1] = dp[u][0] = 1; 25 for (int i=head[u]; i; i=nxt[i]) { 26 int v = to[i]; 27 if (v==fa) continue; 28 DP(v,u); 29 dp[u][1] += min(dp[v][1]-1,dp[v][0]); 30 dp[u][0] += min(dp[v][0]-1,dp[v][1]); 31 } 32 } 33 int main () { 34 n = read(),m = read(); 35 for (int i=1; i<=m; ++i) { 36 int c = read(); 37 dp[i][c] = 1;dp[i][c^1] = INF; 38 } 39 for (int i=1; i<n; ++i) { 40 int u = read(),v = read(); 41 add_edge(u,v); 42 } 43 DP(n,0); 44 cout << min(dp[n][1],dp[n][0]); 45 return 0; 46 }
bzoj 1044 [HAOI2008]木棍分割
1 /* 2 先二分+贪心判断。 3 然后dp 4 f[i][j]到第i个数,分了j次的最大价值。 5 f[i][j] = {sigma f[k][j-1] ,sum[i]-sum[k]<=Len} 6 后面的“滑动窗口”处理。 7 */ 8 #include<cstdio> 9 #include<algorithm> 10 #include<cstring> 11 #include<cmath> 12 #include<iostream> 13 14 using namespace std; 15 16 const int N = 50010; 17 const int mod = 10007; 18 int a[N],f[N][2],q[N],sum[N],n,m; 19 20 inline int read() { 21 int x = 0,f = 1;char ch = getchar(); 22 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 23 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 24 return x * f; 25 } 26 27 bool check(int x) { 28 int cnt = 1,sum = 0; // zz的写成sum=1... 29 for (int i=1; i<=n; ++i) { 30 sum += a[i]; 31 if (sum > x) { 32 sum = a[i]; 33 cnt ++; 34 if (cnt == m + 2) return false; 35 } 36 } 37 return true; 38 } 39 int main () { 40 n = read(),m = read(); 41 int L = 0,R = 0,mid,ans; 42 for (int i=1; i<=n; ++i) { 43 a[i] = read(); 44 L = max(L,a[i]),R += a[i]; 45 sum[i] = sum[i-1] + a[i]; 46 } 47 while (L <= R) { 48 mid = (L + R) / 2; 49 if (check(mid)) ans = mid,R = mid - 1; 50 else L = mid + 1; 51 } 52 cout << ans << ' '; 53 54 int cur = 0,Ans = 0,tot; 55 for (int i=1; i<=n; ++i) f[i][cur] = sum[i]<=ans; // 划分了0次 56 for (int j=1; j<=m; ++j) { 57 cur ^= 1; 58 tot = 0,L = 1; 59 for (int i=1; i<=n; ++i) { 60 while (L < i && sum[i]-sum[L] > ans) 61 tot = (tot - f[L++][cur^1] + mod) % mod; 62 f[i][cur] = tot; // 到第i个位置,划分了j次。 63 tot = (tot + f[i][cur^1]) % mod; 64 } 65 Ans = (Ans + f[n][cur]) % mod; 66 } 67 cout << Ans; 68 return 0; 69 }
bzoj 2431 [HAOI2009]逆序对数列
1 /* 2 求n个数(1,2,3...n),有多少个的逆序对数为k的序列 3 4 对于当前i个数j个逆序对的方案数为f[i][j] 5 那么它可以递推到 f[i+1][j] ~ f[i+1][j+i],即计算最后一个数放的位置 6 复杂度n^3 7 8 把上面的式子反过来,f[a][b]可以由 f[a-1][b-a+1] ~ f[a-1][b] 转移而来 9 每次b+1,只有一个数增加一个数减去,所以记录一个类似滑动窗口的变量,O(1)转移即可。 10 复杂度n^2 11 12 bug一堆... 13 */ 14 15 #include<cstdio> 16 #include<iostream> 17 18 using namespace std; 19 20 int f[1010][1010]; 21 const int mod = 10000; 22 23 inline int read() { 24 int x = 0,f = 1;char ch = getchar(); 25 for (; !isdigit(ch); ch=getchar()) if(ch=='-') f=-1; 26 for (; isdigit(ch); ch=getchar()) x=x*10+ch-'0'; 27 return x * f; 28 } 29 30 int main () { 31 int n,k; 32 cin >> n >> k; 33 f[1][0] = 1; 34 for (int i=2; i<=n; ++i) { 35 int sum = 0; 36 int limit = min((i*(i-1))/2,k); 37 38 for (int j=0; j<=limit; ++j) { 39 sum = (sum + f[i-1][j]) % mod; 40 if (j - i >= 0) sum = (sum - f[i-1][j-i] + mod) % mod; 41 f[i][j] = sum; 42 } 43 } 44 cout << f[n][k]; 45 return 0; 46 }
bzoj 2423 [HAOI2010]最长公共子序列
1 /* 2 1、求两个字符串的最长公共子序列,n^2 3 2、求最长的公共子序列有多少个,在第一问的基础上再加几次转移。 4 */ 5 6 #include<bits/stdc++.h> 7 using namespace std; 8 typedef long long LL; 9 10 inline int read() { 11 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 12 for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 13 } 14 15 const int N = 5010; 16 const int mod = 100000000; 17 int f[2][N],g[2][N]; 18 char a[N],b[N]; 19 20 int main () { 21 scanf("%s%s",a+1,b+1); 22 int A = strlen(a+1) - 1,B = strlen(b+1) - 1; 23 24 for (int i=0; i<=B; ++i) g[0][i] = 1;g[1][0] = 1; 25 int cur = 0; 26 for (int i=1; i<=A; ++i) { 27 cur ^= 1; 28 for (int j=1; j<=B; ++j) { 29 if (a[i] == b[j]) { 30 f[cur][j] = f[cur^1][j-1] + 1; 31 g[cur][j] = g[cur^1][j-1]; 32 if (f[cur][j] == f[cur][j-1]) g[cur][j] = (g[cur][j] + g[cur][j-1]) % mod; 33 if (f[cur][j] == f[cur^1][j]) g[cur][j] = (g[cur][j] + g[cur^1][j]) % mod; 34 } 35 else { 36 f[cur][j] = max(f[cur][j-1],f[cur^1][j]); 37 g[cur][j] = 0; 38 if (f[cur][j] == f[cur][j-1]) g[cur][j] = (g[cur][j] + g[cur][j-1]) % mod; 39 if (f[cur][j] == f[cur^1][j]) g[cur][j] = (g[cur][j] + g[cur^1][j]) % mod; 40 if (f[cur][j] == f[cur^1][j-1]) g[cur][j] = (g[cur][j] - g[cur^1][j-1] + mod) % mod;//减去重复计算的 41 } 42 } 43 } 44 cout << f[cur][B] << "\n" << g[cur][B]; 45 return 0; 46 }
bzoj 3191 [JLOI2013]卡牌游戏
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 5 inline int read() { 6 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 7 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 8 } 9 10 const int N = 55; 11 int a[N]; 12 double f[N][N]; // f[i][j]剩i个玩家,第j个人获胜的概率。 13 14 int main() { 15 int n = read(),m = read(); 16 for (int i=1; i<=m; ++i) a[i] = read(); 17 18 f[1][1] = 1.0; 19 for (int i=2; i<=n; ++i) { // 人数i 20 for (int j=1; j<=i; ++j) { // 获胜玩家j 21 for (int k=1; k<=m; ++k) { // 卡牌k 22 int t = a[k] % i; if (t==0) t = i; // 出局玩家t 23 // if (t == j) continue; 24 f[i][j] += f[i-1][(j-t+i)%i] / (1.0 * m); // t出局后,所有玩家编号-t,j就成了 j-t 25 } 26 } 27 } 28 for (int i=1; i<n; ++i) 29 printf("%.2lf%% ",f[n][i]*100.0); 30 printf("%.2lf%%",f[n][n]*100.0); 31 return 0; 32 }
baoj 1899 [Zjoi2004]Lunch 午餐
1 /* 2 两个窗口打饭,每个人有打饭时间与吃饭时间,将所有人分成两组分别到两个窗口,使总时间最小。 3 4 f[i][j][k]到第i个人1号窗口等饭时间为j,2号窗口为k。空间开不下。 5 sum[i]表示到i所有人的登饭时间之和。然后发现j+k=sum[i],所以f[i][j]表示到第i人1号窗口等饭时间为j的总时间。 6 分情况转移。 7 */ 8 9 #include<bits/stdc++.h> 10 using namespace std; 11 typedef long long LL; 12 13 inline int read() { 14 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 15 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 16 } 17 18 const int N = 205; 19 20 int f[N][N*N],sum[N]; 21 struct Node{ 22 int a,b; 23 bool operator < (const Node &A) const { 24 return b > A.b; 25 } 26 }T[N]; 27 28 int main() { 29 30 int n = read(); 31 for (int i=1; i<=n; ++i) 32 T[i].a = read(),T[i].b = read(); 33 34 sort(T+1,T+n+1); 35 for (int i=1; i<=n; ++i) sum[i] = sum[i-1] + T[i].a; 36 37 memset(f,0x3f,sizeof(f)); 38 int ans = f[0][0]; 39 f[0][0] = 0; 40 41 for (int i=1; i<=n; ++i) { 42 for (int j=0; j<=sum[i-1]; ++j) { 43 // 第i个人在二号窗口,后面的取max为都吃完的时间。二号窗口的变动体现在,第一维从i-1到了i(即从i-1转移),sum[i]也就发生了变化。 44 f[i][j] = min(f[i][j], max(f[i-1][j], sum[i-1]-j+T[i].a+T[i].b)); 45 // 在一号窗口 46 f[i][j+T[i].a] = min(f[i][j+T[i].a], max(f[i-1][j], j+T[i].a+T[i].b)); 47 } 48 } 49 for (int i=0; i<=sum[n]; ++i) ans = min(ans,f[n][i]); 50 cout << ans; 51 return 0; 52 }
bzoj 1046 [HAOI2007]上升序列
1 /* 2 求出每个点往后的最长上升子序列,判断。 3 */ 4 #include<bits/stdc++.h> 5 using namespace std; 6 typedef long long LL; 7 8 inline int read() { 9 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 10 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 11 } 12 13 int a[10010],f[10010],g[10010]; 14 15 int find(int l,int r,int x) { // 单调递减-查找第一个大于x的数 16 int ans = 0; 17 while (l <= r) { 18 int mid = (l + r) >> 1; 19 if (g[mid] > x) ans = mid,l = mid + 1; 20 else r = mid - 1; 21 } 22 return ans; 23 } 24 int main() { 25 int n = read(); 26 for (int i=1; i<=n; ++i) a[i] = read(); 27 28 int len = 1; 29 f[n] = 1;g[len] = a[n]; 30 for (int i=n-1; i>=1; --i) { 31 if (a[i] < g[len]) len++,g[len] = a[i],f[i] = len; 32 else { 33 int pos = find(1,len,a[i]); 34 g[pos+1] = max(g[pos+1],a[i]); // -写成了min。。。 35 f[i] = pos + 1; 36 } 37 } 38 39 int m = read(),t,last; 40 while (m--) { 41 t = read(); 42 if (t > len) {puts("Impossible");continue;} 43 last = 0; 44 for (int i=1; ; ++i) { 45 if (f[i] >= t && a[i] > a[last]) { 46 printf("%d ",a[i]); 47 last = i; 48 t --; 49 if (!t) break; 50 } 51 } 52 puts(""); 53 } 54 return 0; 55 }
bzoj 1820 [JSOI2010]Express Service 快递服务
1 /* 2 三个辆车是没有标号的,所以在dp是,顺序是无关的。 3 4 f[i][a][b][c]表示到第i个位置,三辆车分别在a,b,c是的费用。 5 6 空间太大。 7 可以滚动掉第一维,但是空间还是太大。 8 9 然后发现a,b,c中其中一个必定是q[i](收件地点)。 10 那么f[i][a][b]表示到第i个位置,三辆车分别在a,b,q[i]的最大价值。 11 12 */ 13 #include<bits/stdc++.h> 14 using namespace std; 15 typedef long long LL; 16 17 inline int read() { 18 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 19 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 20 } 21 22 const int N = 1005; 23 int g[N][N],a[N],f[2][N][N]; 24 25 int main() { 26 int n = read(); 27 for(int i=1; i<=n; ++i) 28 for (int j=1; j<=n; ++j) g[i][j] = read(); 29 int tot = 0; 30 while (scanf("%d",&a[++tot]) != EOF) ; 31 32 memset(f,0x3f,sizeof(f)); 33 int cur = 1; 34 f[cur][1][2] = g[3][a[1]]; 35 f[cur][1][3] = g[2][a[1]]; 36 f[cur][2][3] = g[1][a[1]]; 37 38 for (int i=2; i<=tot; ++i) { 39 cur ^= 1; 40 memset(f[cur],0x3f,sizeof(f[cur])); 41 for (int j=1; j<=n; ++j) { 42 for (int k=j; k<=n; ++k) { // 从j开始!减小常数! 43 // 当前三辆车的位置为j,k,a[i],所以从a[i-1]转移到a[i] 44 f[cur][j][k] = f[cur][k][j] = min(f[cur][j][k],f[cur^1][j][k]+g[a[i-1]][a[i]]); 45 // 当前三辆车的位置为a[i-1],k,a[i],所以枚举一个j,从j转移到a[i] 46 f[cur][a[i-1]][k] = f[cur][k][a[i-1]] = min(f[cur][a[i-1]][k],f[cur^1][j][k]+g[j][a[i]]); 47 // 当前三辆车的位置为a[i-1],j,a[i],所以枚举一个k,从k转移到a[i] 48 f[cur][a[i-1]][j] = f[cur][j][a[i-1]] = min(f[cur][a[i-1]][j],f[cur^1][j][k]+g[k][a[i]]); 49 } 50 } 51 } 52 int ans = 0x7fffffff; 53 for (int i=1; i<=n; ++i) 54 for (int j=1; j<=n; ++j) 55 ans = min(ans,f[cur][i][j]); 56 cout << ans; 57 return 0; 58 }
bzoj 1996 [Hnoi2010]chorus 合唱队
1 /* 2 3 想出dp的状态表示! 4 5 */ 6 7 #include<bits/stdc++.h> 8 using namespace std; 9 typedef long long LL; 10 11 inline int read() { 12 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 13 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 14 } 15 16 const int N = 1005; 17 const int mod = 19650827; 18 int f[N][N][2],a[N]; 19 20 int main() { 21 int n = read(); 22 for (int i=1; i<=n; ++i) 23 a[i] = read(),f[i][i][0] = 1; 24 for (int i=n; i>=1; --i) { 25 for (int j=i+1; j<=n; ++j) { // 区间i~j 26 if (a[j] > a[i]) f[i][j][1] += f[i][j-1][0]; 27 if (a[j] > a[j-1]) f[i][j][1] += f[i][j-1][1]; 28 if (a[i] < a[j]) f[i][j][0] += f[i+1][j][1]; 29 if (a[i] < a[i+1]) f[i][j][0] += f[i+1][j][0]; 30 if (f[i][j][1] > mod) f[i][j][1] %= mod; 31 if (f[i][j][0] > mod) f[i][j][0] %= mod; 32 } 33 } 34 cout << (f[1][n][0] + f[1][n][1]) % mod; 35 return 0; 36 }
bzoj 2660 [Beijing wc2012]最多的方案
1 /* 2 预处理出所有斐波那契数,然后从大到小选出一个方案。 3 将斐波那契数按照01串排列,第i个为1表示第i个斐波那契数选。 4 5 1 2 3 5 8 13 6 16 = 3 + 13 001001 7 13 = 3 + 5 + 8 001110 8 13 = 1 + 2 + 5 + 8 110110 9 16 = 1 + 2 + 13 110001 10 11 实质就是将一个高位的1的化为两个低位的1的过程,因为不能重复,所以每个位置只能有一个1,不能有多个。 12 考虑一个1最多化为多少种方案: 13 0000001 - 0000110 - 0011010 - 1101010 14 000001 - 000110 - 011010 15 每次只能 将最左边的一个1化为两个1,而右边的1是无法化下去的,所以可以化为的方案数为:1和上一个1之间的0的个数/2 16 dp 模拟过程即可。dp[i][0/1]当前到第几位,是0还是1的方案数。 17 18 说明:比如0000110中的6号: 19 1、6号要化简,那么先找5号,5-3,4 20 2、此时再找4号,4号要化简,那么先找3号 3-1,2 21 ... 22 所以6号是无法化简的。 23 */ 24 #include<bits/stdc++.h> 25 using namespace std; 26 typedef long long LL; 27 28 inline int read() { 29 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 30 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 31 } 32 33 const int N = 105; 34 35 LL f[N],dp[N][2],q[N]; 36 37 int main() { 38 LL n;int tot = 0; 39 cin >> n; 40 f[1] = 1,f[2] = 2; 41 for (int i=3; i<=88; ++i) f[i] = f[i-1] + f[i-2]; 42 for (int i=88; i>=1; --i) { 43 if (n >= f[i]) { 44 q[++tot] = i;n -= f[i]; 45 } 46 } 47 sort(q+1,q+tot+1); 48 dp[1][1] = 1;dp[1][0] = (q[1] - 1) / 2; 49 for (int i=2; i<=tot; ++i) { 50 dp[i][1] = dp[i-1][0] + dp[i-1][1]; 51 dp[i][0] = dp[i-1][0]*((q[i]-q[i-1])/2) + dp[i-1][1]*((q[i]-q[i-1]-1)/2); // 这里必须要把除以2括起来! 52 } 53 cout << dp[tot][0] + dp[tot][1]; 54 return 0; 55 }
bzoj 3612 [Heoi2014]平衡
1 /* 2 妙题! 3 4 一个杠杆,左边长度为n,右边也是,之间也有一个点,每个点上一个橡皮,重量相同,问拿走k个有多少种方案是杠杆平衡。 5 6 相当于整数划分,左边a个数构成c,右边选k-a个数,构成c。 7 但是这里的整数划分不能取相同的数(每个位置上一个橡皮)。 8 原整数划分方程 f[i][j]=f[i-1][j-1]+f[i-j][j],分为增加一个1的 和 所有数都加1的转移。 9 现在转移方程 f[i][j]=f[i-j][j-1]+f[i-j][j],分为将所有数加1然后增加一个1的 和 所有数都加1的。 10 11 而且不能有>n的数。所以一旦有n+1了,就减去。 12 当前为i>=n+1,假设之前的包含n+1的都减去了,i=(n+1)+(i-(n+1)) 13 就是说当前为i,有j个数,那么包含n+1的就是 和为(i-n-1)有j-1个数的个数,即f[i-n-][j-1] 14 15 */ 16 #include<bits/stdc++.h> 17 using namespace std; 18 typedef long long LL; 19 20 inline int read() { 21 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 22 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 23 } 24 25 int f[100010][12]; // 空间n*k 26 27 int main() { 28 int Case = read(); 29 while (Case--) { 30 int n = read(),k = read(),p = read(); 31 f[0][0] = 1; 32 int W = n * k; // 这里是最大的质量,其实是n+(n-1)+(n-2)+...共k个。 33 for (int i=1; i<=W; ++i) { 34 for (int j=1,lim=min(i,k); j<=lim; ++j) { 35 f[i][j] = (f[i-j][j-1] + f[i-j][j]) % p; 36 if (i > n) f[i][j] = (f[i][j] - f[i-n-1][j-1] + p) % p; 37 } 38 } 39 40 LL ans = 0; 41 for (int i=0; i<=W; ++i) { 42 for (int j=0; j<=k; ++j) { 43 ans = (ans + 1ll * f[i][j] * f[i][k-j]) % p; 44 if (j < k) ans = (ans + 1ll * f[i][j] * f[i][k-j-1]) % p; // 拿0号位置的物品 45 } 46 } 47 cout << ans << "\n"; 48 } 49 50 return 0; 51 }
bzoj 4321 queue2
1 /* 2 3 开始写了一个线性的递推,由i-1个时的方案数*(i-2)得到i的方案。发现不对。。。 4 5 想不出状态的表示,于是看题解。。。 6 f[i][j][0/1]表示推到第i个数,其中有j对数是相差1的,其中i与i-1不相邻或相邻。 7 8 为什么这样表示? 9 10 开始想的是,当前状态,满足条件的个数,但是这没法转移。 11 新加入一个数i,一个状态在i-1时不满足,而i插入到不满足的数对中间,使得没有相邻的了,满足了,所以只记录满足的情况是无法转移的,也要记下不满足的情况。 12 所以正确的状态表示为记录当前有多少不满足的对数,即相差为一的。 13 如果只用两维的话f[i][j]表示到第i个数,有j对的话。 14 那么对于i-1和i-2挨着,i插入后和i-1挨着,但是插入到它们两个之间,此时是消了一对,加了一对,如果只用两维的话这种情况判不出来。 15 16 之后分情况讨论i放到哪个位置,进行转移。 17 18 */ 19 20 #include<bits/stdc++.h> 21 using namespace std; 22 23 const int N = 1010; 24 const int mod = 7777777; 25 long long f[N][N][2]; 26 27 int main() { 28 int n;cin >> n; 29 f[1][0][0] = 1; 30 for (int i=2; i<=n; ++i) { 31 for (int j=0; j<=n; ++j) { 32 f[i][j][1] = (f[i-1][j][1] + f[i-1][j-1][1] + f[i-1][j-1][0] * 2) % mod; 33 f[i][j][0] = (f[i-1][j+1][1] * j + f[i-1][j+1][0] * (j+1)) % mod; 34 f[i][j][0] = (f[i][j][0] + f[i-1][j][1] * (i-j-1) + f[i-1][j][0] * (i-j-2)) % mod; 35 } 36 } 37 cout << f[n][0][0]; 38 return 0; 39 }
bzoj 1925 [Sdoi2010]地精部落
1 /* 2 https://www.cnblogs.com/ciao-sora/p/5988265.html 3 粘个网址,以后再看。 4 */ 5 #include<bits/stdc++.h> 6 using namespace std; 7 typedef long long LL; 8 9 const int N = 5005; 10 int f[2][N]; 11 12 int main() { 13 int n,p;cin >> n >> p; 14 if (n==1) return !printf("1"); 15 16 int cur = 1; 17 f[1][1] = 1; 18 for (int i=2; i<=n; ++i) { 19 cur ^= 1; 20 for (int j=1; j<=n; ++j) 21 f[cur][j] = (f[cur][j-1] + f[cur^1][i-j]) % p; 22 } 23 cout << (f[cur][n] * 2) % p; 24 return 0; 25 }
bzoj 1966 [Ahoi2005]VIRUS 病毒检测
1 /* 2 开始想的dp,第一个匹配到哪,第二个匹配到哪,能不能匹配上。 3 然后发现复杂度有点大,看题解。。。就是这样。。。 4 */ 5 #include<bits/stdc++.h> 6 using namespace std; 7 typedef long long LL; 8 9 inline int read() { 10 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 11 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 12 } 13 14 const int N = 505; 15 bool f[N<<1][N]; 16 char a[N<<1],b[N]; // char a[N],b[N>>1]! 17 int c[N<<1]; // int c[N]! 18 19 bool solve(int n,int m) { 20 memset(f,false,sizeof(f)); 21 memset(c,0x3f,sizeof(c)); 22 f[0][0] = true; 23 for (int i=1; i<=n; ++i) { 24 if (a[i] != '*') { 25 for (int j=1; j<=m; ++j) { 26 if (a[i] == '?' || a[i] == b[j]) { 27 f[i][j] |= f[i-1][j-1]; 28 if (a[i-1] == '*' && c[i-1] < j) f[i][j] = 1; 29 } 30 } 31 } 32 else { 33 if (i==1) f[i][0] = true; 34 for (int j=1; j<=m; ++j) { 35 f[i][j] = (f[i-1][j] | f[i][j-1]); 36 if (f[i][j]) c[i] = min(c[i],j); 37 } 38 } 39 } 40 if (f[n][m]) return false; 41 return true; 42 } 43 int main() { 44 scanf("%s",a+1);int n = strlen(a+1); 45 int q = read(),ans = 0; 46 while (q--) { 47 scanf("%s",b+1); 48 int m = strlen(b+1); 49 if (solve(n,m)) ans ++; 50 } 51 cout << ans; 52 return 0; 53 }
bzoj 1237 [SCOI2008]配对
1 /* 2 如果没有不能相同配对的限制,那么排序后对应位置相减输出即可。 3 题目中有这样一句话:保证所有 Ai各不相同,Bi也各不相同。 4 开始时没读出来。。。 5 6 首先还是排序。 7 然后分情况看一下,如果A[i]!=B[i]那么直接计算答案即可。 8 如果有连续一个相同的,那么它可以和上一个或者下一个交叉一下。 9 如果有两个连续相同的,那么它们也可以交叉互换一下。 10 如果有三个,可以进行三个的交叉互换,有三种交换的情况。 11 如果有四个,那么可以拆成两个的。 12 如果有五个,也可以拆。 13 ... 14 所以最多可以和前后2个位置的数交换。既保证满足条件,也保证是最优的。 15 16 */ 17 #include<bits/stdc++.h> 18 using namespace std; 19 typedef long long LL; 20 21 inline int read() { 22 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 23 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 24 } 25 26 const LL INF = 1e18; 27 const int N = 1e5+10; 28 LL a[N],b[N],f[N]; 29 30 #define get(a,b) (a==b?INF:abs(a-b)) 31 32 int main() { 33 int n = read(); 34 for (int i=1; i<=n; ++i) 35 a[i] = read(),b[i] = read(); 36 sort(a+1,a+n+1); 37 sort(b+1,b+n+1); 38 if (n == 1 && a[1] == b[1]) return !printf("-1"); 39 f[1] = get(a[1],b[1]); 40 f[2] = min(f[1]+get(a[2],b[2]),get(a[1],b[2])+get(a[2],b[1])); 41 for (int i=3; i<=n; ++i) { 42 f[i] = f[i-1] + get(a[i],b[i]); 43 f[i] = min(f[i],f[i-2]+get(a[i-1],b[i])+get(b[i-1],a[i])); 44 f[i] = min(f[i],f[i-3]+get(a[i],b[i-2])+get(a[i-1],b[i])+get(a[i-2],b[i-1])); 45 f[i] = min(f[i],f[i-3]+get(a[i],b[i-1])+get(a[i-1],b[i-2])+get(a[i-2],b[i])); 46 f[i] = min(f[i],f[i-3]+get(a[i],b[i-2])+get(a[i-1],b[i-1])+get(a[i-2],b[i])); 47 } 48 cout << f[n]; 49 return 0; 50 }
bzoj 1560 [JSOI2009]火星藏宝图
1 /* 2 妙题!巧妙的优化! 3 首先如果想到的是n^2的dp,建立一个拓扑图,在拓扑图上进行dp。 4 发现如果a->b->c进行转移,一定不如a->c转移优,即(x+y)^2 = x^2 + y^2 + 2xy > x^2 + y^2 5 所以每个点只会从和他临近的点转移,然后就不会了。。。 6 7 这个临近具体是什么呢? 8 考虑如何一个点会成为b这样的,可以确定每一列中,如果有一个点比b还要靠下,那么b就不会即系转换以,b可以转移到这个点,这个点在转移。 9 那么可以维护每一列最靠下的点是哪个,对于一个点,直接从这些点中转移即可。 10 其实这些点有些也是可以被替代的。 11 12 时间复杂度O(nm) 13 14 */ 15 #include<bits/stdc++.h> 16 using namespace std; 17 typedef long long LL; 18 19 inline int read() { 20 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 21 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 22 } 23 const int N = 2e5+10; 24 const int M = 1e3+10; 25 struct Node{ 26 int x,y,w; 27 bool operator < (const Node &A) const { 28 if (x == A.x) return y < A.y; 29 return x < A.x; 30 } 31 }A[N]; 32 int f[M],pos[M]; 33 34 35 int main() { 36 int n = read(),m = read(); 37 for (int i=1; i<=n; ++i) { 38 A[i].x = read(),A[i].y = read(),A[i].w = read(); 39 } 40 sort(A+1,A+n+1); 41 f[1] = 0;pos[1] = 1; 42 for (int i=1; i<=n; ++i) { 43 int t = -0x7fffffff; 44 for (int j=1; j<=A[i].y; ++j) { 45 if (pos[j]) 46 t = max(t,f[j]-(A[i].x-pos[j])*(A[i].x-pos[j])-(A[i].y-j)*(A[i].y-j)); 47 } 48 f[A[i].y] = t + A[i].w; 49 pos[A[i].y] = A[i].x; 50 } 51 cout << f[m]; 52 return 0; 53 }
bzoj 3174 [Tjoi2013]拯救小矮人
1 /* 2 贪心+dp 3 贪心的思想,a+b比较小的先走,那么逃跑的人是排序后的一段a+b的上升序列,所以首先按照a+b排序。 4 贪心可以确定取的顺序是最优的,但是还要确定最多能走掉多少个 5 接下来要dp:令f[i]表示走完i个矮人之后还能取到的最大高度 6 这样贪心的作用就出来了:按照贪心完的顺序取走矮人,可以保证最优。 7 一开始初始化f[0]表示没有矮人走掉,f[0]=Σa[i]。 8 然后就是枚举取到第i个矮人,可以用它来更新f[j]的情况是f[j]+hi.b>=H。 9 这样就可以算出最大值了 10 11 */ 12 #include<bits/stdc++.h> 13 using namespace std; 14 typedef long long LL; 15 16 inline int read() { 17 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 const int N = 2010; 22 struct Node{ 23 int a,b; 24 bool operator < (const Node &A) const { 25 return (a + b) < (A.a + A.b); 26 } 27 }h[N]; 28 int f[N]; 29 30 int main() { 31 int n = read(),sum = 0; 32 for (int i=1; i<=n; ++i) 33 h[i].a = read(),h[i].b = read(),sum += h[i].a; 34 sort(h+1,h+n+1); 35 memset(f,-1,sizeof(f)); 36 f[0] = sum; 37 int H = read(),ans = 0; 38 for (int i=1; i<=n; ++i) { 39 for (int j=ans; j>=0; --j) { 40 if (f[j] + h[i].b >= H) f[j+1] = max(f[j+1],f[j]-h[i].a); 41 } 42 if (f[ans+1] >= 0) ans++; 43 } 44 cout << ans; 45 return 0; 46 }
bzoj 1855 [Scoi2010]股票交易
1 /* 2 f[i][j]表示到第i天,有j股的最大价值。 3 朴素n^3, 4 枚举天,枚举股票,枚举买了多少股票。 5 f[i][j] = max(f[i-1][j],f[i-w-1][j-k]-ak)) 6 转化枚举上一天有多少股票 7 f[i][j] = max(f[i-1][j],f[i-w-1][k]-a(j-k)) 8 f[i][j] = max(f[i-1][j],f[i-w-1][k]+ak-aj) 9 f[i-w-1][k]+ak 用单调队列优化。 10 https://www.cnblogs.com/qt666/p/7425571.html 11 */ 12 #include<bits/stdc++.h> 13 using namespace std; 14 typedef long long LL; 15 16 inline int read() { 17 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 18 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 19 } 20 21 const int N = 2010; 22 int f[N][N],q[N],L,R; 23 24 int main() { 25 memset(f,-0x3f,sizeof(f)); 26 int n = read(),m = read(),w = read(); 27 for (int i=1; i<=n; ++i) { 28 int a = read(),b = read(),s = read(),t = read(); 29 for (int j=0; j<=s; ++j) f[i][j] = -a*j; // 买入 30 for (int j=0; j<=m; ++j) f[i][j] = max(f[i][j],f[i-1][j]); // 卖出 31 if (i <= w) continue; 32 L = 1,R = 0; 33 for (int j=0; j<=m; ++j) { // 买入 34 while (L <= R && q[L] < j - s) L++; // 最多买s股,保证买的合法 35 while (L <= R && f[i-w-1][q[R]]+q[R]*a <= f[i-w-1][j]+j*a) R--; // 每次取大的,保证转移的点最优 36 q[++R] = j; 37 if (L <= R) f[i][j] = max(f[i][j],f[i-w-1][q[L]]+q[L]*a-j*a); 38 } 39 L = 1,R = 0; 40 for (int j=m; j>=0; --j) { // 卖出 41 while (L <= R && q[L] > j + t) L ++; // --j + b 42 while (L <= R && f[i-w-1][q[R]]+q[R]*b <= f[i-w-1][j]+j*b) R--; 43 q[++R] = j; 44 if (L <= R) f[i][j] = max(f[i][j],f[i-w-1][q[L]]+q[L]*b-j*b); 45 } 46 } 47 int ans = 0; 48 for (int i=0; i<=m; ++i) ans = max(ans,f[n][i]); 49 cout << ans; 50 return 0; 51 }
bzoj 2424 [HAOI2010]订货
1 /* 2 3 推出状态的表示和转移方程。 4 f[i][j]表示到第i天,库存量为j(这个库存量表示今天已经卖完了剩下的)的花费。 5 6 枚举上一天的库存量。 7 f[i][j] = f[i-1][k] + k*m + (j+u-k)*d 8 f[i][j] = f[i-1][k] + (m-d)*k + (j+u)*d; 9 10 发现对于i从1~i中取最小的, 11 对于i+1从1~i+1中取最小的, 12 那么其中1~i的是重复的,所以记一个变量,每次取min即可。 13 14 与单调队列转移不同的是: 15 每个点的转移区间是递增的。 16 i: 1~10 17 i+1: 3~12 18 i+2: 7~15 19 这样重复的计算的是两个区间重叠的部分,前面也有不满足的。 20 所以每次需要在前面弹出一些不满足的,在后面弹出一些较大的(默认从小的转移)。 21 然后加入当前的。 22 23 24 */ 25 #include<bits/stdc++.h> 26 using namespace std; 27 typedef long long LL; 28 29 inline int read() { 30 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 31 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 32 } 33 34 #define Rep(i,a,b) for (int i = (a); i <= b; ++ i) 35 const int N = 55; 36 const int M = 10005; 37 int f[N][M],U[N],d[N]; 38 39 int main() { 40 int n = read(),m = read(),S = read(); 41 Rep (i,1,n) U[i] = read(); 42 Rep (i,1,n) d[i] = read(); 43 memset(f,0x3f,sizeof(f)); 44 f[0][0] = 0; 45 Rep (i,1,n) { 46 int res = 1e9,k = 0; 47 Rep (j,0,S) { 48 while (k <= min(j+U[i],S)) { 49 res = min(res, f[i-1][k] + (m - d[i]) * k); k++; 50 } 51 f[i][j] = res + (j + U[i]) * d[i]; 52 } 53 } 54 cout << f[n][0]; 55 return 0; 56 }
bzoj 1063 [Noi2008]道路设计
1 /* 2 神奇树形dp。 3 如果树是一条链,那么不便利程度的最小值0,二叉树也是0 4 所以只有三叉树及以上才会有不便利程度。 5 当然是三叉树的不便利程度最大(因为相同的节点,三叉树的深度更大)。 6 然后计算出三叉树的不便利程度最大是10; 7 而且每个点最多被一条铁路覆盖,所以每个根节点最多向儿子连两条边,(合为一条铁路) 8 于是状态可以表示为 f[N][j][k=0/1/2] 第i个节点,最大的不便利程度为j,向儿子连了0/1/2条边 9 分情况转移。 10 11 明白状态是怎么来的,为什么这样表示状态! 12 */ 13 #include<bits/stdc++.h> 14 using namespace std; 15 typedef long long LL; 16 17 inline int read() { 18 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 19 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 20 } 21 22 const int N = 100010; 23 24 int head[N],nxt[N<<1],to[N<<1],Enum; 25 LL f[N][11][3],mod; // 第i个节点,最大的不便利程度为j,向儿子连了0/1/2条边 26 27 void add_edge(int u,int v) { 28 ++Enum; to[Enum] = v; nxt[Enum] = head[u]; head[u] = Enum; 29 } 30 LL get(LL t) { 31 if (!t) return 0; 32 return t % mod ? t % mod : mod; // -- 33 } 34 void dfs(int u,int fa) { 35 for (int i=0; i<=10; ++i) f[u][i][0] = 1; 36 for (int i=head[u]; i; i=nxt[i]) { 37 int v = to[i]; 38 if (v == fa) continue; 39 dfs(v,u); 40 for (int j=0; j<=10; ++j) { 41 LL f0 = !j ? 0 : f[v][j-1][0] + f[v][j-1][1] + f[v][j-1][2]; // 不向这个儿子连边 42 LL f1 = f[v][j][0] + f[v][j][1]; // 向这个儿子连边 43 LL tmp = f[u][j][2] * f0 + f[u][j][1] * f1; f[u][j][2] = get(tmp); //原来2条*现在0条,原来1条*现在1条 44 tmp = f[u][j][1] * f0 + f[u][j][0] * f1; f[u][j][1] = get(tmp); //原来1条*现在0条,原来1条*现在1条 45 tmp = f[u][j][0] * f0; f[u][j][0] = get(tmp);//原来0条*现在0条 46 } 47 } 48 } 49 int main() { 50 int n = read(),m = read(); mod = read(); 51 for (int i=1; i<=m; ++i) { 52 int u = read(),v = read(); 53 add_edge(u,v);add_edge(v,u); 54 } 55 if (m != n - 1) { 56 puts("-1\n-1");return 0; // -- 57 } 58 dfs(1,0); 59 LL ans; 60 for (int i=0; i<=10; ++i) { 61 if (ans = f[1][i][0] + f[1][i][1] + f[1][i][2]) { 62 printf("%d\n%d",i,ans%mod); 63 break; 64 } 65 } 66 return 0; 67 }
bzoj 1812 [Ioi2005]riv
1 /* 2 树形背包,然后就不会了。。 3 首先转化成二叉树。没想到。 4 f[i][j][k] 表示以i为根的子树内,有j个伐木场,距离i最近的伐木场为k(k是i的祖先) 5 */ 6 #include<bits/stdc++.h> 7 using namespace std; 8 typedef long long LL; 9 10 inline int read() { 11 int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; 12 for (;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; 13 } 14 15 const int N = 205; 16 const int INF = 1e9; 17 int head[N],nxt[N<<1],to[N<<1],len[N<<1],Enum,dis[N]; 18 int f[N][N][N],w[N],tr[N],ls[N],rs[N]; 19 20 void add_edge(int u,int v,int w) { 21 ++Enum; to[Enum] = v; len[Enum] = w; nxt[Enum] = head[u]; head[u] = Enum; 22 } 23 void dfs(int u,int fa) { 24 for (int i=head[u]; i; i=nxt[i]) { 25 int v = to[i]; 26 if (v == fa) continue; 27 dis[v] = dis[u] + len[i]; 28 dfs(v,u); 29 } 30 } 31 int dp(int i,int j,int k) { 32 if (f[i][j][k] != -1) return f[i][j][k]; 33 f[i][j][k] = INF; 34 int res = 0; 35 for (int t=0; t<=j; ++t) { 36 res = 0; 37 if (ls[i]) res += dp(ls[i],t,k); // i不建伐木场 38 if (rs[i]) res += dp(rs[i],j-t,k); // i不建伐木场 39 f[i][j][k] = min(f[i][j][k], res+(dis[i]-dis[k])*w[i]); // 加上i到伐木场的花费。 40 if (t < j) { 41 res = 0; 42 if (ls[i]) res += dp(ls[i],t,i); // 建伐木场 43 if (rs[i]) res += dp(rs[i],j-t-1,k); // 除了左儿子,其他的都是i的兄弟节点,i建伐木场对其没有影响 44 f[i][j][k] = min(f[i][j][k],res); 45 } 46 } 47 return f[i][j][k]; 48 } 49 int main() { 50 int n = read(),m = read(); 51 memset(tr,-1,sizeof(tr)); 52 for (int i=1; i<=n; ++i) { 53 w[i] = read(); 54 int x = read(), len = read(); 55 if (tr[x] == -1) ls[x] = i,tr[x] = i; 56 else rs[tr[x]] = i,tr[x] = i; 57 add_edge(x,i,len); 58 } 59 dfs(0,0); 60 memset(f,-1,sizeof(f)); 61 printf("%d\n",dp(0,m,0)); 62 return 0; 63 }
COGS 2240 [HZOI 2016]架设电话线路
1 /* 2 首先可以O(n*max(h[i])^2)即O(n*100*100)的dp 3 f[i][j]表示到i,高度为j,然后从i-1转移,转移O(100*100) 4 考虑如何优化转移。 5 6 对于f[i][j],考虑f[i-1][k],假设k<j 7 那么f[i][j] = f[i-1][k] + c(j - k) + (j - h[i]) ^ 2 8 f[i][j] = f[i-1][k]- ck + cj + (j - h[i]) ^ 2 9 设设为wij = cj + (j - h[i]) ^ 2 10 f[i][j] = f[i-1][k]- ck + wij 11 wij只与i和j有关系,对于f[i][j]是固定的,所以现在只需要找到一个k,使得f[i-1][k]-ck最小就好了。 12 13 同理对于k>j的也要做一遍。 14 */ 15 #include<cstdio> 16 #include<algorithm> 17 #include<cstring> 18 #include<iostream> 19 #include<cctype> 20 using namespace std; 21 typedef long long LL; 22 23 inline int read() { 24 int x = 0, f = 1; char ch = getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f = -1; 25 for (; isdigit(ch); ch=getchar()) x = x * 10 + ch - '0'; return x * f; 26 } 27 28 int f[2][105], h[100100]; 29 30 int main() { 31 freopen("phonewire.in","r",stdin);freopen("phonewire.out","w",stdout); 32 int n = read(), c = read(); 33 for (int i=1; i<=n; ++i) h[i] = read(); 34 int cur = 1; 35 for (int i=h[1]; i<=100; ++i) f[cur][i] = (i - h[1]) * (i - h[1]); 36 for (int i=2; i<=n; ++i) { 37 cur ^= 1; 38 memset(f[cur], 0x3f, sizeof(f[cur])); 39 for (int res=1e9,j=0; j<=100; ++j) { 40 if (j >= h[i - 1]) res = min(res, f[cur ^ 1][j] - c * j); 41 if (j >= h[i]) f[cur][j] = min(f[cur][j], res + c * j + (j - h[i]) * (j - h[i])); 42 } 43 for (int res=1e9,j=100; j>=0; --j) { 44 if (j >= h[i - 1]) res = min(res, f[cur ^ 1][j] + c * j); 45 if (j >= h[i]) f[cur][j] = min(f[cur][j], res - c * j + (j - h[i]) * (j - h[i])); 46 } 47 } 48 int ans = 1e9; 49 for (int i=0; i<=100; ++i) ans = min(ans, f[cur][i]); 50 cout << ans; 51 return 0; 52 }
--------