其实很早之前就学过树形dp,今天总接一下。树形dp就是一个在树上跑的dp(滑稽)
先是一道板子题:树上最大独立集
直接上代码了。
#include#include #include #include using namespace std; struct node { int x,y,next; }; node a[110005]; int len,last[110005]; void add(int x,int y) { a[++len].x = x; a[len].y = y; a[len].next = last[x]; last[x] = len; } int fa[11000],son[11000]; int f[11000][2]; int v[110000]; /* f[i][1]代表请i的最大值 f[i][0]代表不请i的最大值 */ template <class T> void read(T &x) { char c; int op = 0; while(c = getchar(),c > '9' || c < '0') if(c == '-') op = 1; x = c - '0'; while(c = getchar(),c >= '0' && c <= '9') x = x * 10 + c - '0'; if(op == 1) x = -x; } void treedp(int x) { f[x][1] = v[x]; for(int k = last[x];k;k = a[k].next) //相当于dfs treedp(a[k].y); for(int k = last[x];k;k = a[k].next) { int y = a[k].y; f[x][1] += f[y][0]; //dp转移式① } f[x][0] = 0; for(int k = last[x];k;k = a[k].next) { int y = a[k].y; f[x][0] += max(f[y][0],f[y][1]);//dp转移式② } } int main() { int n; read(n); memset(f,-1,sizeof(f)); memset(fa,0,sizeof(fa)); for(int i = 1;i <= n;i++) read(v[i]); int xx,yy;len = 0; memset(last,0,sizeof(last)); while(scanf("%d%d",&xx,&yy) != EOF) { if(xx == 0 && yy == 0) { break; } add(yy,xx); fa[xx] = yy; //找根节点 } int root = 0; for(int i = 1;i <= n;i++) if(fa[i] == 0) { root = i; break; } treedp(root); printf("%d\n",max(f[root][0],f[root][1])); return 0; }
然后还有几个稍微比这个难一点的题,比如:加分二叉树
【问题描述】 设一个有n个节点的二叉树的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。 每个节点都有一个分数(均为正整数),记第i个节点的分数为di, 每棵子树都有一个加分,任一棵子树subtree的加分计算方法如下: subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数 若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。 试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出; (1)tree的最高加分 (2)tree的前序遍历 【输入格式】 第1行:一个整数n(n<30),为节点个数。 第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。 【输出格式】 第1行:一个整数,为最高加分(结果不会超过2,0 0000 0000)。 第2行:n个用空格隔开的整数,为该树的前序遍历。 【样例输入】 5 5 7 1 2 10 【样例输出】 145 3 1 2 4 5
这个题需要枚举中间的断点,然后进行dp。dp比之前简单了,但是其他的要难一些。
#include#include using namespace std; int root[50][50],d[50],f[50][50]; void pre_visit(int l,int r) { if(l <= r) { cout< " "; pre_visit(l,root[l][r] - 1); pre_visit(root[l][r] + 1, r); } } int main() { int m; cin>>m; for(int i = 0;i <= m;i++) { for(int j = 0;j <= m;j++) { f[i][j] = 1; } } for(int i = 1;i <= m;i++) { cin>>d[i]; root[i][i] = i; f[i][i] = d[i]; } for(int k = 2;k <= m;k++) { for(int l = 1;l <= m - k + 1;l++) { int r = l + k - 1; for(int i = l;i <= r;i++) { if(f[l][r] < f[l][i - 1] * f[i + 1][r] + d[i]) { root[l][r] = i; f[l][r] = f[l][i - 1] * f[i + 1][r] + d[i]; } } } } cout< 1][m]<<endl; cout< 1][m]<<" "; pre_visit(1,root[1][m] - 1); pre_visit(root[1][m] + 1,m); }
还有一个皇宫看守,和最大独立点集很像
【问题描述】 太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫。 皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状;有边直接相连的宫殿可以互相望见。大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全天候看守,在不同的宫殿安排看守所需的费用不同。 可是陆小凤手上的经费不足,无论如何也没法在每个宫殿都安置留守侍卫。 编程任务:帮助陆小凤布置侍卫,在看守全部宫殿的前提下,使得花费的经费最少。 【输入格式】 输入文件中数据表示一棵树,描述如下: 第1行 n,表示树中结点的数目。 第2行至第n+1行,每行描述每个宫殿结点信息,依次为:该宫殿结点标号i(00 < n<=1500)个结点的树,结点标号在1到n之间,且标号不重复。 【输出格式】 输出文件仅包含一个数,为所求的最少的经费。 输入样例: 6 1 30 3 2 3 4 2 16 2 5 6 3 5 0 4 4 0 5 11 0 6 5 0 输出样例: 25
这个题很好想。
#include#include using namespace std; typedef long long ll; struct node { ll x,y,next; }; node a[110000]; ll v[100000],f[100000][5]; ll last[200000],len = 0; bool bk[50000]; /* i节点安全代表i节点和子树安全 不安全代表i节点不安全,但是子树安全 f[i][0]表示x点不放人,但是安全 f[i][1]表示x点不放人,不安全 f[i][2]表示x点放人,所以安全 f[i][3]表示x点放人,但是不安全,显然不成立 */ void treedp(int x) { f[x][2] = v[x]; f[x][1] = 0;f[x][0] = 0; ll minn = 2147364847; bool bkk = false; for(int k = last[x];k;k = a[k].next) { ll y = a[k].y; if(bk[y] == false) { bk[y] = true; treedp(y); minn = min(f[y][2] - f[y][0],minn); f[x][0] += min(f[y][0],f[y][2]); if(f[y][2] <= f[y][0]) { bkk = true; }//一定选f[i][2] f[x][1] += f[y][0]; f[x][2] += min(f[y][2],min(f[y][1],f[y][0])); } } if(bkk == false) { f[x][0] += minn; } } void add(int x,int y) { a[++len].x = x; a[len].y = y; a[len].next = last[x]; last[x] = len; } int main() { ll n; cin>>n; memset(f,0,sizeof(f)); for(int i = 1;i <= n;i++) { ll x,m,k; cin>>x>>k>>m; v[x] = k; for(int j = 1;j <= m;j++) { ll y; cin>>y; add(x,y); add(y,x); } } ll root = 1; memset(bk,false,sizeof(bk)); bk[root] = true; treedp(root); cout< 0],f[root][2]); return 0; }