这两道题是学长精心准备的,想了很长时间,比较经典。
第一题 树上染色
这一题一开始没思路主要是卡在了在从下向上转移中,如何将每个儿子的贡献值的算上
后来怂了题解QAQ
这题是树状背包问题
主要难点在于如何将每条边数加上,
设当前的节点为x而儿子节点为to
那么我们可以发现当前数组f[x][j]中包含的是x之前的儿子所做的贡献
这是树状背包常考虑的事情那么我们为了求当前的to所做的贡献
只需考虑当前to与x之间的边所做的贡献
(至于证明,显然此时f[x][j+k]=max(f[x][j+k],f[x][j]+f[to][k]+........)中f[x][j]是已转移过的,而此时需要将新加子树算进,这样枚举完x的子树后就是正确的。)
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <string> 5 #include <cmath> 6 #include <queue> 7 #include <stack> 8 #include <vector> 9 #define MAXN 5001 10 #define pt printf("----------- "); 11 #define push_back ps 12 #define ll long long 13 using namespace std; 14 struct node { 15 ll to, n, w; 16 } e[MAXN]; 17 ll head[MAXN], tot; 18 void add(ll u, ll v, ll w) { 19 e[++tot].to = v; 20 e[tot].n = head[u]; 21 e[tot].w = w; 22 head[u] = tot; 23 } 24 ll f[MAXN][MAXN]; 25 ll size[MAXN]; 26 ll n, K; 27 bool bian[MAXN]; 28 void DFS(ll x) { 29 bian[x] = 1; 30 size[x] = 1; 31 f[x][0]=f[x][1]=0; 32 for (ll i = head[x]; i; i = e[i].n) { 33 ll to = e[i].to; 34 if (bian[to] == 1) 35 continue; 36 bian[to] = 1; 37 DFS(to); 38 size[x] += size[to]; 39 for (ll j = min(K, size[x]); j >= 0; --j) { 40 for (ll k = 0; k <= j; ++k) { 41 if (k > size[to]) 42 break; 43 f[x][j] = 44 max(f[x][j], f[x][j - k] + f[to][k] + 45 e[i].w * (k * (K - k) + (size[to] - k) * (n - K - (size[to] - k)))); 46 // printf("size[%lld]=%lld size[%lld]=%lld 黑zi=%lld 黑qi=%lld 白zi=%lld 47 // 白qi=%lld ",to,size[to],x,size[x],k,K-k,size[to]-k,n-K-(size[to]-k)); 48 // printf("---f[%lld][%lld]=%lld f[%lld][%lld]=%lld ",x,j-k,f[x][j-k],to,k,f[to][k]); 49 } 50 // printf("f[%lld][%lld]=%lld ",x,j,f[x][j]); 51 } 52 } 53 } 54 ll root = 1; 55 ll ru[MAXN]; 56 int main() { 57 memset(f,-0x3f,sizeof(f)); 58 scanf("%lld%lld", &n, &K); 59 for (ll i = 1; i <= n - 1; ++i) { 60 ll x, y, w; 61 scanf("%lld%lld%lld", &x, &y, &w); 62 add(x, y, w); 63 add(y, x, w); 64 } 65 // DFS_S(root); 66 memset(bian, 0, sizeof(bian)); 67 DFS(root); 68 ll ans = 0; 69 ans = max(ans, f[root][K]); 70 printf("%lld ", ans); 71 }
第二题
一道不错的树状DP
有几个限制条件n个物品,各有价值,各有优惠券,除一外都有一个使用的条件,问钱b买多少商品。
这题我们不直接求结果,用f[x][y][2],表示第x点买y个物品的最小值,1表示用券,0则反。
那么最后一位是1时比较好处理
因为x必选,所以f[x][j+k][1]=min(f[x][j+k][1],f[x][j][1]+min(f[to][k][1],f[to][k][0]);
可以提前给f[x][1][1]加上c[x]-d[x],这样转移方程中就不会加重。
而为0时,f[x][j+k][0]=min(f[x][j+k][0],f[x][j][0]+f[to][k][0])
因为x可能选
最后再用for从1~size[x]用f[x][i-1]+c[i]更新f[x][i]最小值
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<algorithm> 6 #include<cmath> 7 using namespace std; 8 #define MAXN 6000 9 int head[MAXN],tot;struct node{int to,n;}e[MAXN]; 10 void add(int u,int v){e[++tot].to=v;e[tot].n=head[u];head[u]=tot;} 11 int f[MAXN][MAXN][2];int n,c[MAXN],d[MAXN]; 12 int deep=1; 13 int size[MAXN]; 14 void DFS(int x) 15 { 16 size[x]=1; 17 f[x][1][1]=c[x]-d[x];f[x][0][0]=0; 18 for(int i=head[x];i;i=e[i].n) 19 { 20 int to=e[i].to; 21 DFS(to); 22 // printf("---------%d ",to); 23 // printf("deep=%d ",deep); 24 for(int j=size[x];j>=0;--j) 25 { 26 for(int k=size[to];k>=0;--k) 27 { 28 f[x][j+k][1]=min(f[x][j+k][1],f[x][j][1]+min(f[to][k][1],f[to][k][0])); 29 f[x][j+k][0]=min(f[x][j+k][0],f[x][j][0]+f[to][k][0]); 30 // printf("f[%d][%d][1]=%d f[%d][%d][0]=%d ",x,j,f[x][j+k][1],x,j,f[x][j+k][0]); 31 } 32 } 33 size[x]+=size[to]; 34 } 35 for(int i=size[x];i>=1;--i) 36 { 37 f[x][i][0]=min(f[x][i][0],f[x][i-1][0]+c[x]); 38 } 39 } 40 int b; 41 int main() 42 { 43 scanf("%d%d",&n,&b); 44 memset(f,0x3f,sizeof(f)); 45 for(int i=1;i<=n;++i) 46 { 47 int x; 48 if(i==1) 49 scanf("%d%d",&c[i],&d[i]); 50 else 51 { 52 scanf("%d%d%d",&c[i],&d[i],&x); 53 add(x,i); 54 } 55 f[i][1][1]=c[i]-d[i]; 56 } 57 int root=1; 58 DFS(root); 59 int x; 60 for(x=1;x<=n;++x) 61 { 62 if(f[1][x][1]>b&&f[1][x][0]>b)break; 63 } 64 printf("%d ",x-1); 65 }
树状DP注意点:
1.首先树状DP大部分情况都是逆推,用根节点统计答案。
2.在做树状背包时一定要注意不能单纯用f[to]更新f[x],因为这样枚举不全,只考虑了一棵子树
所以一般两层循环枚举第二维来更新
3注意倒序,不要重复更新。
最后感谢kx解决我做题的傻逼错误。