zoukankan      html  css  js  c++  java
  • [Usaco2002 Feb]Rebuilding Roads重建道路

    题目描述

    一场可怕的地震后,奶牛用N个牲口棚(1 <= N <= 150,编号1..N)重建了农民John的牧场。奶牛没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是唯一的。因此,牧场运输系统可以被构建成一棵树。John想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有P(1 <= P <= N)个牲口棚的子树和剩余子牲口棚分离,John想知道这些道路的最小数目。

    输入格式

    第1行:2个整数, N和P

    第2..N行:每行2个整数I和J,表示节点I是节点J的父节点。

    输出格式

    单独一行,包含一旦被破坏将分离出恰含P个节点的子树的道路的最小数目。


    直观的做法就是去掉一些点之后有没有出现大小为P的连通块。可以用暴搜来完成这个算法,但复杂度是不可接受的O(2^N * N)。如果记忆化应该能过,但代码不好写。

    分析题目。我们发现要让整棵树一下断出一个大小为P的连通块是很难的。但是好在我们可以多次断点。如果我们断掉若干个点,断去这些点都会给连通块减少一点大小,我们是一定可以得到想要的连通块的。所以这题的重点就在于一个点断不断的选择上。

    根据刚才"凑出P个点"的思想,我们可以设计出状态:dp(i,j)表示以i为根的子树中断掉j个点的最少次数。由于牧场是一棵树,最小值显然具有传递性。设节点u有k个儿子,并且设以节点u为根的子树的大小为size(u),那么传递性具体用状态转移方程表示就是:

    [dp[u][i]=Min_{1≤x≤k}{{}dp[u][i-j]+dp[son[x]][j]{}} ]

    由于在枚举到son(x)之前我们已经处理了若干个u的儿子,你可以理解为:在处理son(x)之前u的子树中添加了一棵新的son(x)的子树。那么加号前面就可以理解为:在son(x)之前的子树中断i-j个点,再在son(x)的子树中断j个点。

    考虑边界情况。

    显然dp(u,0)=0,dp(u,size(u))=1。第二句表示把整棵u的子树都断掉,那么只需要断掉u和其父亲节点的连线即可。

    边界情况告诉我们,在计算dp数组前我们就需要求出size数组。所以这道题需要两次dfs来完成:一个求size,一个求dp。

    然后我们枚举每个节点,考虑以这些点为根的所有子树来计算答案。假设答案就是在节点i的子树中断掉一些点而形成,那么答案就是dp(i,size(i)-q)+dp(i,size(i))。加上后一项的原因是我们考虑答案就在i的子树中,所以我们要把i和其父亲断开。然后枚举每个i,求最小值即可。

    需要注意的细节有:

    1.默认根节点为1,那么初始化时dp(1,size(1))应等于0,因为它没有父亲。

    2.最后统计答案时应注意size(i)≥q。

    时间复杂度为O(N^2 * Q)

    * 代码中用m代替了q(个人习惯)

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #define maxn 151
    #define maxm 21
    using namespace std;
     
    struct edge{
        int to,next;
        edge(){}
        edge(const int &_to,const int &_next){ to=_to,next=_next; }
    }e[maxn<<1];
    int head[maxn],k;
     
    int dp[maxn][maxn],size[maxn];
    int n,m;
     
    inline int read(){
        register int x(0),f(1); register char c(getchar());
        while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
        while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        return x*f;
    }
    inline void add(const int &u,const int &v){ e[k]=edge(v,head[u]),head[u]=k++; }
     
    void dfs1(int u,int pre){
        size[u]=1;
        for(register int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(v==pre) continue;
            dfs1(v,u);
            size[u]+=size[v];
        }
        dp[u][0]=0,dp[u][size[u]]=1;
    }
     
    void dfs2(int u,int pre){
        for(register int i=head[u];~i;i=e[i].next){
            int v=e[i].to;
            if(v==pre) continue;
            dfs2(v,u);
            for(register int i=size[u]-1;i>=1;i--){
                for(register int j=0;j<=i;j++){
                    dp[u][i]=min(dp[u][i],dp[u][i-j]+dp[v][j]);
                }
            }
        }
    }
         
     
    int main(){
        memset(head,-1,sizeof head);
        n=read(),m=read();
        for(register int i=1;i<n;i++){
            int u=read(),v=read();
            add(u,v),add(v,u);
        }
     
        memset(dp,0x3f,sizeof dp);
        dfs1(1,0),dp[1][n]=0,dfs2(1,0);
        int ans=0x3f3f3f3f;
        for(register int i=1;i<=n;i++) if(size[i]>=m) ans=min(ans,dp[i][size[i]-m]+dp[i][size[i]]);
        printf("%d
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    协变与逆变
    反射
    TreeCombo
    使用TreeCombo示例
    Calling R from java using JRI
    [转]相似度计算常用方法综述
    [转]聚类算法KMeans, KMedoids, GMM, Spectral clustering,Ncut
    Learning D3.js with App iLearning D3.js
    [repost ]经典的机器学习方面源代码库
    [转]数据挖掘中所需的概率论与数理统计知识、上
  • 原文地址:https://www.cnblogs.com/akura/p/10914260.html
Copyright © 2011-2022 走看看