• 重建道路


    传送门

    这道题对我算是意义非凡
    毕竟这是我独立自主AC的第一道动规较难题

    解法:

    这道题想想就想到了树上背包

    于是我 爽快地温习了一下树上背包 爽快地写完代码 爽快地得了72分。。。

    然后我才发现
    这道题tm的不是考树上背包
    而是考特判

    先说说背包咋写
    设dp[i][j]表示第i个节点的位置删掉j个点需要次数 这个设法有点神奇。。。
    因为要删n-p个点 为了方便 我把读入的p赋为n-p
    树上背包的转移在此就不详细说 具体看代码
    最后分离出包含根节点的树的操作数即为dp[root][p]

    要特判是因为可以把子树分离出去
    对于第i点的子树 要分离出p个点
    就要减去子树中sz[i]-p个点 再断掉与父亲的连边
    操作数就为dp[i][sz[i]-p]+1(实际上此时p为n-p 因为上面的将p赋为了n-p)

    代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<cmath>
    #include<queue>
    #include<map>
    #define inf 2000000000
    #define min(x,y) ((x)<(y)?(x):(y))
    #define max(x,y) ((x)>(y)?(x):(y))
    #define rep(i,a,b) for(int i=(a);i<=(b);++i)
    #define dwn(i,a,b) for(int i=(a);i>=(b);--i)
    using namespace std;
    typedef long long ll;
    bool vis[160];
    int n,p,root,minn=inf,sz[160],par[160],dp[160][160];
    struct EDGE
    {
        int to,nxt;
    }edge[310];
    int head[160],tot=1;
    void add(int u,int v)
    {
        edge[++tot].to=v;
        edge[tot].nxt=head[u];
        head[u]=tot;
    }
    void dfs(int x)
    {
        vis[x]=1,sz[x]=1;
        for(int i=head[x];i;i=edge[i].nxt)
        {
            int y=edge[i].to;
            if(vis[y]) continue;
            dfs(y);
            sz[x]+=sz[y];
        }
        vis[x]=0;
    }
    void get_dp(int x)
    {
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].nxt)
        {
            int y=edge[i].to;
            if(vis[y]) continue;
            get_dp(y);
            dwn(j,p,1)//树上背包,倒序很重要
            {
                rep(k,0,j)
                {
                    dp[x][j]=min(dp[x][j],dp[x][k]+dp[y][j-k]);
                }
            }
        }
        dp[x][sz[x]]=1;
        vis[x]=0;
    }
    int main()
    {
        scanf("%d%d",&n,&p);
        p=n-p;
        rep(i,1,n-1)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            vis[v]=1;
            par[v]=u;
            add(u,v),add(v,u);
        }
        rep(i,1,n)
        {
            if(!vis[i])
            {
                root=i;
            }
            vis[i]=0;
        }
        dfs(root);
        memset(dp,127/3,sizeof(dp));
        rep(i,1,n) dp[i][0]=0;
        get_dp(root);
        rep(i,1,n)
        {
            if(i==root) continue;
            if(sz[i]==n-p)//若 i不为根节点 这时把i子树直接分离只需一步肯定最优 其实不一定要写这个特判 下面minn操作其实包含了这种
            {
                printf("1
    ");
                return 0;
            }
            minn=min(minn,dp[i][sz[i]-n+p]+1);//分离子树操作
        }
        printf("%d
    ",min(minn,dp[root][p]));
        return 0;
    }
    
    
  • 相关阅读:
    三位水仙花数
    常用Json
    毫秒数日前格式化
    常用ajax请求
    T-SQL应用,视图、存储过程、触发器、游标、临时表等
    SQL2-子查询、join查询
    SQL1-(增删改查、常用函数)
    html回车事件
    插入数据,返回最新id
    iframe高度自适应
  • 原文地址:https://www.cnblogs.com/MYsBlogs/p/11048010.html
Copyright © 2020-2023  润新知