树形DP入门学习

A - Anniversary party

Thinking:

dp[i][0]:表示不邀请i员工其子树达到的最大快乐值，dp[i][1]则表示邀请。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
#define mst(s, t) memset(s, t, sizeof(s))
const int INF = 0x3f3f3f3f;
const int maxn = 6010;
vector<int> G[maxn];
int father[maxn], dp[maxn][2];
void dfs(int root){
for(int i=0; i<G[root].size(); i++){
dfs(G[root][i]);
}
for(int i=0; i<G[root].size(); i++){
dp[root][0] += max(dp[G[root][i]][0], dp[G[root][i]][1]);
dp[root][1] += dp[G[root][i]][0];
}
}
int main()
{
freopen("in.txt", "r", stdin);
mst(dp, 0); mst(father, -1);
int n;
scanf("%d", &n);
for(int i=1; i<=n; i++){
scanf("%d", &dp[i][1]);
G[i].clear();
}
int fa, so;
while(scanf("%d%d", &so, &fa) && fa && so){
G[fa].push_back(so);
father[so] = fa;
}
int root = 1;
while(father[root] != -1)    root=father[root];
dfs(root);
printf("%d\n", max(dp[root][0], dp[root][1]));
return 0;
}


B - Strategic game

dp[root][0] += dp[G[root][i]][1];
dp[root][1] += min(dp[G[root][i]][0], dp[G[root][i]][1]);


#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
#define mst(s, t) memset(s, t, sizeof(s))
const int INF = 0x3f3f3f3f;
const int maxn = 1510;
int dp[maxn][2], father[maxn];
vector<int> G[maxn];
void dfs(int root){
for(int i=0; i<G[root].size(); i++){
dfs(G[root][i]);
}
for(int i=0; i<G[root].size(); i++){
dp[root][0] += dp[G[root][i]][1];
dp[root][1] += min(dp[G[root][i]][0], dp[G[root][i]][1]);
}
}
int main()
{
//freopen("in.txt", "r", stdin);
int n;
while( scanf("%d", &n) != EOF){
for(int i=0; i<=n; i++){
G[i].clear();
dp[i][1] = 1,  dp[i][0] = 0;
father[i] = -1;
}
for(int i=0; i<n; i++){
int root, node, cnt;
scanf("%d:(%d)",&root, &cnt);
for(int i=0; i<cnt; i++){
scanf("%d", &node);
G[root].push_back(node);
father[node] = root;
}
}
int root = 1;
while(father[root] != -1) root=father[root];
dfs(root);
printf("%d\n", min(dp[root][0], dp[root][1]));
}
return 0;
}


C - Tree Cutting

Thinking：

• 所有子树的结点数的最大值childmax
• 所有子树(这里包括node)的结点数之和sum。
递归过程中用上一层的sum，不断更新这一层的childmax。
而childmax和sum则共同用来判断这个node是否可以删除。
下面再分析判断条件： childmax<=n/2 && n-sum<=n/2
• childmax<=n/2 ：去掉node后，原先node的子树均满足条件。
• n-sum<=n/2 ：去掉node后，原先除node和node的所有子树外的树(就当是node的祖先树吧)均满足条件。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
#define mst(s, t) memset(s, t, sizeof(s))
const int INF = 0x3f3f3f3f;
const int maxn = 10010;
vector<int> G[maxn];
int ans[maxn], num, n;
int dfs(int node, int father){
int sum = 1, childmax = 0;   //若是叶子结点则return sum=1，否则求其子树(包括自己)的总结点数
for(int i=0; i<G[node].size(); i++){
if(G[node][i] == father)continue; //因为是树结构，这里可以在无向时避免遍历成环
int sum_son = dfs(G[node][i], node);
childmax = max(sum_son, childmax);//所有子树的结点数的最大值
sum += sum_son;//sum:node的子树的结点数和
}
childmax = max(childmax, n-sum);
if(childmax <= n/2){
/*
* 当node结点的孩子结点的结点数最大为Sum,若Sum<=n/2,则该点符合条件
* 因为去掉node后，任意子树结点数<=n/2, max()保证其非子树结点和仍<=n/2
* 故该点满足条件
*/
ans[num++] = node;
}
return sum;
}
int main()
{
//freopen("in.txt", "r", stdin);
scanf("%d", &n);
for(int i=0; i<n-1; i++){
int a, b;
scanf("%d%d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
num = 0;
int tmp = dfs(1, 0);
//cout << n << "==" << tmp << endl; //验证
sort(ans, ans+num);
if(num){
for(int i=0; i<num; i++){
printf("%d\n", ans[i]);
}
}else{
printf("NONE\n");
}
return 0;
}


D - Tree of Tree

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
#define mst(s, t) memset(s, t, sizeof(s))
const int INF = 0x3f3f3f3f;
const int maxn = 110;
vector<int> G[maxn];
int dp[maxn][maxn];  //dp[i][j]:node[i]结点数为j的子树的最大权值
int k, ans, cnt[maxn], weight[maxn];
int dfs(int node, int father){
cnt[node] = 1;
for(int i=0; i<G[node].size(); i++){
if(G[node][i] == father) continue;
cnt[node] += dfs(G[node][i], node);
}
dp[node][1] = weight[node];
//这里初始化不能在main()内  ？？
/*
*  dp[node][j-t]是之前的子节点为根更新的子树产生的
*  dp[v][t]是以当前子节点为根的子树产生的
*  j如果顺序遍历，前面dp[node][j]的更新会影响后面的dp[node][j-t]，导致后面
*更新dp[node][j]时是一当前子节点为根的子树产生的
*/
for(int i = 0; i < G[node].size(); i++){
int v = G[node][i];
for(int j = cnt[node]; j >= 1; j--){
for(int t = 0; t<j && t<=cnt[v]; t++){
dp[node][j] = max(dp[node][j], dp[node][j-t]+dp[v][t]);
}
}
}
ans = max(ans, dp[node][k]);
return cnt[node];
}
int main()
{
freopen("in.txt", "r", stdin);
int n;
while(scanf("%d%d",&n, &k) != EOF){
mst(dp, 0);  ans = 0;
for(int i=0; i<maxn; i++){
G[i].clear();
}
for(int i=0; i<n; i++){
scanf("%d", &weight[i]);
}
int a, b;
for(int i = 1; i < n; i++){
scanf("%d%d", &a, &b);
G[a].push_back(b);
G[b].push_back(a);
}
dfs(0, -1);
printf("%d\n", ans);
}
return 0;
}


E - Cell Phone Network

(这个概念叫最小支配集)

• dp[u][]：以点u为根的被染色的点的个数
• dp[u][0]:u不染色，父节点染色覆盖u min(1, 2)u不染色，不能覆盖子节点v，故要不v染色覆盖自己，要不v被v染色的子节点覆盖
• dp[u][1]:u不染色，子节点存在被染色的覆盖u min(1,2)u不染色，所以子节点v不存在被染色的父亲；若所有v均不染色，此时u未被覆盖，故需要有一个v来染色，选择min(dp[v][2]-dp[v][1])即可。
• dp[u][2]:u染色 min(0,1,2)+1 子节点v染不染色都可以；自己染色故需+1
/*
* poj3659
* 最小支配集:从所有顶点中取尽量少的点组成一个集合，
*            使剩下的所有点都与取出来的所有点相连。
* dp[u]:以点u为根的被染色的点的个数
*
* dp[u][0]:u不染色，父节点染色覆盖u
* dp[u][1]:u不染色，子节点存在被染色的覆盖u
* dp[u][2]:u染色
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 10010;
vector<int> G[maxn];
int dp[maxn][3], n;

void dfs(int u, int f){
//叶子结点
if(G[u].size()==1 && G[u][0]==f){
dp[u][0] = 0;
dp[u][1] = inf;
dp[u][2] = 1;
return;
}
int mini = inf, flag = 1;
for(int i=0; i<G[u].size(); i++){
int v = G[u][i];
if(v == f) continue;
dfs(v, u);
dp[u][0] += min(dp[v][1], dp[v][2]);
dp[u][2] += min(min(dp[v][0], dp[v][1]), dp[v][2]);
if(dp[v][1] < dp[v][2]){
dp[u][1] += dp[v][1];
mini = min(mini, (dp[v][2] - dp[v][1]));
}else{
flag = 0;
dp[u][1] += dp[v][2];
}

}
dp[u][2]++;   //u点需要染色
if(flag){
/*
* 如果所有子节点dp[v][1]<dp[v][2],则所有子节点点不放,这时必须有一个孩子结点放才可以保证
* u被覆盖
*/
dp[u][1] += mini;
}
}

int main(){
//freopen("in.txt", "r", stdin);
int n; scanf("%d", &n);
for(int i=0; i<=n; i++)G[i].clear();
for(int i=1; i<n; i++){
int a, b; scanf("%d%d", &a, &b);
G[a].push_back(b); G[b].push_back(a);
}
dfs(1, -1);
printf("%d\n", min(dp[1][1], dp[1][2]));
//1是根,无父节点
return 0;
}


F - Computer

1、dp：计算点v在树上的最远距离，通过dfs()寻找。v通过v的子树可以找到最远距离，v也可以通过v的父节点找到最远距离。通过子树找，向下遍历更新即可(即向下寻找)。通过父节点找，需要知道父节点的最远距离，父节点可以通过找自己的父节点获得最远距离(即一直向上寻找)，也可以通过寻找子树获得最远距离(但不能包含以v为根的子树(否则会重复))(即先向上后向下寻找)，这里就需要结点的次远距离(即该距离是不包含结点最远距离上的第一个结点的最远距离，故称为次远距离)。

/*
* hdu2196
*
*/
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn =  1e4+10;
struct edge{
int to, val;
edge(int a, int b) : to(a), val(b) {}
};
vector<edge> G[maxn];
int dp[3][maxn], id[maxn];
//树只有一个父亲，会有多个儿子
//id[v]: v在v的子树中可以得到的最大距离，所经过的第一个孩子结点
//dp[v][0]: v在v的所有子树中获得的最长距离
//dp[v][1]: v的孩子的第二长距离
//dp[v][2]: v通过父亲获得的最长距离
void dfs1(int x, int f){
for(int i=0; i<G[x].size(); i++){
int to = G[x][i].to, w = G[x][i].val;
if(to == f) continue;
dfs1(to, x);
if(dp[0][x] < dp[0][to] + w){
dp[0][x] = dp[0][to] + w;
id[x] = to;   //记录点x的最大距离经过的第一个孩子结点
}
}

for(int i=0; i<G[x].size(); i++){
int to = G[x][i].to, w = G[x][i].val;
if(to == f) continue;
if(id[x] == to) continue;  //找次大的
dp[1][x] = max(dp[1][x], w + dp[0][to]);
}
}
void dfs2(int x, int f){
for(int i=0; i<G[x].size(); i++){
int to = G[x][i].to, w = G[x][i].val;
if(to == f) continue;
if(to == id[x]){
dp[2][to] = max(dp[2][x], dp[1][x]) + w;
//to是x的孩子:to的最大距离是 x不经过to的最大距离(即次大距离)[向下的]和
//x向上的最大距离  的最大值 + dist(x,to) (画图理解)
//这里的转移也是dp[v][1]和id[x]存在的意义
}else{
dp[2][to] = max(dp[2][x], dp[0][x]) + w;
//to不是x最大距离经过的点
//则to的最大距离是dist(x,to)和x向上或向下的最大距离的最大值
}
dfs2(to, x);
//0和1子树的信息可以直接用,2也是步步更新,一直到最优
}
}
int main(){
//freopen("in.txt", "r", stdin);
int n;
while(scanf("%d", &n) != EOF){
memset(dp, 0, sizeof(dp));
for(int i=1;i<=n;i++)G[i].clear();
for(int i=2; i<=n; i++){
int a, b; scanf("%d%d", &a, &b);
G[i].push_back(edge(a, b));
G[a].push_back(edge(i, b));
}
dfs1(1, -1);
dfs2(1, -1);
for(int i=1; i<=n; i++){
printf("%d\n", max(dp[0][i], dp[2][i]));
}
}
return 0;
}


2、用树的直径求解：3次dfs()。前两次求树的直径，后两次求得所有点距离直径端点的最远距离。

/*
* 树中的最长路径，
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+10;
struct edge{
int to, val;
edge(int a, int b) : to(a), val(b) {}
};
vector<edge> G[maxn];
int dp[maxn], max_len, s;

void dfs(int x, int f, int len){
//len:起点到当前点的距离
if(max_len <= len){
s = x;
max_len = len;
}
for(int i=0; i<G[x].size(); i++){
int to = G[x][i].to, w = G[x][i].val;
if(f == to) continue;
dfs(to, x, len+w);
dp[to] = max(dp[to], len+w);
//更新起点到当前点的距离
}
}
int main(){
freopen("in.txt", "r", stdin);
int n;
while(scanf("%d", &n) != EOF){
memset(dp, 0, sizeof(dp));
for(int i=1;i<=n;i++) G[i].clear();
for(int i=2; i<=n; i++){
int a,b; scanf("%d%d",&a, &b);
G[i].push_back(edge(a, b));
G[a].push_back(edge(i, b));
}
s=0, max_len=0;
dfs(1, -1, 0);
dfs(s, -1, 0);
dfs(s, -1, 0);
for(int i=1; i<=n; i++){
printf("%d\n", dp[i]);
}
}
return 0;
}


hdu6446 Tree and Permutation

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
#define LL long long
const LL mod = 1e9+7;
struct edge{
int to, val;
edge(int a, int b) : to(a), val(b) {}
};
vector<edge> G[maxn];
LL d[maxn], w[maxn], node[maxn];
//node[x]: f-->x这条边在x一边的点的个数
//w[x]: f-->x这条边的权值
//d[i]: i!
void get_d(){
d[1] = 1;
for(int i=2; i<maxn; i++){
d[i] = (1LL * i * d[i-1]) % mod;
}
}

LL dfs(int x, int f){
LL ans = 1;
for(int i=0; i<G[x].size(); i++){
int v = G[x][i].to;
if(v == f){
w[x] = (LL)G[x][i].val;  //将边f---->x的权值存在当前结点w[x]
}else{
ans += dfs(v, x); //统计结点数
}
}
if(f!=-1 && G[x].size()==1){ //叶子结点
return node[x] = 1;   //叶子结点一边的点数为1
}
return node[x] = ans;
}
int main(){
//freopen("in.txt", "r", stdin);
get_d();
int n;
while(scanf("%d", &n) != EOF){
for(int i=0; i<=n; i++) G[i].clear();
for(int i=1; i<n; i++){
int x, y, z;  scanf("%d%d%d", &x, &y, &z);
G[x].push_back(edge(y, z));
G[y].push_back(edge(x, z));
}
dfs(1, -1);
LL ans = 0;
//ans = sum(2*x*(n-x)*(n-1)!*w[i]) = (n-1)!*sum(2*x*(n-x)*w[i])
for(int i=2; i<=n; i++){
ans  = (ans + ( ((2*node[i]*(n-node[i]))%mod) * w[i])%mod )%mod;
}
printf("%lld\n", (ans * d[n-1])%mod);
}
return 0;
}


UVA 10859 Placing Lampposts（训练指南P70）

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
vector<int> G[maxn];
int d[maxn][2], n, m;
//d[i][j]: i的父节点是否放灯的值为j;  d[i][j]:以i为根的最小x值
bool vis[maxn][2];
int dfs(int i, int j, int f){
int &ans = d[i][j];
if(vis[i][j]) return ans;
vis[i][j] = true;
ans = 2000;   //i结点放灯,权重很大
for(int k=0; k<G[i].size(); k++){
if(G[i][k] == f) continue;
ans += dfs(G[i][k], 1, i);
}
if(j==0 && f>=0){
//因为i的父节点没放灯，所以这是被一盏灯照亮
ans++;
}
if(j || f<0){        //i是根或i的父亲放灯,i可以不放灯
int sum = 0;
for(int k=0; k<G[i].size(); k++){
if(G[i][k] == f) continue;
sum += dfs(G[i][k], 0, i);
}
if(f >= 0) sum++;
ans = min(ans, sum);
}
return ans;
}

int main(){
//freopen("in.txt", "r", stdin);
int t;
scanf("%d", &t);
while(t--){
int n, m;
scanf("%d%d", &n, &m);
for(int i=0; i<=n; i++)G[i].clear();
for(int i=0; i<m; i++){
int x, y;
scanf("%d%d", &x, &y);
G[x].push_back(y);
G[y].push_back(x);
}
memset(vis, 0, sizeof(vis));
int ans = 0;
for(int i=0; i<n; i++){
if(!vis[i][0]){
ans += dfs(i, 0, -1);
}
}
printf("%d %d %d\n", ans/2000, m-ans%2000, ans%2000);
}
return 0;
}

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
const int M = 2000;
vector<int> G[maxn];
int dp[maxn][2];
bool vis[maxn];
void dfs(int x){
vis[x] = true;
dp[x][0] = 0; dp[x][1] = M;
for(int i=0; i<G[x].size(); i++){
int v = G[x][i];
if(vis[v]) continue;
dfs(v);
dp[x][0] += dp[v][1] + 1;
dp[x][1] += min(dp[v][1], dp[v][0]+1);
}
}
int main(){
//freopen("in.txt", "r", stdin);
int t; scanf("%d", &t);
while(t--){
int n, m; scanf("%d%d", &n, &m);
for(int i=0;i<=n;i++)G[i].clear();
for(int i=0; i<m; i++){
int a, b;scanf("%d%d", &a, &b);
G[a].push_back(b); G[b].push_back(a);
}
memset(vis, 0, sizeof(vis));
int ans = 0;
for(int i=0; i<n; i++){
if(vis[i]) continue;
dfs(i);
ans += min(dp[i][0], dp[i][1]);
}
printf("%d %d %d\n", ans/M, m-ans%M, ans%M);
}
return 0;
}

posted @ 2018-08-14 01:15  seaupnice  阅读(15230)  评论(0编辑  收藏  举报