F. Maximum Weight Subset(贪心or树形dp解法)

题:https://codeforces.com/contest/1249/problem/F

题意:给一颗树,边权为1,节点有点权,问取到一个点集,俩俩之间路径超过k,是点权和最大

思路:贪心地取点,先将点按照深度经行排序,每一次,取一个点权大于0的点,然后对于这个点bfs出去的路径小于k的点减去当前点的a[u],然后将a[i]加入到ans中

#include<bits/stdc++.h>
using namespace std;
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=b;i>=a;i--)
#define pb push_back
const int M=250;
int deep[M],vis[M],a[M],b[M],k,n;
vector<int>g[M];
void dfs(int u,int f){
    deep[u]=deep[f]+1;
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v!=f){
            dfs(v,u);
        }
    }
}
bool cmp(int x,int y){
    return deep[x]>deep[y];
}
struct node{
    int val,st;
};
int bfs(int st){
    queue<node>que;
    node s;
    s.val=st;
    s.st=0;
    que.push(s);
    memset(vis,0,sizeof(vis));
    int c=a[st];
    while(!que.empty()){
        node u=que.front();
        vis[u.val]=1;
        a[u.val]-=c;
        que.pop();
        if(u.st!=k){
            for(int i=0;i<g[u.val].size();i++){
                int v=g[u.val][i];
                if(!vis[v]){
                    node p;
                    p.val=v;
                    p.st=u.st+1;
                    que.push(p);
                }            
            }
        }
        
    }
    return c;
}
int main(){
    
    scanf("%d%d",&n,&k);
    fo(i,1,n)
        scanf("%d",&a[i]);
    fo(i,1,n-1){
        int x,y;
        scanf("%d%d",&x,&y);
        g[x].pb(y);
        g[y].pb(x);
    }
    dfs(1,0);
    fo(i,1,n)b[i]=i;
    sort(b+1,b+1+n,cmp);
    int ans=0;
    fo(i,1,n)
        if(a[i]>0)
            ans+=bfs(i);
    cout<<ans<<endl;
    return 0;
}
View Code

 树形dp做法

dp[u][i]表示以u的子树中被选的点离u最近距离是i的最大权值和;

对当前节点,我们分取和不取的情况;

取的情况:那么当前点的子树对当前点的贡献肯定就是∑dp[v][k];

不取的情况:那么我们就既要保证子树之间的距离要大于k,又要满足子树对当前节点距离为i的贡献。若一个子树取了距离为i-1,那么就要保证其他子树取的距离都要大于k-i。

      当平衡的时候我们得到一个式子k-(k-i+1) = i-1,所以要在k-i和i-1中取最大值,作为要对当前节点u做贡献的v的距离j,也就是dp[v][j]。

      分析一下,当j取i-1的时候要求其他子树与当前节点u的距离要大于k-(i-1),因为在上面 j 已经取了(k-i)和(i-1)的最大值,所以不用考虑其他,直接把dp[v][j]加上去;

              当 j 取k-i时,我们发现实际上,k-i一定比i-1更深,所以dp[v][k-1]<=dp[v][i-1],所以取值上虽然取的时深度为k-i深度的值,此时我们可以假想取的时i-1的位置,

           所以就满足了距离为 i 的限制,接着,又因为只是要大于k而已,我们取的深度明显大于k一些,所以为了最大化答案,就取max(dp[v][i-1]-dp[v][j])。只是一个子树达到了 i-1 ,

           这并不会影响俩俩子树之间距离大于k的事实

#include<bits/stdc++.h>
using namespace std;
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=b;i>=a;i--)
#define pb push_back
const int M=220;
vector<int>g[M];
int n,k;
int a[M];
int dp[M][M];//dp[u][i]表示以u的子树中被选的点离u最近距离是i的最大权值和
void dfs(int u,int f){
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i];
        if(v==f)
            continue;
        dfs(v,u);
    }
    //cout<<"!!"<<endl;
    for(int i=n-1;i>=0;i--){
        if(i==0){//u不取的情况 
            for(int p=0;p<g[u].size();p++){
                int v=g[u][p];
                if(v==f)
                    continue;
                dp[u][i]+=dp[v][k];
            }
            dp[u][i]+=a[u];
        }
        else{
            int j=max(i-1,k-i);
            int tot=0,mx=0;
            for(int p=0;p<g[u].size();p++){
                int v=g[u][p];
                if(v==f)
                    continue;
                tot+=dp[v][j];
                mx=max(mx,dp[v][i-1]-dp[v][j]);
            }
            dp[u][i]=tot+mx;
        }
        dp[u][i]=max(dp[u][i],dp[u][i+1]);
    }
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int x,y,i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        g[x].pb(y);
        g[y].pb(x);
    }
    dfs(1,0);
    printf("%d\n",dp[1][0]);
    return  0;
}
View Code

 

posted @ 2019-10-23 20:10  starve_to_death  阅读(306)  评论(0编辑  收藏  举报