树形DP 学习总结

DP毕竟是算法中最精妙的部分,理解并玩得花哨还是需要一定的时间积累

之前对普通的DP也不敢说掌握,只能说略懂皮毛

在学习树形DP 的同时也算是对DP有了更深的理解吧

DP的关键就在于状态的定义以及找转移

首先要考虑清楚状态,状态要能够很好地并且完整地描述子问题

其次考虑最底层的状态,这些状态一般是最简单的情况或者是边界情况

再就是考虑某一个状态能从哪些子状态转移过来,同时还要考虑转移的顺序,确保子问题已经解决

树形DP很多时候就是通过子节点推父亲节点的状态

还是通过题目来加强理解吧

1.HDU 1520

题意:给一棵树,选最多的结点使得选择的结点不存在直接的父子关系

很容易想到一个结点有两个状态:选或者不选

所以自然地想到状态dp[u][0/1]表示u子树内的最佳答案,u的状态为选或者不选

初始化自然是叶子结点dp[u][0]=0,dp[u][1]=w[u]

转移则可以考虑依次考虑

u不选的时候:u的儿子可以任意选或者不选,所以dp[u][0]+=max(dp[v][0],dp[v][1])

u选的时候:u的儿子必定不能选,所以dp[u][1]+=dp[v][0]   然后dp[u][1]+=w[u]表示加上u这个点

答案自然就是max(dp[rt][0],dp[rt][1])了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 6005;
const int M = 1e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N],w[N],deg[N];
int dp[N][2];

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(deg,0);
    mems(dp,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
}

void dfs(int u){
    dp[u][0]=0;
    dp[u][1]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        dp[u][0]+=max(dp[v][1],dp[v][0]);
        dp[u][1]+=dp[v][0];
    }
}
int n;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        int u,v;
        while(1){
            scanf("%d%d",&v,&u);
            if(!v&&!u) break;
            addedge(u,v);deg[v]++;
        }
        int rt;
        for(int i=1;i<=n;i++) if(!deg[i]){
            dfs(rt=i);
            break;
        }
        printf("%d\n",max(dp[rt][0],dp[rt][1]));
    }
    return 0;
}
View Code

2.POJ 1436

题意:选中一个点则与其相连的边将被覆盖,问最少选几个点使得树中所有边均被覆盖

和上一个题很类似

同样状态设为dp[u][0/1]

初始的底层状态自然是dp[u][0]=0,dp[u][1]=1;

考虑一个非叶子结点和它儿子的所有连边

如果当前结点不选,那这些边只能由其儿子保护,所以dp[u][0]+=dp[v][1]

如果当前结点选,那这些边已被保护,其儿子选和不选都行,所以dp[u][1]+=min(dp[v][0],dp[v][1])

答案自然是min(dp[rt][0],dp[rt][1])

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1505;
const int M = 1e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N],deg[N];
int dp[N][2];

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(deg,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
}

void dfs(int u){
    dp[u][0]=0;
    dp[u][1]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        dp[u][0]+=dp[v][1];
        dp[u][1]+=min(dp[v][0],dp[v][1]);
    }
}

int n,k,u,v;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++){
            scanf("%d:(%d)",&u,&k);
            for(int j=0;j<k;j++){
                scanf("%d",&v);
                addedge(u,v);deg[v]++;
            }
        }
        int rt;
        for(int i=0;i<n;i++) if(!deg[i]){
            dfs(rt=i);
            break;
        }
        printf("%d\n",min(dp[rt][0],dp[rt][1]));
    }
    return 0;
}
View Code

3.URAL 1018

题意:树中每个点有权值,问只留下k个点剩下的最大权值和是多少?留下的点还是构成一棵树

树形背包

状态定义成dp[u][i]表示u子树内剩i个点的最大权值

考虑叶子结点:dp[u][0]=0,dp[u][1]=w[u]

考虑非叶子结点的一个状态dp[u][i],对于当前的一个儿子v,枚举一个k表示从这个儿子里取几个结点,维护一个最大值

其实我们这里的状态是三维的,表示u子树的前j个子树取了i个结点的答案

我们使用滚动数组把j这一维滚掉

这里简化了题目,每一个结点固定只有两个儿子,用一般做法做肯定也是没问题的

还有要注意的地方就是这里根是一定要保留的

处理方法就是对于状态dp[u][1]直接赋值,枚举时候i从2开始,这样就可以默认根已取

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 105;
const int M = 1e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N],w[N];
int dp[N][N],sz[N];
int ls[N],rs[N];

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(w,-1);
    //mems(deg,0);
    mems(dp,0);
    mems(ls,-1);
    mems(rs,-1);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    sz[u]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(ls[u]==-1) ls[u]=v;
        else rs[u]=v;
    }
}

void dfs(int u){
    int f=0;
    dp[u][0]=0;dp[u][1]=w[u];
    if(ls[u]!=-1) dfs(ls[u]),f=1;
    if(rs[u]!=-1) dfs(rs[u]),f=1;
    if(!f) return;
    for(int i=2;i<=sz[u];i++)
    for(int j=0;j<=sz[ls[u]];j++) if(i-1>=j) dp[u][i]=max(dp[u][i],dp[ls[u]][j]+dp[rs[u]][i-1-j]+w[u]);
}

int n,k;

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&k)){
        init();
        int u,v,x,rt=1;w[rt]=0;
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&x);
            addedge(u,v);
            if(w[v]==-1) w[v]=x;
            else w[u]=x;
        }
        dfs1(rt,-1);
        dfs(rt);
        printf("%d\n",dp[rt][k+1]);
    }
    return 0;
}
View Code

4.HDU 2196

题意:对于树中的每一个结点,输出树中与其距离最远的结点的距离值

经典的树形DP问题

状态定义为dp[u][0/1]为u到其子树内结点的最远距离、次远距离

这样一轮dp下来,可以想到对于根来说,dp[rt][0]就是它的答案

但是对于其它结点来说只得到了其子树内的答案,而我们需要的是其对于整棵树的信息

这里需要再一次dfs,相当于反过来从根往叶子再dp一次,通过根的答案推其余结点的答案

这里之所以要维护一个次大值,就是对于一个结点u的儿子v,

若u的最远距离是经过u的,那v的答案应该是v子树内的答案和u的次大值比较,否则v的答案和u的最大值比较

画个图就明白了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e4+5;
const int M = 2e4+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int mx[N][2],id[N][2];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(mx,0);
    mems(id,-1);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        if(mx[v][0]+edge[i].w>=mx[u][0]){
            mx[u][1]=mx[u][0];
            id[u][1]=id[u][0];id[u][0]=v;
            mx[u][0]=mx[v][0]+edge[i].w;
        }
        else if(mx[v][0]+edge[i].w>mx[u][1]) mx[u][1]=mx[v][0]+edge[i].w,id[u][1]=v;
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(id[u][0]==v){
            if(mx[v][1]<mx[u][1]+edge[i].w){
                mx[v][1]=mx[u][1]+edge[i].w;
                id[v][1]=u;
            }
        }
        else{
            if(mx[v][1]<mx[u][0]+edge[i].w){
                mx[v][1]=edge[i].w+mx[u][0];
                id[v][1]=u;
            }
        }
        if(mx[v][0]<mx[v][1]){
            swap(mx[v][0],mx[v][1]);
            swap(id[v][0],id[v][1]);
        }
        dfs2(v,u);
    }
}

int n,u,w;

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        for(int i=2;i<=n;i++){
            scanf("%d%d",&u,&w);
            addedge(i,u,w);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        for(int i=1;i<=n;i++) printf("%d\n",mx[i][0]);
    }
    return 0;
}
View Code

5.POJ 2152

题意:树中每个结点有两个值:w[i]表示在i建设防火设施的价格,d[i]表示与i最近的防火设施的距离上限,求满足所有d[i]的最小花费

算是一道比较难的树形dp,状态和普通的树形DP略有不同

树形dp很多时候是把一个结点及其子树看成一个整体,然后考虑这个结点的状态进行转移

考虑到数据范围N<=1000,可以定义状态dp[u][i]表示u依靠i时,保证子树内安全的最小花费

为了转移方便,再定义all[u]表示保证u的安全的最小花费

其实可以理解为all[u]是在dp[u][i]中取了个最优值

要确定一个点是否能被u依靠就需要知道u与该点的距离

所以先n^2处理树中任意两点的距离

考虑叶子结点:dp[u][i]=w[i]

考虑一个非叶子结点u,先枚举它依靠的结点i

再考虑u的儿子v,v可以选择依靠或者不依靠i,则dp[u][i]+=min(dp[v][i]-c[i],all[v])

对于u的每一个i更新u的最优解all[u]

对于u的孙子k是不需要考虑的,因为k依靠i的情况在解决v的时候已经考虑到了,所以不会有重复计算的情况

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e3+5;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int dp[N][N],all[N];
int n,u,v,x;
int cost[N],d[N],dis[N][N];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
    mems(all,INF);
    mems(dp,INF);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void dfs1(int rt,int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dis[rt][v]=dis[rt][u]+edge[i].w;
        dfs1(rt,v,u);
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs2(v,u);
    }
    for(int k=1;k<=n;k++) if(dis[u][k]<=d[u]){
        dp[u][k]=cost[k];
        for(int i=first[u];i!=-1;i=edge[i].next){
            int v=edge[i].e;
            if(v==fa) continue;
            dp[u][k]+=min(dp[v][k]-cost[k],all[v]);
        }
        all[u]=min(all[u],dp[u][k]);
    }
}

int T;
int main(){
    //freopen("in.txt","r",stdin);
    for(int i=0;i<N;i++) dis[i][i]=0;
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&cost[i]);
        for(int i=1;i<=n;i++) scanf("%d",&d[i]);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&x);
            addedge(u,v,x);
        }
        for(int i=1;i<=n;i++) dfs1(i,i,-1);
        dfs2(1,-1);
        printf("%d\n",all[1]);
    }
    return 0;
}
View Code

6.POJ 3162

题意:对于树中每一个结点i找到另一个结点使得两者距离dp[i]最远,最后输出一段最长区间的长度,区间maxv-minv<=M

只是在树形dp上加了点东西而已,用线段树+two pointer维护就好了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e6+5;
const int M = 2e6+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int dp[N][2],id[N][2];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void update(int u){
    if(dp[u][0]<dp[u][1]){
        swap(dp[u][0],dp[u][1]);
        swap(id[u][0],id[u][1]);
    }
}

void dfs1(int u,int fa){
    dp[u][1]=dp[u][0]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        if(edge[i].w+dp[v][0]>dp[u][1]){
            dp[u][1]=edge[i].w+dp[v][0];
            id[u][1]=v;
        }
        update(u);
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(id[u][0]==v){
            if(dp[u][1]+edge[i].w>dp[v][1]){
                dp[v][1]=dp[u][1]+edge[i].w;
                id[v][1]=u;
            }
        }
        else{
            if(dp[u][0]+edge[i].w>dp[v][1]){
                dp[v][1]=dp[u][0]+edge[i].w;
                id[v][1]=u;
            }
        }
        update(v);
        dfs2(v,u);
    }
}


int minv[N<<2],maxv[N<<2];

void build(int L,int R,int pos){
    if(L==R){
        minv[pos]=maxv[pos]=dp[L][0];
        return;
    }
    mdzz;
    build(lson);
    build(rson);
    minv[pos]=min(minv[ls],minv[rs]);
    maxv[pos]=max(maxv[ls],maxv[rs]);
}

int query_min(int l,int r,int L,int R,int pos){
    if(l<=L&&R<=r) return minv[pos];
    mdzz;
    int ans=INF;
    if(l<=mid) ans=min(ans,query_min(l,r,lson));
    if(r>mid) ans=min(ans,query_min(l,r,rson));
    return ans;
}

int query_max(int l,int r,int L,int R,int pos){
    if(l<=L&&R<=r) return maxv[pos];
    mdzz;
    int ans=-INF;
    if(l<=mid) ans=max(ans,query_max(l,r,lson));
    if(r>mid) ans=max(ans,query_max(l,r,rson));
    return ans;
}

int n,m,u,v;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=2;i<=n;i++){
            scanf("%d%d",&u,&v);
            addedge(i,u,v);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        int ans=0;
        build(1,n,1);
        int l=1,r=1;
        while(1){
            if(l>r||l>n||r>n) break;
            int mxv=query_max(l,r,1,n,1);
            int miv=query_min(l,r,1,n,1);
            if(mxv-miv<=m){
                ans=max(ans,r-l+1);
                r++;
            }
            else l++;
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

7.codeforces 219D

题意:树边有方向,选择一个点,翻转最少路径,使得其能到达其余所有的点,输出所有可能的答案

可以将翻转理解为一种花费,那不翻转就是花费0,翻转则为1

可以定义dp[u]表示u到所有点的距离和,那dp[u]最小的就是答案

依旧先考虑u的子树内的答案

考虑一个叶子:dp[u]=0;

考虑一个非叶子u以及其的一个儿子v:很容易想到dp[u]+=dp[v]+w[u,v]

一次dfs下来后rt的答案已知,接下来通过rt来推其余结点对于整棵树的答案

考虑结点u及其一个儿子v,v到v子树内的答案已知,v到u除v子树的结点的答案是dp[u]-dp[v]-w[u,v]

所以dp[v]+=dp[u]-dp[v]-w[u,v]+w[v,u]

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 2e5+5;
const int M = 4e5+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

int tot;
int first[N];
int dp[N],sz[N];

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[M];

void init(){
    tot=0;
    mems(first,-1);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u],0);
    first[u]=tot++;
    edge[tot]=node(u,first[v],1);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    dp[u]=0;sz[u]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        dp[u]+=dp[v]+edge[i].w;
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dp[v]+=(dp[u]-dp[v]-edge[i].w)+(edge[i].w^1);
        dfs2(v,u);
    }
}

int n,u,v;
int main(){
    init();
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    dfs1(1,-1);
    dfs2(1,-1);
    int ans=INF;
    for(int i=1;i<=n;i++) ans=min(ans,dp[i]);
    //for(int i=1;i<=n;i++) cout<<i<<' '<<dp[i]<<endl;
    printf("%d\n",ans);
    for(int i=1;i<=n;i++) if(ans==dp[i]) printf("%d ",i);
    return 0;
}
View Code

8.POJ 1155

题意:树中每个叶子有点权表示收入,边权表示花费。选择某些叶子后,不必要的边可删掉,最多选择几个点使得花费>=收入

状态很多时候是根据要求的东西来定义的

这里定义dp[u][i]为子树u取i个叶子的最优解

由于不选叶子的话是不需要任何花费的,所以容易想到dp[u][0]=0

考虑一个叶子,其第二维只有0/1两种取值,1的话明显是dp[u][1]=w[u]

考虑一个非叶子,第二维1~sz[u]都是无法确定的状态,而且考虑到结果可能是负值,而且我们需要的是一个最大值,所以初始化为-INF

对于一个结点u的一个儿子v,枚举在这个儿子中取的叶子数k,维护个最优解就好了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 3e3+5;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[N<<1];

int tot;
int first[N],w[N],dp[N][N],sz[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(w,0);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    int f=0;sz[u]=0;dp[u][0]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        f=1;
        dfs1(v,u);
        sz[u]+=sz[v];
    }
    if(!f){
        sz[u]=1;dp[u][1]=w[u];
    }
    else{
        for(int i=1;i<=sz[u];i++)
            dp[u][i]=-INF;
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs2(v,u);
        for(int j=sz[u];j>=1;j--)
        for(int k=1;k<=sz[v];k++) if(j>=k)
            dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-edge[i].w);
    }
}

int n,m,u,v,k;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<=n-m;i++){
            scanf("%d",&k);
            for(int j=0;j<k;j++){
                scanf("%d%d",&u,&v);
                addedge(i,u,v);
            }
        }
        for(int i=n-m+1;i<=n;i++) scanf("%d",&w[i]);
        dfs1(1,-1);
        dfs2(1,-1);
        for(int i=m;i>=0;i--) if(dp[1][i]>=0){
            printf("%d\n",i);
            break;
        }
    }
    return 0;
}
View Code

9.HDU 1011

题意:树中每一个结点有一个花费一个收益,问K元能取得的最大收益是多少

明显的树形背包,一个结点可以看成一个物品

XJB搞搞就行了

有个坑是a/20向上取整不能写成(a-1)/20+1,a可能是0

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 105;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot,n,m;
int first[N],c[N],p[N],dp[N][N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    int x=(c[u]+19)/20;
    for(int i=x;i<=m;i++) dp[u][i]=p[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        for(int j=m;j>=x;j--)
        for(int k=1;k<=j-x;k++)
            dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        if(n==-1&&m==-1) break;
        init();
        for(int i=1;i<=n;i++) scanf("%d%d",&c[i],&p[i]);
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        if(!m){
            puts("0");
            continue;
        }
        dfs1(1,-1);
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
View Code

10.POJ 1947

题意:问最少需要破坏多少条边能产生一个大小为K的块

dp[u][i]表示u的子树中产生一个i大小的块需要破坏的最少边数

num[u]表示u的儿子数

这题考虑的方向不同平常

考虑块的大小1~sz[u] (0的大小是不合法的状态)

若要在u的子树中产生一个1大小的块,初始情况应该是u和所有儿子的连边断开,所以dp[u][1]=num[u]

其实这样处理相当于对于每一个状态来说u都是默认取了的

考虑一个结点u及其一个儿子v,对于状态dp[u][i] 枚举在v中取的结点数k

最后要枚举一个块的根,因为我们之前处理状态的时候是默认根取,所以最优解还可能在子树中

如果加一维状态[0/1]表示u是否取的话应该可以避免这个问题,不过也没有去尝试了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 155;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot,n,m;
int first[N],dp[N][N],sz[N],num[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,INF);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    sz[u]=1;num[u]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        num[u]++;
        sz[u]+=sz[v];
    }
}

void dfs2(int u,int fa){
    dp[u][1]=num[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs2(v,u);
        for(int j=m;j>=1;j--)
        for(int k=1;k<=sz[v];k++)
           if(j-k>=1) dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]-1);

    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        /*for(int i=1;i<=n;i++){
            cout<<i<<":"<<endl;
            for(int j=1;j<=sz[i];j++) cout<<dp[i][j]<<' ';
            cout<<endl;
        }*/
        int ans=dp[1][m];
        for(int i=2;i<=n;i++) ans=min(ans,dp[i][m]+1);
        printf("%d\n",ans);
    }
    return 0;
}
View Code

11.HDU 1561

题意:每个结点有一个权值,问选择K个结点(必须按路径选择)获得的最大权值

典型的树形背包

由于不是任意选,我们只需要在状态转移的时候变通一下就好了

对于u来说,我要选择其子孙的话前提是u选择

所以对第二维i==1的时候单独处理,而且之后不再更新这个值就能保证u是默认被选择的

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#define ls pos<<1
#define rs pos<<1|1
#define lson L,mid,pos<<1
#define rson mid+1,R,pos<<1|1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 205;
const int M = 2e3+5;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N];

int tot,n,m;
int first[N],dp[N][N],w[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    //edge[tot]=node(u,first[v]);
   // first[v]=tot++;
}

void dfs(int u){
    dp[u][1]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        for(int j=m;j>=2;j--)
        for(int k=0;k<=m;k++)
            if(j-1>=k) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    w[0]=0;
    while(scanf("%d%d",&n,&m)&&(n||m)){
        init();m++;
        int rt=0,u;
        for(int i=1;i<=n;i++){
            scanf("%d%d",&u,&w[i]);
            addedge(u,i);
        }
        dfs(rt);
        printf("%d\n",dp[rt][m]);
    }
    return 0;
}
View Code

12.HDU 4003

题意:树中每条边有花费,有k个机器人,问遍历所有的点的最少花费(可以回头,每次只能从s出发

这一题的状态定义很有意思

dp[u][i]表示遍历完u的子树后有i个机器人没有回到u结点的最优解

对于每一棵子树都是需要遍历完的,所以必须选择一个方案

为了保证至少选择一个方案,所以在考虑当前儿子v的时候先将答案加上一个状态,这里是加上没有机器人留在v子树的方案

然后再对v做一个背包,若有更优解则初始放置的选择将被替换掉

其实相当于对于子树v有k件物品来选择,必须选择一件

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e4+5;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[N<<1];

int tot;
int first[N],dp[N][12];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,0);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

int n,s,m;

void dfs(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>=0;j--){
            dp[u][j]+=dp[v][0]+edge[i].w*2;
            for(int k=1;k<=j;k++)
                dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]+edge[i].w*k);
        }
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d%d",&n,&s,&m)){
        init();
        int u,v,w;
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        dfs(s,-1);
        printf("%d\n",dp[s][m]);
    }
    return 0;
}
View Code

13.HDU 4276

题意:每个点有点权,边有花费,问T时间能否从1到达n,若能,能获取的最大权值是多少

首先想到树中两点的路径是唯一的,既然要从1到达n,先考虑最短路径能否到达

之后剩余的时间作为背包容量,因为其余的点都是非必须的,所以去了必须还要回来,故每条非必要的边花费都需要*2

 所以做法就是先把总时间减去1到n的路径距离,并把路径上的花费置零

然后对于每一个结点做背包,对于u的儿子v枚举一个花费k,注意边的花费要double

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 105;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[N<<1];

int tot;
int first[N],dp[N][N*5],pre[N],id[N],w[N],inq[N],dis[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,0);
    mems(inq,0);
    mems(dis,INF);
}

void addedge(int u,int v,int W){
    edge[tot]=node(v,first[u],W);
    first[u]=tot++;
    edge[tot]=node(u,first[v],W);
    first[v]=tot++;
}

int n,m,cnt;
queue<int>q;

void bfs(int s){
    while(!q.empty()) q.pop();
    pre[s]=1;dis[s]=0;
    inq[s]=1;
    q.push(s);
    while(!q.empty()){
        int u=q.front();q.pop();inq[u]=0;
        if(u==n) break;
        for(int i=first[u];i!=-1;i=edge[i].next){
            int v=edge[i].e;
            if(!inq[v]&&dis[u]+edge[i].w<dis[v]){
                dis[v]=dis[u]+edge[i].w;
                q.push(v);
                inq[u]=1;
                pre[v]=u;
                id[v]=i;
            }
        }
    }
    cnt=0;
    int u=n;
    while(u!=1){
        cnt+=edge[id[u]].w;
        edge[id[u]].w=edge[id[u]^1].w=0;
        u=pre[u];
    }
}

void dfs2(int u,int fa){
    for(int i=0;i<=m;i++) dp[u][i]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs2(v,u);
        for(int j=m;j>=0;j--)
        for(int k=0;k<=j;k++) if(j-edge[i].w*2-k>=0)
            dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-edge[i].w*2-k]);
    }
}

int u,v,W;
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&W);
            addedge(u,v,W);
        }
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        bfs(1);
        if(cnt>m){
            printf("Human beings die in pursuit of wealth, and birds die in pursuit of food!\n");
            continue;
        }
        m-=cnt;
        dfs2(1,-1);
        /*for(int i=1;i<=n;i++){
            cout<<i<<':'<<endl;
            for(int j=0;j<=m;j++) cout<<dp[i][j]<<' ';cout<<endl;
        }*/
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
View Code

14.HDU 3586

题意:给一个限制m,切断的路径权值和不超过m,单个边权值也不超过k,求最小的k使得所有叶子和根不相连

二分一个k

对于一个确定的k,dp[u]表示u的叶子全部和u分离需要的最小花费

考虑叶子节点:dp[u]=INF(不合法状态

考虑非叶子结点:一开始是没有和儿子相连,所以dp[u]=0

考虑u的一个儿子v,若(u,v)这条边是<=lim,则可以选择在消除这条边或者是在v的子树中消除,两者取一个最优解

最后判断dp[1]是否小于给定的m

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1005;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[N<<1];

int tot,n,m;
int first[N],dp[N];

void init(){
    tot=0;
    mems(first,-1);
}

void addedge(int u,int v,int w){
    edge[tot]=node(v,first[u],w);
    first[u]=tot++;
    edge[tot]=node(u,first[v],w);
    first[v]=tot++;
}

void dfs(int u,int fa,int lim){
    dp[u]=0;int f=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u,lim);f=1;
        int tmp;
        if(edge[i].w<=lim) tmp=edge[i].w;
        else tmp=INF;
        dp[u]+=min(tmp,dp[v]);
    }
    if(!f) dp[u]=INF;
}

bool check(int mid){
    mems(dp,INF);
    dfs(1,-1,mid);
    return dp[1]<=m;
}

int u,v,w;
int main(){
    //freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&m)&&(n||m)){
        init();
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        int low=1,high=INF,mid,ans=-1;
        while(low<=high){
            mid=(low+high)>>1;
            if(check(mid)){
                ans=mid;
                high=mid-1;
            }
            else low=mid+1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

15. POJ 3107

题意:输出树中的结点k,删去k后产生的最大的联通块最小

直接两次dp就好了,第一次处理子树第二次考虑父亲除去当前结点产生的最大块,维护个最大值

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 50005;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot,n,m;
int first[N],dp[N],sz[N];
vector<int> ans;
void init(){
    tot=0;
    ans.clear();
    mems(first,-1);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    sz[u]=1;dp[u]=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        dp[u]=max(dp[u],sz[v]);
    }
}

void dfs2(int u,int fa){
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dp[v]=max(dp[v],n-sz[v]);
        dfs2(v,u);
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d",&n)){
        init();
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs1(1,-1);
        dfs2(1,-1);
        int cnt=INF;
        for(int i=1;i<=n;i++) cnt=min(cnt,dp[i]);
        //for(int i=1;i<=n;i++) cout<<i<<' '<<dp[i]<<endl;
        for(int i=1;i<=n;i++) if(dp[i]==cnt) ans.push_back(i);
        for(int i=0;i<ans.size();i++){
            if(i) printf(" ");
            printf("%d",ans[i]);
        }
        puts("");
    }
    return 0;
}
View Code

16.POJ 3140

题意:选择一条树边断开,使得分成的两部分的总点权差最小,输出最小值

就直接预处理每一个点及其子树的总点权

枚举一个点和其父亲断开,取个最优值就好了

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e5+5;
const int M = N<<1;
const LL MOD = 998244353;
const LL INF = 1e18;

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot,n,m;
int first[N];
LL sz[N],num[N],cnt;

void init(){
    tot=0;cnt=0;
    mems(first,-1);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

void dfs(int u,int fa){
    sz[u]=num[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        sz[u]+=sz[v];
    }
}
int u,v,cas=1;
int main(){
    //freopen("in.txt","r",stdin);
    while(scanf("%d%d",&n,&m)&&(n||m)){
        init();
        for(int i=1;i<=n;i++) scanf("%lld",&num[i]),cnt+=num[i];
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1,-1);
        LL ans=INF;
        for(int i=2;i<=n;i++){
            LL tmp=cnt-sz[i]*2;
            if(tmp<0) tmp*=-1;
            ans=min(ans,tmp);
        }
        printf("Case %d: %lld\n",cas++,ans);
    }
    return 0;
}
View Code

17. POJ 2486

题意:从树根1走K步能获得的最大点权,可以走回头路

dp[u][i][0/1]表示从u出发走i步,最终回到u/不回到u的最优值

考虑一个叶子结点:dp[u][0][0]=w[u];

考虑一个非叶子结点u及其一个儿子v,枚举一个k表示对于这个儿子v走的步数

有3种结果:留在v子树,回到u,留在u其它子树

画图就能看出三种情况的转移:

留在v子树:状态分裂为dp[u][j-k-1][0],dp[v][k][1]  花费为1步

回到u:状态分裂为dp[u][j-k-2][0],dp[v][k][0] 花费为2步

留在其他子树:状态分裂为dp[u][j-k-2][1],dp[v][k][0] 花费为2步

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 105;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot;
int first[N],dp[N][N<<1][2],w[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

int n,m;

void dfs(int u,int fa){
    for(int i=0;i<=m;i++) dp[u][i][0]=dp[u][i][1]=w[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>=1;j--)
        for(int k=0;k<=j;k++){
            if(j>=k+2) dp[u][j][0]=max(dp[u][j][0],dp[v][j-k-2][0]+dp[u][k][0]);
            if(j>=k+1) dp[u][j][1]=max(dp[u][j][1],dp[v][j-k-1][1]+dp[u][k][0]);
            if(j>=k+2) dp[u][j][1]=max(dp[u][j][1],dp[v][j-k-2][0]+dp[u][k][1]);
        }
    }
}

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        int u,v;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1,-1);
        printf("%d\n",max(dp[1][m][0],dp[1][m][1]));
    }
    return 0;
}
View Code

18. HDU 4044

题意:每个点有ki种选择,每种选择对应一种一种花费一种收益 问拥有m元,令x为1到所有叶子(不含1)的路径的点权和最小值

求x的最大值

一开始的想法是的定义dp[u][i][0/1]为u子树花i元,u选或者不选的答案

但是后面会发现这样定义状态的话对于dp[u][i][1]的合并不是很好处理,因为我没有记录u选择的是哪个方案

看了题解后学了一个新姿势

定义dp[u][i]为u不选的时候花i元的最优解

预处理一个w[u][i]表示u结点花i元最多能获得的权值

考虑一个叶子结点:dp[u][i]=w[u][i]

考虑一个非叶子结点不选时候:每一个儿子v加进来的时候,状态都分裂为dp[v][k]和dp[u][j-k]

对于每一个枚举的k来说,答案是min(dp[v][k],dp[u][j-k]) 维护这个答案的最大值就是dp[u][j]

考虑完u不选的情况后,再对u单独做一次背包,枚举u的花费,取个最优值就是答案

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e3+5;
const int M = 205;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
    node(){}
    node(int a,int b):e(a),next(b){}
}edge[N<<1];

int tot;
int first[N],dp[N][M],kind[N],chose[N][M];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,INF);
    mems(chose,0);
}

void addedge(int u,int v){
    edge[tot]=node(v,first[u]);
    first[u]=tot++;
    edge[tot]=node(u,first[v]);
    first[v]=tot++;
}

int T,n,m;

void dfs1(int u,int fa){
    int f=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        f++;
        for(int j=m;j>=0;j--){
            int tmp=0;
            for(int k=0;k<=j;k++) tmp=max(tmp,min(dp[u][j-k],dp[v][k]));
            dp[u][j]=tmp;
        }
    }
    if(!f){
        for(int i=0;i<=m;i++) dp[u][i]=chose[u][i];
        return;
    }
    for(int j=m;j>=0;j--)
    for(int k=0;k<=j;k++) dp[u][j]=max(dp[u][j],dp[u][j-k]+chose[u][k]);
}

int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        int u,v,k;
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        scanf("%d",&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&k);
            for(int j=1;j<=k;j++){
                scanf("%d%d",&u,&v);
                chose[i][u]=max(chose[i][u],v);
            }
            for(int j=1;j<=m;j++) chose[i][j]=max(chose[i][j],chose[i][j-1]);
        }
        dfs1(1,-1);
        printf("%d\n",dp[1][m]);
    }
    return 0;
}
View Code

 19.HDU 5758

题意:点有点权边有花费,点权只能获得一次,花费每次经过都要扣除,可以走回头路,问能拿到的最大价值

参考Apple Tree可以知道需要加一维[0/1]表示是否回到u

定义dp[u][0/1]表示从u出发不回/回到u的最优解

求一个结点对于整棵树的信息一般都是先处理子树内的信息,再第二次dfs处理父亲对答案的影响

先考虑子树内:

考虑一个叶子结点:dp[u][0]=dp[u][1]=w[u]

考虑一个非叶子u和他的一个儿子v:

对于dp[u][0]来说,v的贡献只能是dp[v][0]-2*w[u,v],如果这个值小于0我必然不走v

对于dp[u][1]来说,如果不停在v内,v的贡献也是dp[v][0]-2*w[u,v],如果停在u则状态分裂为dp[u][0]+dp[v][1]-w[u,v]

再考虑fa对u的影响

考虑fa对u的影响时一般需要把u对fa的影响先排除

对于dp[fa][0]来说u的影响只能是max(0,dp[u][0]-2*w[fa,u]),直接减去就行了

对于dp[fa][1]来说有两种情况

若最终不停在u,则和dp[fa][0]一样处理

若停在u,则需要对fa再做一次排除u后的背包

这里将fa对u的影响作为参数下传,为的是保证在推u的时候fa的值是对于整棵树的,而其余结点是对于其子树的

之所以这样做是因为用fa更新u的时候,排除u对fa的影响之后fa相当于u的一棵新子树

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e5+50;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next,w;
    node(){}
    node(int a,int b,int c):e(a),next(b),w(c){}
}edge[N<<1];

int tot,n,m;
int first[N],dp[N][2],w[N],id[N];

void init(){
    tot=0;
    for(int i=1;i<=n;i++) first[i]=-1;
}

void addedge(int u,int v,int W){
    edge[tot]=node(v,first[u],W);
    first[u]=tot++;
    edge[tot]=node(u,first[v],W);
    first[v]=tot++;
}

void dfs1(int u,int fa){
    dp[u][0]=dp[u][1]=w[u];
    id[u]=-1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs1(v,u);
        int tmp=max(0,dp[v][0]-2*edge[i].w);
        dp[u][1]+=tmp;
        if(dp[u][1]<dp[u][0]+max(0,dp[v][1]-edge[i].w)){
            dp[u][1]=dp[u][0]+max(0,dp[v][1]-edge[i].w);
            id[u]=v;
        }
        dp[u][0]+=tmp;
    }
}
int ans[N];
void dfs2(int u,int fa,int f0,int f1){
    int t[2]={dp[u][0],dp[u][1]};
    int idd=id[u];
    t[1]+=f0;
    if(t[1]<t[0]+f1){
        t[1]=t[0]+f1;
        idd=fa;
    }
    t[0]+=f0;
    ans[u]=max(t[0],t[1]);

    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(v==idd){
            int tmp0=w[u]+f0,tmp1=w[u]+f1;
            for(int k=first[u];k!=-1;k=edge[k].next){
                int vv=edge[k].e;
                if(vv==fa||vv==v) continue;
                int tmp=max(0,dp[vv][0]-2*edge[k].w);
                tmp1+=tmp;
                tmp1=max(tmp1,tmp0+max(0,dp[vv][1]-edge[k].w));
                tmp0+=tmp;
            }
            tmp0=max(0,tmp0-2*edge[i].w);
            tmp1=max(0,tmp1-edge[i].w);
            dfs2(v,u,tmp0,tmp1);
        }
        else{
            int tmp=max(0,dp[v][0]-2*edge[i].w);
            int tmp0=max(0,t[0]-tmp-2*edge[i].w);
            int tmp1=max(0,t[1]-tmp-edge[i].w);
            dfs2(v,u,tmp0,tmp1);
        }
    }
}

int T,u,v,W,cas=1;

int main(){
    //freopen("in.txt","r",stdin);
    //freopen("pending.txt","w",stdout);
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        init();
        for(int i=1;i<=n;i++) scanf("%d",&w[i]);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&u,&v,&W);
            addedge(u,v,W);
        }
        dfs1(1,-1);
        dfs2(1,-1,0,0);
        printf("Case #%d:\n",cas++);
        for(int i=1;i<=n;i++) 
            printf("%d\n",ans[i]);
    }
    return 0;
}
View Code

 20.NUBT 1638

题意:建图略麻烦,抽象出来就是说树中选m个结点,其中rt到每一个叶子的路径上被选的结点数不超过k,问获得的最大权值是多少

定义dp[u][i][j][0/1]表示u子树选i个,最多的路径选了j个,u选/不选

dp值全部初始化为-1表示不合法状态

考虑一个叶子结点:选的话是dp[u][1][1][1]=w[u]  不选dp[u][0][0][0]=0

考虑一个非叶子结点u和他的一个儿子v:

对于状态dp[u][i][j][0],可能转移过来的状态有dp[u][i-k][j][0]+dp[v][k][0~j][0/1]或者dp[u][i-k][0~j][0]+dp[v][k][j][0/1]

对于状态dp[u][i][j][1],有dp[u][i-k][j][1]+dp[v][k][0~j-1][0/1]或dp[u][i-k][0~j][1]+dp[v][k][j-1][0/1](之所以v是j-1为上限是因为u选了的话合并后v的最长链长度必然+1,这样做是为了限制最长链在j范围内)

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 120;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct Point{
    int x,y;
}p[N<<2];

bool cmp(Point a,Point b){
    return a.x<b.x;
}

struct Node{
    int y,l,r;
}point[N];

bool cmp1(Node a,Node b){
    if(a.y==b.y) return a.l<b.l;
    return a.y>b.y;
}

struct node{
    int e,next;
}edge[N<<1];

int first[N],cnt,tot;
int dp[N][N][11][2],w[N],sz[N];

void init(){
    tot=0;cnt=0;
    mems(first,-1);
    mems(dp,-1);
}

void addedge(int u,int v){
    edge[tot]=(node){v,first[u]};
    first[u]=tot++;
}

void build(int l,int r,int fa){
    for(int i=l+1;i<=r;i++){
        if(p[l].y==p[i].y){
            addedge(fa,++cnt);
            w[cnt]=p[i].x-p[l].x;
            //cout<<fa<<' '<<cnt<<' '<<w[cnt]<<endl;
            build(l+1,i-1,cnt);
            build(i,r,fa);
            return ;
        }
        else if(p[i].y>p[l].y){
            build(l+1,i-1,fa);
            build(i,r,fa);
        }
    }
}

int n,m,K;

void dfs(int u){
    dp[u][1][1][1]=w[u];
    dp[u][0][0][0]=0;

    sz[u]=1;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        dfs(v);
        sz[u]+=sz[v];
        for(int j=sz[u];j>=0;j--)
        for(int k=0;k<=K;k++)
        for(int p=0;p<=min(sz[v],j);p++)
        for(int kk=0;kk<=k;kk++){
            if(dp[u][j-p][k][0]!=-1&&(dp[v][p][kk][0]!=-1||dp[v][p][kk][1]!=-1))
            dp[u][j][k][0]=max(dp[u][j][k][0],dp[u][j-p][k][0]+max(dp[v][p][kk][0],dp[v][p][kk][1]));

            if(dp[u][j-p][kk][0]!=-1&&(dp[v][p][k][0]!=-1||dp[v][p][k][1]!=-1))
            dp[u][j][k][0]=max(dp[u][j][k][0],dp[u][j-p][kk][0]+max(dp[v][p][k][0],dp[v][p][k][1]));

            if(kk>=1&&dp[u][j-p][k][1]!=-1&&(dp[v][p][kk-1][0]!=-1||dp[v][p][kk-1][1]!=-1))
            dp[u][j][k][1]=max(dp[u][j][k][1],dp[u][j-p][k][1]+max(dp[v][p][kk-1][0],dp[v][p][kk-1][1]));

            if(k>=1&&dp[u][j-p][kk][1]!=-1&&(dp[v][p][k-1][0]!=-1||dp[v][p][k-1][1]!=-1))
            dp[u][j][k][1]=max(dp[u][j][k][1],dp[u][j-p][kk][1]+max(dp[v][p][k-1][0],dp[v][p][k-1][1]));

        }
    }
}

int cas=1;

int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d%d",&n,&m,&K)){
        init();
        for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y);
        sort(p+1,p+1+n,cmp);
        build(1,n,0);

        int rt=0;
        dfs(rt);
        int ans=-1;
        for(int i=0;i<K;i++) ans=max(ans,dp[rt][m][i][0]);
        printf("Case %d: %d\n",cas++,ans);
    }
    return 0;
}
View Code

21.HDU 5758

题意:边花费1,可以从一个点瞬移到另一个点,问瞬移次数最少的前提下遍历所有边的最小花费

可以知道瞬移次数是(叶子数+1)/2

定义dp[u][0]为遍历完u后的最小花费(只是第一次dp,答案不一定是最终答案

考虑非叶子u和他的一个儿子v:

若v有偶数个儿子,则要在瞬移次数最少的情况下遍历(u,v)这条边,就必须有两个结点和v外的结点配对,所以(u,v)对答案贡献2

同理奇数的时候贡献1

如果总叶子数是偶数那答案已经出来了

但是如果总叶子数是奇数这样的答案可能会 偏大

其实奇数的情况就是在偶数的情况加了一条没有分叉的单链

所以再定义dp[u][1]为遍历u后的最小花费(第二次dp,独立与第一次dp

对于一个u,枚举这条单链出现的儿子v:

如果这个儿子就是一条单链,并且u是这条单链的起点,则dp[u][1]=min(dp[u][1],dp[u][0])

如果不是儿子v不是单链则把这条单链从v中剔除,则v中的叶子数的奇偶就变化了

dp[u][1]先减去dp[v][0],再减去奇偶变化的影响d,再加上单链存在与v的dp值dp[v][1],维护最优解

最后若总叶子数是偶数则取第一次dp的结果,否则取第二次的

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 1e5+5;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
}edge[N<<1];

int first[N],cnt,tot;
LL dp[N][2];
int sz[N],deg[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(deg,0);
    mems(dp,INF);
}

void addedge(int u,int v){
    edge[tot]=(node){v,first[u]};
    first[u]=tot++;
    edge[tot]=(node){u,first[v]};
    first[v]=tot++;
}

void dfs(int u,int fa){
    dp[u][0]=0;sz[u]=0;
    int flag=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        flag++;
        dfs(v,u);
        sz[u]+=sz[v];
        dp[u][0]+=dp[v][0];
        if(sz[v]&1) dp[u][0]++;
        else dp[u][0]+=2;
    }
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        if(flag>1&&sz[v]==1) dp[u][1]=min(dp[u][1],dp[u][0]);
        if(dp[v][1]==INF) continue;
        LL tmp=dp[u][0]-dp[v][0]+dp[v][1];
        if(sz[v]&1) tmp++;
        else tmp--;
        dp[u][1]=min(dp[u][1],tmp);
    }
    if(!flag) sz[u]=1;
}

int n,m,u,v,T;

int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d",&n);
        for(int i=1;i<n;i++){
            scanf("%d%d",&u,&v);
            addedge(u,v);
            deg[v]++;deg[u]++;
        }
        int rt=1,cnt=0;
        for(int i=1;i<=n;i++) {
            if(deg[i]!=1)
                rt=i;
            else cnt++;
        }
        dfs(rt,-1);
        printf("%lld\n",dp[rt][cnt&1]);
    }
    return 0;
}
View Code

 22.codeforces Round #322(Div.2) F

题意:把叶子平分染成两种颜色,其余点随便染,求最少有对相邻的点颜色不同  输入保证叶子是偶数个

定义dp[u][i][0/1]表示u子树染i个儿子0色,u染0/1色

这题经典的地方在于叶子和非叶子的初始情况不一样

先dp值全部赋INF表示不合法

对于叶子来说:dp[u][1][0]=0,dp[u][0][1]=0,其余两个状态不合法

考虑非叶子u:

非叶子u的颜色对第二维是没影响的,所以dp[u][0][1]=dp[u][0][0]=0

由于这里是必须要选择儿子的情况,之前有些题目是儿子可以不选

必须选的情况的处理方法有两种,第一种是先强制放一种状态,再枚举其他状态看看是否更优,有就替换,没有就保留原来的

或者说是用一个变量去存放儿子的最优情况,然后把这个最优情况强制合并到原状态里

这里我采用的是第二种方法

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

const int N = 5005;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;

struct node{
    int e,next;
}edge[N<<1];

int first[N],tot;
int dp[N][N][2],deg[N],leaf[N];

void init(){
    tot=0;
    mems(first,-1);
    mems(dp,INF);
    mems(deg,0);
}

void addedge(int u,int v){
    edge[tot]=(node){v,first[u]};
    first[u]=tot++;
    edge[tot]=(node){u,first[v]};
    first[v]=tot++;
}

int n,u,v;

void dfs(int u,int fa){
    dp[u][0][1]=0;
    dp[u][0][0]=0;
    leaf[u]=0;
    int flag=0;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        flag=1;
        leaf[u]+=leaf[v];
        for(int j=leaf[u];j>=0;j--){
            int miv[2]={INF,INF};
            for(int k=0;k<=min(leaf[v],j);k++){
                miv[0]=min(miv[0],min(dp[u][j-k][0]+dp[v][k][1]+1,dp[u][j-k][0]+dp[v][k][0]));
                miv[1]=min(miv[1],min(dp[u][j-k][1]+dp[v][k][1],dp[u][j-k][1]+dp[v][k][0]+1));
            }
            dp[u][j][0]=miv[0];
            dp[u][j][1]=miv[1];
        }
    }
    if(!flag){
        leaf[u]=1;
        dp[u][0][0]=INF;
        dp[u][1][0]=0;
    }
}

int main(){

    init();
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        deg[v]++;deg[u]++;
    }
    if(n==2) return 0*puts("1");
    int rt,cnt=0;
    for(int i=1;i<=n;i++){
        if(deg[i]!=1) rt=i;
        else cnt++;
    }
    dfs(rt,-1);
    //for(int i=1;i<=n;i++)
    //for(int j=0;j<=leaf[i];j++) printf("i_%d j_%d %d %d\n",i,j,dp[i][j][0],dp[i][j][1]);
    printf("%d\n",min(dp[rt][cnt/2][0],dp[rt][cnt/2][1]));
    return 0;
}
View Code

23.COJ 1793

题意:对于每个点给一个限制xi表示xi选了才能选i,现在最多选m个人,问在这些限制下最多能选几个

如果连边(xi->i)一眼看过去结构很像树,因为每个结点只有一个父亲

但是这里可能会构成强联通分量

不过很容易想到把强联通分量缩点后也是可以构成树或者森林的

森林我们可以建立一个虚根0来合并成一棵树

一开始有个地方没想明白,就是对于一个强联通分量来说可能去掉一个点后还是强联通分量,所以一个强联通分量内我选择几个人是不好判断的

但是仔细想想题目给的条件,每个点的入度只能为1,而这样构成的强联通分量只能是一个简单环

而对于一个简单环来说我只能全取或者全不取

所以问题就转化为了一个m的背包,每个点的sz即使花费也是价值,问能拿到的最大价值是多少

这里有个地方需要变通

这里是选了父亲才能选择儿子,所以对于一个结点u以及他的儿子v来说,我要把v加进来我首先得保证u被选

所以我枚举v的容量k的时候始终保证j-k也就是剩余的容量始终是大于sz[u]的,这样就能保证u始终被选

#include"cstdio"
#include"queue"
#include"cmath"
#include"stack"
#include"iostream"
#include"algorithm"
#include"cstring"
#include"queue"
#include"map"
#include"set"
#include"vector"
#include"bitset"
#define LL long long
#define ull unsigned long long
#define mems(a,b) memset(a,b,sizeof(a))
#define mdzz int mid=(L+R)>>1
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;
 
const int N = 1e3+5;
const int M = N<<1;
const LL MOD = 998244353;
const int INF = 0x3f3f3f3f;
 
struct node{
    int s,e,next;
}edge[M];
 
int tot,id;
int first[N];
int block,tp,n,m;
int low[N],dfn[N],ins[N],deg[N];
int st[N],belong[N],sz[N];
int dp[N][N];
 
void init(int n){
    tot=0;tp=0;block=0;id=0;
    for(int i=0;i<=n;i++){
        first[i]=-1;
        dfn[i]=0;
        ins[i]=0;
        deg[i]=0;
        for(int j=0;j<=m;j++) dp[i][j]=0;
    }
}
 
void addedge(int u,int v){
    edge[tot]=(node){u,v,first[u]};
    first[u]=tot++;
    //edge[tot]=(node){u,first[v]};
    //first[v]=tot++;
}
 
void tarjan(int u){
    low[u]=dfn[u]=++id;
    ins[u]=1;
    st[++tp]=u;
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(!dfn[v]){
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(ins[v]) low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u]){
        block++;
        sz[block]=0;
        int v;
        do{
            v=st[tp--];
            ins[v]=0;
            belong[v]=block;
            sz[block]++;
        }
        while(v!=u);
    }
}
 
void rebuild(){
    int tot2=tot;
    tot=0;
    for(int i=0;i<=n;i++) first[i]=-1;
    for(int i=0;i<tot2;i++){
        int u=belong[edge[i].s];
        int v=belong[edge[i].e];
        if(u==v) continue;
        deg[v]++;
        addedge(u,v);
    }
}
 
void dfs(int u,int fa){
    for(int i=sz[u];i<=m;i++) dp[u][i]=sz[u];
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=m;j>=sz[u];j--)
        for(int k=0;k<=j-sz[u];k++)
            dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k]);
    }
}
int u;
 
int main(){
    //freopen("in.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        init(n);
        for(int i=1;i<=n;i++){
            scanf("%d",&u);
            addedge(u,i);
        }
        for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
        rebuild();
        int rt=0;sz[0]=0;
        for(int i=1;i<=block;i++) if(!deg[i]) addedge(rt,i);
        dfs(rt,-1);
        printf("%d\n",dp[rt][m]==-1?0:dp[rt][m]);
    }
    return 0;
}
View Code

 

posted @ 2016-08-31 00:56  Septher  阅读(4699)  评论(2编辑  收藏  举报