树形DP简单得不能再简单的清理
大致模版:
void dp(int u,int fa)
{
f[][]= ;f[][]= ;
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa)continue;//防“倒吸”
dp(v,u);
balabalabala~~~~~~~;
}
}
T1传送门 :Lg longest || MZOJ Longest
难度★
T2传送门:Lg 战略游戏
难度★
T3:Lg P1352【没有上司的舞会】
难度★★
T1
求一颗树的直径
【T1】
法一:BFS、DFS来回两遍
从任意一点出发找最长A,再从A找最长;
和树形DP没有一丁点关系
#include <bits/stdc++.h>
using namespace std;
const int maxn=1005;
queue<int>q;
struct EDGE
{
int u,v,w,nxt;
}edge[maxn<<1];
int head[maxn],dist[maxn];
int size=0;
bool used[maxn];
void add(int u,int v,int w)
{
edge[size].u=u;
edge[size].v=v;
edge[size].w=w;
edge[size].nxt=head[u];
head[u]=size++;
}
int n;
int read()
{
int x=0;char ch=getchar();
while(ch>'9' || ch<'0')ch=getchar();
while(ch<='9' && ch>='0')
{
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
void init()
{
freopen("longest10.in","r",stdin);
freopen("longest10.out","w",stdout);
}
void readdata()
{
memset(head,-1,sizeof(head));
n=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read(),w=read();
add(u,v,w);
add(v,u,w);
}
}
void bfs()
{
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].v,w=edge[i].w;
if(!used[v])
{
q.push(v);
dist[v]=dist[u]+w;
used[v]=1;
}
}
}
}
void work()
{
memset(used,0,sizeof(used));
memset(dist,0,sizeof(dist));
q.push(1);used[1]=true;
bfs();
int maxd=0;
int flag=0;
for(int i=1;i<=n;i++)
if(dist[i]>maxd)
{
maxd=dist[i];
flag=i;
}
memset(used,0,sizeof(used));
memset(dist,0,sizeof(dist));
q.push(flag);used[flag]=true;
bfs();
maxd=0;
for(int i=1;i<=n;i++)
maxd=max(maxd,dist[i]);
printf("%d",maxd);
}
int main()
{
init();
readdata();
work();
return 0;
}
法二:树形DP
如何求树的直径?
状态设计:f[i][0]表示到i的最长路径。f[i][1]表示到i的第二长路径,那么易得 f[i][0]+ f[i][1]即为经过i的最长路径。
状态转移:如果在儿子中还有存在更优解(f[son1][0]>f[father][0] || f[son1][0]>f[father][1])更新father的状态。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000+5;
int size=0,ans=0;
int head[maxn],f[maxn][2];
struct EDGE
{
int u,v,w,nxt;
}edge[maxn<<1];
void add(int u,int v,int w)
{
edge[size].u=u;
edge[size].v=v;
edge[size].w=w;
edge[size].nxt=head[u];
head[u]=size++;
}
int n;
int read()
{
char ch=getchar();int x=0;
while(ch>'9' || ch<'0')ch=getchar();
while(ch<='9' && ch>='0')
{
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
void init()
{
freopen("Longest DP.in","r",stdin);
}
void readdata()
{
memset(head,-1,sizeof(head));
n=read();
for(int i=1;i<n;i++)
{
int u,v,w;
u=read();v=read();w=read();
add(u,v,w);
add(v,u,w);
}
}
void dp(int u,int fa)
{
f[u][0]=0;f[u][1]=0;
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].v,w=edge[i].w;
if(v==fa)continue;
dp(v,u);
int dis=f[v][0]+w;
if(dis>f[u][0])//两种情况:1.当儿子的值比fa最大值还要大时,
{
f[u][1]=f[u][0];//
f[u][0]=dis;//
}
else if(dis>f[u][1])//2.当儿子的值<f[fa][0] && >f[fa][1]
{
f[u][1]=dis;
}
ans=max(ans,f[u][0]+f[u][1]);//找最优解
}
}
void work()
{
dp(1,0);
printf("%d",ans);
}
int main()
{
//init();
readdata();
work();
return 0;
}
/**************************************************************
Problem: 1264
User: mzg1811
Language: C++
Result: 正确
Time:0 ms
Memory:1760 kb
****************************************************************/
【T2】
T2
覆盖一棵树的最小代价
如何将一颗树完全附带且代价最小?
状态设计:f[i][0]表示i这个点不放士兵所需的最小代价,f[i][1]表示 表示i这个点放士兵所需的最小代价
状态转移:画图可知
1.如果i不放士兵,那么他的sons必须放士兵
2.如果i放士兵,那么他的儿子可放可不放,求他们的最小值即可
#include <bits/stdc++.h>
using namespace std;
const int maxn=1500+5;
int n;
int f[maxn][2];
int size=0;
int head[maxn];
struct EDGE
{
int u,v,nxt;
}edge[maxn<<1];
void add(int u,int v)
{
edge[size].u=u;
edge[size].v=v;
edge[size].nxt=head[u];
head[u]=size++;
}
void dp(int u,int fa)
{
f[u][0]=0;f[u][1]=1;
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa)continue;
dp(v,u);
f[u][0]+=f[v][1];//必须放
f[u][1]+=min(f[v][0],f[v][1]);//可放可不放
}
}
void initdata()
{
memset(head,-1,sizeof(head));
memset(edge,0,sizeof(edge));
size=0;
memset(f,0,sizeof(f));
}
void init()
{
// freopen("Defense MZOJ1063.in","r",stdin);
}
void readdata()
{
while(scanf("%d",&n)!=EOF)
{
initdata();
for(int i=1;i<=n;i++)
{
int u,t;
scanf("%d%d",&u,&t);
for(int i=1;i<=t;i++)
{
int v;
scanf("%d",&v);
add(u,v);
add(v,u);
}
}
dp(0,-1);//随便从那个点开始
printf("%d\n",min(f[0][0],f[0][1]));//找最小
}
}
void work()
{
}
int main()
{
init();
readdata();
work();
return 0;
}
2019.1.19
11.01
更新一波
T3:Lg P1352【没有上司的舞会】
某大学有N个职员,编号为1~N。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
输入格式:
第一行一个整数N。(1<=N<=6000)
接下来N行,第i+1行表示i号职员的快乐指数Ri。(-128<=Ri<=127)
接下来N-1行,每行输入一对整数L,K。表示K是L的直接上司。
最后一行输入0 0
输出格式:
输出最大的快乐指数。
1.状态设计:f[i][0/1]表示i这个点 0去||1不去 参加的最大值
2.状态转移:如果上司去了,那么下属一定不能去:f[fa][1]=Σf[儿子][0]+happy[i];如果上司没有去,下属可去可不去f[fa][1]=Σmax(f[儿子][0],f[儿子][1])
#include <bits/stdc++.h>
using namespace std;
const int maxn=6005;
int n,head[maxn],size=0,happy[maxn],f[maxn][2],ans=0;
struct EDGE
{
int u,v,nxt;
}edge[maxn<<1];
void add(int u,int v)
{
edge[size].u=u;
edge[size].v=v;
edge[size].nxt=head[u];
head[u]=size++;
}
int read()
{
char ch=getchar();int x=0,flag=1;
while(ch<'0' || ch>'9')
{
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*flag;
}
void init()
{
freopen("Lg 1352.in","r",stdin);
}
void readdata()
{
memset(head,-1,sizeof(head));
n=read();
for(int i=1;i<=n;i++)
{
happy[i]=read();
}
for(int i=1;i<n;i++)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
}
void dp(int u,int fa)
{
f[u][0]=0;f[u][1]=happy[u];
for(int i=head[u];~i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa)continue;
dp(v,u);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
}
}
void work()
{
dp(1,1);
for(int i=1;i<=n;i++)
for(int j=0;j<=1;j++)
ans=max(ans,f[i][j]);
printf("%d",ans);
}
int main()
{
init();
readdata();
work();
return 0;
}
19.1.19
17.17