链接:P1122 最大子树和
算法分析
典型的树形DP,要结合贪心的思想。f[x]存储以x为根可以得到的最大美丽值,若子树的美丽值小于零,即对结果有害,应减去。
状态转移方程: f [ x ] + = f [ y ] > 0 ? f [ y ] : 0 f[x]+=f[y]>0?f[y]:0 f[x]+=f[y]>0?f[y]:0
Code:
#include
using namespace std;
inline int Read(){
int dx=0,fh=1;
char c;
c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fh=-1;
c=getchar();
}
while(c<='9'&&c>='0'){
dx=dx*10+c-'0';
c=getchar();
}
return dx*fh;
}
struct node{
int to,next;
}edg[170000];
int n,beauty[170000],h[170000],cnt,ans,f[170000];
void add(int a,int b){
++cnt;
edg[cnt].to=b;
edg[cnt].next=h[a];
h[a]=cnt;
}
void dfs(int u){
f[u]=beauty[u];
for(int i=h[u];i;i=edg[i].next){
int v=edg[i].to;
dfs(v);
if(f[v]>0) f[u]+=f[v];
}
ans=max(ans,f[u]);
}
int main(){
n=Read();
for(int i=1;i<=n;++i) beauty[i]=Read();
for(int i=1;i<=n-1;++i){
int aa,bb;
aa=Read(),bb=Read();
add(aa,bb);add(bb,aa);
}
dfs(1);
printf("%d",ans);
return 0;
}
总结与反思
链接:P2014 [CTSC1997]选课
算法分析:
树形分类背包DP。每个先修课都是后驱课的父节点,整个图即为一个森林,每个根节点可以连接到一个虚拟的价值为0的总先驱节点,由于先驱节点占空间,背包容量要加一。设 f [ x ] [ y ] f[x][y] f[x][y]为以 x x x为根,占用 y y y个空间的最大学分,然后利用多重背包来解即可。
Code:
#include
using namespace std;
inline int Read(){
int dx=0,fh=1;
char c;
c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fh=-1;
c=getchar();
}
while(c<='9'&&c>='0'){
dx=dx*10+c-'0';
c=getchar();
}
return dx*fh;
}
struct node{
int to,next;
}edg[17000];
int n,m,h[1700],score[1000],cnt,ans,f[17000][500];
void add(int a,int b){
++cnt;
edg[cnt].to=b;
edg[cnt].next=h[a];
h[a]=cnt;
}
void dfs(int u){
f[u][1]=score[u];
int flag=1;
for(int i=h[u];i;i=edg[i].next){
flag=0;
int v=edg[i].to;
dfs(v);
for(int j=m;j>=1;--j)
for(int k=1;k<j;++k)
f[u][j]=max(f[u][j],f[v][k]+f[u][j-k]);
}
if(flag==1)
for(int i=2;i<=m+1;++i)
f[u][i]=f[u][i-1];
}
int main(){
n=Read(),m=Read();
for(int i=1;i<=n;++i){
int pre;
pre=Read();
score[i]=Read();
add(pre,i);
}
++m;
dfs(0);
printf("%d",f[0][m]);
return 0;
}
总结与反思
链接:287. 积蓄程度
算法分析:
该数是一个无根树,可以以任意一点为根,因此一个最朴素的思想就是以单个节点为根,打一遍DP。
但是…时间复杂度感人。因此,要找一种更为巧妙的方式。
设 c [ i ] [ j ] c[i][j] c[i][j]为 i i i与 j j j之间的容量,先以任意一点(设为root)为根,用 d [ x ] d[x] d[x]存储 x x x节点对于它的子树的最大流量。易得状态转移方程:
d [ x ] = Σ m i n ( d [ y ] + c [ x ] [ y ] ) d[x]=\Sigma min(d[y]+c[x][y]) d[x]=Σmin(d[y]+c[x][y])
一遍dfs就能把所有的 d d d求出来了。然后设 f [ x ] f[x] f[x]为以 x x x为源的整个树的流量。显然, f [ r o o t ] = d [ r o o t ] f[root]=d[root] f[root]=d[root]。
由于知道的是根,因此要从上往下递推,若已知 f [ u ] f[u] f[u],则 f [ v ] f[v] f[v]可分为两部分, f [ v ] f[v] f[v]的子树( d [ v ] d[v] d[v])以及整棵树除了 v v v的子树以外的部分。
先看特殊情况: u u u点的度为1,则以 u u u为根时,水流全部入 v v v点,易得状态转移方程: f [ v ] = d [ v ] + c [ i ] [ j ] f[v]=d[v]+c[i][j] f[v]=d[v]+c[i][j]
我们知道以 u u u为根时, u u u-> v v v的流量 = m i n ( d [ v ] , c [ u ] [ v ] ) =min(d[v],c[u][v]) =min(d[v],c[u][v])。所以 u u u流向其他地方的流量为 f [ v ] − m i n ( d [ v ] , c [ u ] [ v ] ) f[v]-min(d[v],c[u][v]) f[v]−min(d[v],c[u][v]),即为以 v v v根时, u u u子树的最大承载度:
不难得出状态转移方程: f [ v ] = d [ v ] + m i n ( f [ v ] − m i n [ d [ v ] , c [ u ] [ v ] , c [ u ] [ v ] ) f[v]=d[v]+min(f[v]-min[d[v],c[u][v],c[u][v]) f[v]=d[v]+min(f[v]−min[d[v],c[u][v],c[u][v])
然后码就完了。
Code:
#include
#pragma GCC optimize(2)
using namespace std;
const int INF=2147483647;
inline int Read(){
int dx=0,fh=1;
char c;
c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fh=-1;
c=getchar();
}
while(c<='9'&&c>='0'){
dx=dx*10+c-'0';
c=getchar();
}
return dx*fh;
}
struct node{
int to,next,val;
}edg[300000];
int n,h[300000],cnt,ans,f[300000],T,d[300000],dgr[300000];
void add(int a,int b,int val){
++cnt;
edg[cnt].to=b;
edg[cnt].next=h[a];
edg[cnt].val=val;
h[a]=cnt;
}
void dfs1(int fa,int u){
if(dgr[u]==1&&fa==edg[h[u]].to) d[u]=INF;
for(int i=h[u];i;i=edg[i].next){
int v=edg[i].to;
if(v==fa) continue;
dfs1(u,v);
d[u]+=min(d[v],edg[i].val);
}
}
void dfs(int fa,int u){
for(int i=h[u];i;i=edg[i].next){
int v=edg[i].to;
if(v==fa) continue;
if(dgr[u]==1) f[v]=edg[i].val;
else f[v]=min(edg[i].val,f[u]-min(edg[i].val,d[v]));
if(dgr[v]>1) f[v]+=d[v];
dfs(u,v);
}
ans=max(ans,f[u]);
}
void Init(){
memset(edg,0,sizeof(edg));
memset(h,0,sizeof(h));
memset(f,0,sizeof(f));
memset(d,0,sizeof(d));
memset(dgr,0,sizeof(dgr));
n=0;cnt=0;ans=0;
}
int main(){
T=Read();
for(int TT=1;TT<=T;++TT){
Init();
n=Read();
for(int i=1;i<n;++i){
int aa,bb,cc;
aa=Read();bb=Read();cc=Read();
++dgr[aa];++dgr[bb];
add(aa,bb,cc);
add(bb,aa,cc);
}
dfs1(0,1);
f[1]=d[1];
dfs(0,1);
printf("%d\n",ans);
}
return 0;
}
总结与反思:
链接:P2015 二叉苹果树
算法分析:
先把整棵树的基本信息弄明白,遍历一遍,求出每个节点的左子树 l [ ] l[] l[]与右子树 r [ ] r[] r[]。题目中说保留 m m m条边,相当于保留 m + 1 m+1 m+1个节点。每个边上的苹果可以转移到该边连接的儿子上面。然后设 f [ u ] [ i ] f[u][i] f[u][i]为以 u u u为根的子树占用 i i i个节点可获得的最大苹果树。
目标: f [ 1 ] [ m + 1 ] f[1][m+1] f[1][m+1]
边界条件:
可推出状态转移方程:
f [ u ] [ i ] = m a x ( f [ l [ u ] ] [ k ] + f [ r [ u ] ] [ j − k − 1 ] + a p [ u ] ) f[u][i]=max(f[l[u]][k]+f[r[u]][j-k-1]+ap[u]) f[u][i]=max(f[l[u]][k]+f[r[u]][j−k−1]+ap[u])
Code:
#include
using namespace std;
inline int Read(){
int dx=0,fh=1;
char c;
c=getchar();
while(c<'0'||c>'9'){
if(c=='-') fh=-1;
c=getchar();
}
while(c<='9'&&c>='0'){
dx=dx*10+c-'0';
c=getchar();
}
return dx*fh;
}
struct node{
int to,next,val;
}edg[3000];
int n,h[3000],cnt,ans,f[1000][1006],m,l[1006],r[1006],ctt[1006],ap[1006];
void add(int a,int b,int val){
++cnt;
edg[cnt].to=b;
edg[cnt].next=h[a];
edg[cnt].val=val;
h[a]=cnt;
}
void dfs1(int u,int fa){
int flag=0;
for(int i=h[u];i;i=edg[i].next){
int v=edg[i].to;
if(v==fa) continue;
ap[v]=edg[i].val;
flag=1;
if(ctt[u]==0) l[u]=v,++ctt[u];
else r[u]=v;
dfs1(v,u);
}
if(flag==0)
for(int i=1;i<=m;++i)
f[u][i]=edg[h[u]].val;
f[u][1]=ap[u];
}
void dfs(int u,int fa){
for(int i=h[u];i;i=edg[i].next){
int v=edg[i].to;
if(v==fa) continue;
dfs(v,u);
for(int j=2;j<=m;++j)
for(int k=0;k<j;++k)
f[u][j]=max(f[u][j],f[l[u]][k]+f[r[u]][j-k-1]+ap[u]);
}
}
int main(){
n=Read(),m=Read();++m;
for(int i=2;i<=n;++i){
int aa=Read(),bb=Read(),cc=Read();
add(aa,bb,cc);
add(bb,aa,cc);
}
dfs1(1,0);
dfs(1,0);
printf("%d",f[1][m]);
return 0;
}
总结与反思: