hdu1054 Strategic Game(贪心,树形dp,二分匹配)

题意:

鲍勃喜欢玩战略游戏。现在有n个城市,他们构成了一棵树。鲍勃可以在某些城市派一个士兵守护,该士兵可以瞭望到所有与该城市相连的边。问鲍勃最少要派遣多少个士兵,才能把所有的边都瞭望到。

首先是树形dp的解法:

我们可以这样定义状态f[i]表示以i为子树的根所需建的最少的塔数

有两种状态f[i][0]表示不建塔,f[i][1]表示建塔

那么可以很显然地得到:

如果i不建塔,那么i的儿子j必须建塔,于是f[i][0]=sum(f[i][1])

如果i建塔,那么i的儿子j可建可不建,于是f[i][1]=min(f[j][0],f[j][1])+1;

通过递归自下而上来实现,代码如下:

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
const int MAXN=10000;
using namespace std;
int k,num,n,a,b,cnt,fir[MAXN+5],F[MAXN+5][2];
struct node{int e,next;}h[(MAXN<<1)+5];
struct node1{
    int ch,next;
}tree[MAXN+5];
bool vis[MAXN+5];

inline void Clear(){
    memset(h,0,sizeof h);
    memset(fir,0,sizeof fir);
    cnt=0;
    memset(F,0,sizeof F);
    memset(tree,0,sizeof tree);
    memset(vis,0,sizeof vis);
}
void dfs(int s){
    vis[s]=1;
    for(int i=fir[s];i;i=h[i].next)
    {
        if(!vis[h[i].e])
        {
            tree[h[i].e].next=tree[s].ch;
            tree[s].ch=h[i].e;
            dfs(h[i].e);
        }
    }
}
int tree_dp(int root,int flag){
    if(F[root][flag]>-1)
        return F[root][flag];
    if(!tree[root].ch)
    {
        if(flag) return F[root][flag]=1;
        else return F[root][flag]=0;
    }
    if(flag)
    {
        int sum=1;
        for(int i=tree[root].ch;i;i=tree[i].next)
            sum+=min(tree_dp(i,0),tree_dp(i,1));
        return F[root][flag]=sum;
    }
    else
    {
        int sum=0;
        for(int i=tree[root].ch;i;i=tree[i].next)
            sum+=tree_dp(i,1);
        return F[root][flag]=sum;
    }
}
inline void Read(int &Ret){
    char ch;bool flag=0;
    for(;ch=getchar(),ch<'0'||ch>'9';) if(ch=='-') flag=1;
    for(Ret=ch-'0';ch=getchar(),'0'<=ch&&ch<='9';Ret=Ret*10+ch-'0');
    flag&&(Ret=-Ret);
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        Clear();
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a);
            getchar(); getchar();
            scanf("%d",&k);
            a++; getchar();
            for(int j=1;j<=k;j++)
            {
                scanf("%d",&b); b++;
                F[a][0]=F[a][1]=F[b][0]=F[b][1]=-1;
                h[++cnt].e=a; h[cnt].next=fir[b]; fir[b]=cnt;
                h[++cnt].e=b; h[cnt].next=fir[a]; fir[a]=cnt;
            }
        }
        int s=1; dfs(s);
        tree_dp(s,1);
        tree_dp(s,0);
        printf("%d\n",min(F[s][0],F[s][1]));
    }

}

然后是贪心解法:

我总觉得贪心解法有点神奇,但是又是如此的有道理,贪心策略是:对于图中度数为1的点来说,在它上面放士兵很显然是

并不优的,但是呢,对于其相接的那个点,放士兵一定是最优的,这个是比较显然的,不证了。所以说,方法就是,通过度

数为1的节点,删除节点,又再在删去节点后的树中继续找度为1的节点,不断循环,直到不存在度为1的节点即可

代码:(当时没想出来,参考了一下网上的代码,跑得比树形dp快)

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>

const int MAXN=1505;
using namespace std;
int edge[MAXN]; queue<int> Q;
vector<int> tree[MAXN];
int ans,N,a,b,k; bool vis[MAXN];

int main()
{
    while(scanf("%d",&N)!=EOF)
    {
        memset(vis,0,sizeof vis);ans=0;
        memset(edge,0,sizeof edge);
        for(int i=1;i<=N;i++)
        {
            scanf("%d:(%d)",&a,&k);a++;
            for(int j=1;j<=k;j++)
            {
                scanf("%d",&b); b++;
                tree[a].push_back(b);
                tree[b].push_back(a);
                edge[a]++; edge[b]++;
            }
        }
        if(N==1){putchar('1'); putchar(10); continue;}
        for(int i=1;i<=N;i++)
            if(tree[i].size()==1) Q.push(i);
        while(!Q.empty())
        {
            int s=Q.front(); Q.pop();
            int e=tree[s][0]; //对于这个决策,肯定是选e更优
            if(vis[e]) continue;
            ans++; vis[e]=1;
            int len=tree[e].size();
            for(int i=0;i<len;i++)
            {
                edge[tree[e][i]]--;
                if(vis[tree[e][i]]) continue;
                if(edge[tree[e][i]]==1)
                    Q.push(tree[e][i]);
            }
        }
        printf("%d\n",ans);
        for(int i=1;i<=N;i++) tree[i].clear();
    }
}

二分匹配的解法容我再想想。。。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值