100道动态规划——37 UVA 1218 Perfect Service 树形DP 分析问题

        树形DP

        老实说一开始是考虑用点的个数减去最大独立集的点的个数,但是很快就发现不对,数据如下:

        6

        1 2

        1 3

        1 4

        1 5

        2 6

        在这个例子中我们很显然的发现节点2有两台服务器与之相连,是不符合题意的

        。。。然后就不会做了。

        定义状态dp[i][k]其中k表示该点的状态,k=0表示该点是一个服务器,k=1表示该点并不是一个服务器,但是其父亲是一个服务器,k=2表示该点和其父亲都不是服务器

        这三种情况对应着三个状态转移方程

        k=0的时候,对于节点i的子节点j,j既可以是服务器,也可以不是服务器,因此方程为dp[i][0]=sum(min(dp[j][0],dp[j][1]))+1 所求为服务器数量,要记得+上自己

        k=1的时候,因为其父亲是一个服务器,这就要求每一个子节点都必须不是服务器,不然该点就会有两个服务器与之相连,不合题意,因此方程是dp[i][1]=sum(dp[j][2])

        k=2的时候,因为其和其父亲都不是服务器,这就要求i的某一个子节点必须是服务器,而且其余的子节点必须不是服务器,因此有方程dp[i][2]=dp[j][0]+Σdp[L][2]  其中L代表i的儿子且L!=j,我们需要枚举j,对于每一次枚举j,我们都需要把那个Σ算出来,需要m次,m为儿子个数,同时枚举j也需要m次,一共需要m^2次。但是我们可以对这个式子变一下形来降低时间复杂度

        Σdp[L][2]=(Σdp[j'][2])-dp[j][2]这里的j'的取值范围是i的所有儿子,然后我们就可以发现,(Σdp[j'][2])这一项实际上就是dp[i][1],因此,新的状态转移方程就是

        dp[i][2]=min(dp[i][1]-dp[j][2]+dp[j][0])

        然后还有需要注意一点的就是,要注意溢出的情况,因为dp[i][2]是一个min表达式,我们通常会给dp[i][2]赋一个比较大初值,但是考虑这一组数据

        100

        1 2

        2 3

        2 4

        2 5

        .....

        2 100

        我们会发现,最优解就是在2点放一个服务器,但假若我们的dfs一开始是在1处放置了服务器,那么2是不放的,这就要求2的每一个子节点都必须不放服务器,2的每一个子节点都不会再有子节点了。。假若你的比较大的初值赋得不是很好的话,按照k=1的转移方程,dp[2][1]=sum(dp[j][2]),溢出了

        我的解决办法是,因为数据上限是10000,我的初值就是10010,比上限大一点,而且保证最坏情况下也达不到这个10010,当然了,你也可以给dp[2][1]特判,假若dp[2][1]一旦大于你的INF,又重新设置为INF

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int maxm=1E4+10;

int n,ind,dp[maxm][3],x,y,dfs(int i,int k,int fa);
vector<int> v[maxm];

int main(){
    ios_base::sync_with_stdio(0);
    while(cin>>n){
        memset(dp+1,-1,sizeof(dp[0])*n);
        for(int i=1;i<n;++i){
            cin>>x>>y;
            v[x].push_back(y);
            v[y].push_back(x);
        }
        for(ind=1;v[ind].size()!=1;++ind);

        cout<<min(dfs(ind,0,0),dfs(ind,2,0))<<endl;

        for(int i=1;i<=n;++i)
            v[i].clear();
        cin>>n;
        if(n==-1)
            break;

    }
    return 0;
}

int dfs(int i,int k,int fa){
    if(dp[i][k]!=-1)
        return dp[i][k];
    dp[i][k]=0;
    if(k==1){
        for(int j=0;j<v[i].size();++j)
        if(v[i][j]!=fa)
            dp[i][k]+=dfs(v[i][j],2,i);
    }else if(k==2){
        dp[i][k]=maxm;
        for(int j=0;j<v[i].size();++j)
        if(v[i][j]!=fa)
            dp[i][k]=min(dp[i][k],dfs(i,1,fa)-dfs(v[i][j],2,i)+dfs(v[i][j],0,i));
    }else{
        for(int j=0;j<v[i].size();++j)
        if(v[i][j]!=fa)
            dp[i][k]+=min(dfs(v[i][j],0,i),dfs(v[i][j],1,i));
        ++dp[i][k];
    }
    return dp[i][k];
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值