树形DP
树形DP
算法思路
总而言之,我认为树形\(DP\)和线性\(DP\)没什么大区别,唯一不同的是它要用树储存,且要从叶向根进行更新。其它没什么技巧性的东西,只要推出式子往往就能过,没什么好说的。
题目
P1352/qzez1697 [模板]没有上司的舞会
这是一道树形DP模板题.
思路:设 \(dp_{i,0}\) 表示以 \(i\) 为根的子树在 \(i\) 不去时的最大值, \(dp_{i,1}\) 表示以 \(i\) 为根的子树在 \(i\) 要去时的最大值。
显然 \(dp_{i,0} = \sum\limits_{j \in i}{\max (f_ {j,0},f_ {j,1}) }\),\(dp_{i,1} = \sum\limits_{j \in i}(f_{j,0})\)。
不难理解,如果 \(i\) 不去,那么就等于它的子树的去或不去的最大值之和;如果要去,那么它的子树的根一定不能去,故等于它的子树不去的和。
#include<iostream>
#include<cstdio>
#define maxn 6005
using namespace std;
int n,x,y;
int a[maxn],f[maxn][2],inde[maxn];
struct node{
int to,nex;
}g[maxn];
int head[maxn],w=0;
void add(int from,int to){
g[++w].to=to;
g[w].nex=head[from];
head[from]=w;
}
void dp(int x){
if(head[x]==0){
f[x][1]=a[x];
return;
}
f[x][1]=a[x];
for(int i=head[x];i!=0;i=g[i].nex){
dp(g[i].to);
f[x][0]+=max(f[g[i].to][0],f[g[i].to][1]);
f[x][1]+=f[g[i].to][0];
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(y,x);
inde[x]++;
}
for(int i=1;i<=n;i++){
if(inde[i]==0){
dp(i);
printf("%d",max(f[i][1],f[i][0]));
break;
}
}
return 0;
}
poj1985 Cow Marathon
树的直径
先从任意一个点\(A\)开始,\(DFS\)找到离他最远的点\(B\),再从点\(B\)开始,再\(DFS\)找到离他最远的点\(C\),\(BC\)即为树的直径。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 200005
using namespace std;
int n,m,x,y,dis;
char xx;
int head[maxn],w=0;
int fp,fd=-1;
struct node{
int to,nex,dis;
}a[maxn*2];
void add(int from,int to,int dis){
a[++w].to=to;
a[w].dis=dis;
a[w].nex=head[from];
head[from]=w;
}
void dfs(int p,int fa,int tot){
bool flag=0;
for(int i=head[p];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
flag=1;
dfs(a[i].to,p,tot+a[i].dis);
}
if(!flag){
if(tot>fd){
fd=tot;
fp=p;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d %c",&x,&y,&dis,&xx);
add(x,y,dis);
add(y,x,dis);
}
fp=0;
fd=-1;
dfs(1,-1,0);
dfs(fp,-1,0);
printf("%d",fd);
return 0;
}
重心
poj1655 Balancing Act
树的重心
先计算每棵子树的大小,再计算把某节点去掉后剩下的树的大小的最大值\(bal_i\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 20005
using namespace std;
int t;
int n,x,y;
int head[maxn],w=0;
struct node{
int to,nex;
}a[maxn*2];
void add(int from,int to){
a[++w].to=to;
a[w].nex=head[from];
head[from]=w;
}
int size[maxn],bal[maxn];
void search(int x,int fa){
size[x]=1;
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
search(a[i].to,x);
size[x]+=size[a[i].to];
}
}
void searchb(int x,int fa){
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
searchb(a[i].to,x);
}
bal[x]=n-size[x];
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
bal[x]=max(bal[x],size[a[i].to]);
}
}
void set(){
memset(a,0,sizeof(a));
memset(head,0,sizeof(head));
w=0;
memset(size,0,sizeof(size));
memset(bal,0,sizeof(bal));
}
int main(){
scanf("%d",&t);
while(t--){
set();
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
search(1,-1);
searchb(1,-1);
int mini=1;
for(int i=2;i<=n;i++){
mini=bal[i]<bal[mini]?i:mini;
}
printf("%d %d\n",mini,bal[mini]);
}
return 0;
}
/*
1
6
1 2
1 3
2 4
2 5
5 6
*/
poj3107 Godfather
还是找树的重心
思路同上,不过这题要输出所有重心。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 50005
using namespace std;
int t;
int n,x,y;
int head[maxn],w=0;
struct node{
int to,nex;
}a[maxn*2];
void add(int from,int to){
a[++w].to=to;
a[w].nex=head[from];
head[from]=w;
}
int size[maxn],bal[maxn];
void search(int x,int fa){
size[x]=1;
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
search(a[i].to,x);
size[x]+=size[a[i].to];
}
}
void searchb(int x,int fa){
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
searchb(a[i].to,x);
}
bal[x]=n-size[x];
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
bal[x]=max(bal[x],size[a[i].to]);
}
}
void set(){
memset(a,0,sizeof(a));
memset(head,0,sizeof(head));
w=0;
memset(size,0,sizeof(size));
memset(bal,0,sizeof(bal));
}
int main(){
set();
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
search(1,-1);
searchb(1,-1);
int minn=bal[1];
for(int i=2;i<=n;i++){
minn=bal[i]<minn?bal[i]:minn;
}
for(int i=1;i<=n;i++){
if(bal[i]==minn){
printf("%d ",i);
}
}
return 0;
}
poj2378 Tree Cutting
去除一个点,使剩下的每个连通子图中点的数量都不超过\(n/2\)
本质上,还是用找树的重心一样的方法。因为我们已经计算出了去掉每个点后剩下的图中的最大节点数,存在\(bal_i\)中。所以我们只要找所有的\(bal_i\)中,如果\(bal_i < (n/2)\),那么输出,注意可能有多个解。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 50005
using namespace std;
int t;
int n,x,y;
int head[maxn],w=0;
struct node{
int to,nex;
}a[maxn*2];
void add(int from,int to){
a[++w].to=to;
a[w].nex=head[from];
head[from]=w;
}
int size[maxn],bal[maxn];
void search(int x,int fa){
size[x]=1;
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
search(a[i].to,x);
size[x]+=size[a[i].to];
}
}
void searchb(int x,int fa){
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
searchb(a[i].to,x);
}
bal[x]=n-size[x];
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
bal[x]=max(bal[x],size[a[i].to]);
}
}
void set(){
memset(a,0,sizeof(a));
memset(head,0,sizeof(head));
w=0;
memset(size,0,sizeof(size));
memset(bal,0,sizeof(bal));
}
int main(){
set();
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
search(1,-1);
searchb(1,-1);
for(int i=1;i<=n;i++){
if(bal[i]<=n/2){
printf("%d\n",i);
}
}
return 0;
}
poj3140 Contestants Division
求删去某条边后,剩下两棵子树的节点权值之和的差的绝对值最小
这道题打了我一个多小时。
思路其实很简单,就是将任意一个作为根(我用的是1),之后算出每棵子树的点权和。再枚举每条边,计算删掉后的两棵子树的节点权值之和的差的绝对值,计算输出即可。
细节很多:
- 一定要开\(long long\);
- \(abs\)要手写;
- 数组记得初始化;
- 开始时\(ans\)一定要赋值成一个很\(\color{red}大\)的值 。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 1000005
using namespace std;
long long n,m,x,y,tot;
long long p[maxn],head[maxn],w;
long long f[maxn];
struct node{
long long to,nex;
}a[maxn*2];
void add(long long from,long long to){
a[++w].to=to;
a[w].nex=head[from];
head[from]=w;
}
void set(){
w=0;
tot=0;
memset(p,0,sizeof(p));
memset(head,0,sizeof(head));
memset(a,0,sizeof(a));
memset(f,0,sizeof(f));
}
void search(long long x,long long fa){
f[x]=p[x];
for(long long i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
search(a[i].to,x);
f[x]+=f[a[i].to];
}
}
long long abs(long long x){
if(x<0){
return -x;
}else{
return x;
}
}
long long findans(){
long long ans=1000000000000;//一定要很大!!!
for(long long i=1;i<=n;i++){
for(long long j=head[i];j!=0;j=a[j].nex){
ans=min(ans,abs(f[a[j].to]-(tot-f[a[j].to])));
}
}
return ans;
}
int main(){
long long xx=0;
while(1){
set();
xx++;
scanf("%lld%lld",&n,&m);
if(n==0&&m==0){
return 0;
}
for(long long i=1;i<=n;i++){
scanf("%lld",&p[i]);
tot+=p[i];
}
for(long long i=1;i<=m;i++){
scanf("%lld%lld",&x,&y);
add(x,y);
add(y,x);
}
search(1,-1);
printf("Case %lld: %lld\n",xx,findans());
}
return 0;
}
/*
7 6
1 1 1 1 1 1 1
1 2
2 7
3 7
4 6
6 2
5 7
4 3
1 6 3 10
1 2
2 4
1 3
*/
qzez1161 【TEST 3】旅行
无描述
大意
从\(1\)号节点开始遍历,不走回头路,在每一条可行的路上都选最多\(k\)个节点,和为\(s_i\),求\(max(s_i)\)。(描述较抽象,看题目即可)
思路
设\(f_{i,j}\)为以\(i\)为根的子树,选取\(j\)个节点的最小值,显然最终答案为\(\max\limits_{j=1}^{k}{f_{1,j}}\)。
下面推状态转移式,对于\(i\)节点来说,如果它要选,那么\(f_{i,j}=\max\limits_{k\in i}{f_{k,j}}\),因为不去它,就等于是它的子树的最大值。如果要去,显然让它的子树的第二维减\(1\)即可,就是少去一个城市而已,即\(f_{i,j}=\max\limits_{k\in i}{f_{k,j-1}+a_i}\)。稍加整理,得到\(f_{i,j}=\max (f_{i,j},\max (\max\limits_{k\in i}{f_{k,j}},\max\limits_{k\in i}{f_{k,j-1}+a_i}))\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 10005
#define maxk 1005
using namespace std;
int n,k,x,y;
long long a[maxn],f[maxn][maxk];
int w=0,head[maxn];
struct gragh{
int to,nex;
}g[maxn*2];
void add(int x,int y){
g[++w].to=y;
g[w].nex=head[x];
head[x]=w;
}
void search(int x,int fa){
f[x][0]=0;
bool flag=0;
for(int i=head[x];i!=0;i=g[i].nex){
if(g[i].to==fa){
continue;
}
flag=1;
search(g[i].to,x);
for(int j=1;j<=k;j++){
f[x][j]=max(f[x][j],max(f[g[i].to][j],f[g[i].to][j-1]+a[x]));
}
}
if(!flag){//叶子
f[x][1]=a[x];
return;
}
}
int main(){
memset(f,128,sizeof(f));
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
search(1,-1);
long long ans=f[1][k];
for(int i=1;i<=k;i++){
ans=max(ans,f[1][i]);
}
printf("%lld",ans);
return 0;
}
/*
5 2
2 4 1 3 2
1 2
1 3
3 4
1 5
*/
换根
qzez1698 Accumulation Degree
一道换根模板题
首先,初始化一遍,计算出以1(或其它任意点)为起点的最大流量。显然此时 \(f_i = \sum\limits_{j \in i}{\min (f_j,w_{i,j})}\)。特殊地,若\(j\)为叶子节点,\(f_i\)应直接加上\(w_{i,j}\)。
然后进行换根。最终答案 \(g_i = f_i+\min((g_{fa}-\min(f_i,w_{fa,i})),w_{fa,i})\)(\(fa\)为\(i\)父亲,其中\(g_{fa}-\min(f_i,w_{fa,i})\)是\(g_{fa}\)减去\(i\)子树后剩下的最大流量)。特殊地,若\(fa\)的度数为1,\(g_i\)应直接等于\(f_i+w_{fa,i}\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 200005
using namespace std;
int t;
int n,x,y,z;
int head[maxn],w=0,de[maxn];
struct node{
int to,len,nex;
}a[maxn*2];
int f[maxn],g[maxn];
void set(){
memset(a,0,sizeof(a));
memset(head,0,sizeof(head));
w=0;
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
memset(de,0,sizeof(de));
}
void add(int from,int to,int len){
a[++w].to=to;
a[w].len=len;
a[w].nex=head[from];
head[from]=w;
}
void flow(int x,int fa){
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
if(de[a[i].to]==1){
f[x]+=a[i].len;
}else{
flow(a[i].to,x);
f[x]+=min(f[a[i].to],a[i].len);
}
}
}
void search(int x,int fa){
for(int i=head[x];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
if(de[x]==1){
g[a[i].to]=f[a[i].to]+a[i].len;
}else{
g[a[i].to]=f[a[i].to]+min((g[x]-min(f[a[i].to],a[i].len)),a[i].len);
}
search(a[i].to,x);
}
return;
}
int main(){
scanf("%d",&t);
while(t--){
set();
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
de[x]++;
de[y]++;
}
flow(1,-1);
// printf("%d",f[1]);
g[1]=f[1];
search(1,-1);
int maxx=g[1];
for(int i=2;i<=n;i++){
maxx=max(maxx,g[i]);
}
printf("%d\n",maxx);
}
return 0;
}
/*
1
5
1 2 3
2 3 4
2 4 5
2 5 5
*/
P2986 Great Cow Gathering
又是换根的模板题
先算出到\(1\)的方便值,后换根即可。
#include<iostream>
#include<cstdio>
#define maxn 100005
#define ll long long
using namespace std;
int n,x,y,z;
ll num[maxn],size[maxn],f[maxn],ans[maxn];
int head[maxn],w=0;
struct node{
int nex,to,dis;
}a[maxn*2];
void add(int from,int to,int dis){
a[++w].to=to;
a[w].dis=dis;
a[w].nex=head[from];
head[from]=w;
}
void ssize(int u,int fa){
for(int i=head[u];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
ssize(a[i].to,u);
}
size[u]=num[u];
for(int i=head[u];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
size[u]+=size[a[i].to];
}
}
void search(int u,int fa){
for(int i=head[u];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
search(a[i].to,u);
}
f[u]=0;
for(int i=head[u];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
f[u]+=f[a[i].to]+size[a[i].to]*a[i].dis;
}
}
void change(int u,int fa,int dis){
ans[u]=ans[fa]-size[u]*dis+(size[1]-size[u])*dis;
for(int i=head[u];i!=0;i=a[i].nex){
if(a[i].to==fa){
continue;
}
change(a[i].to,u,a[i].dis);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
}
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
ssize(1,-1);
search(1,-1);
ans[1]=f[1];
for(int i=head[1];i!=0;i=a[i].nex){
change(a[i].to,1,a[i].dis);
}
ll anss=ans[1];
for(int i=2;i<=n;i++){
anss=min(anss,ans[i]);
}
printf("%lld",anss);
return 0;
}
/*
5
1
3
1
5
2
1 3 1
2 3 2
3 4 3
4 5 3
*/

浙公网安备 33010602011771号