1. 程式人生 > >UVA-1220-Party at Hali-Bula && UVA-1218-Perfect Service(樹形DP)

UVA-1220-Party at Hali-Bula && UVA-1218-Perfect Service(樹形DP)

UVA-1220-Party at Hali-Bula

題意:

一個公司員工要舉行聚會,要求任意一個人不能和他的直接上司同時到場,一個員工只有一個支系上司,現在求最多有多少人到場,並且方案是否唯一(紫書282頁)

分析:

紫薯寫的很清楚,而且也很基礎,就不重複了,只做幾點記錄和總結

  • 輸入中輸入的是名字,每個名字要和一個id對應,當然最容易想到的就是map。但是還需要注意一點,就是不能保證輸入的順序,也就是說如果首先輸入的父節點之前沒有出現過,那麼就沒辦法獲取到父節點的id。建樹時要考慮輸入資料的順序(或者是重複輸入的)
  • 關於此題,因為要判斷唯一性,所以不是單純的樹的最大獨立點集問題。而新增的陣列f
    ,則可以很方便的把唯一性的狀態儲存下來。
#define maxn 210
vector<int> v[maxn];
map<string,int> M;
int n;
int ans,res;
int dp[maxn][2],f[maxn][2];
void dfs(int u)
{
    dp[u][0] = 0;
    dp[u][1] = 1;
    f[u][0] = f[u][1] = 1;
    int l = v[u].size();
    //樹形dp基本框架
    for(int i=0;i<l;i++)
    {
        int y = v[u][i];
        dfs(y);
        dp[u][1] += dp[y][0];
        if(f[y][0]==0)f[u][1] = 0;
        
        dp[u][0] += max(dp[y][0],dp[y][1]);
        if(dp[y][0]==dp[y][1])f[u][0] = 0;
        else if(dp[y][0]>dp[y][1]&&f[y][0]==0)f[u][0] = 0;
        else if(dp[y][0]<dp[y][1]&&f[y][1]==0)f[u][0] = 0;
    }
}
//獲取對應id
int cnt;
int id(const string& str)
{
    if(!M.count(str))
        M[str] = ++cnt;
    return M[str];
}
int main()
{
    while(cin>>n)
    {
        if(n==0)break;
        for(int i=1;i<=n;i++)
            v[i].clear();
        //ans儲存人數,res儲存是否唯一
        ans = 0;res = 1;
        M.clear();
        cnt = 0;
        string a,b;
        cin>>a;
        id(a);
        //輸入的坑點就在這裡
        for(int i=2;i<=n;i++)
        {
            cin>>a>>b;
            v[id(b)].push_back(id(a));
            //M[a] = i;這是之前錯誤的寫法,因為我們不能保證M[b]一定存在。
            //v[M[b]].push_back(M[a]);
        }
        dfs(1);
        ans = max(dp[1][0],dp[1][1]);
        if(dp[1][0]==dp[1][1]||(dp[1][0]>dp[1][1]&&f[1][0]==0)||(dp[1][0]<dp[1][1]&&f[1][1]==0))
            res = 0;
        printf("%d %s\n",ans,res==0?"No":"Yes");
    }
    return 0;
}

UVA-1218-Perfect Service

題意:

有n個機器組成的樹形結構,要求一臺伺服器必須連線一臺電腦,求使用的最少的伺服器

分析

對於任意一個機器u,可以是伺服器也可以不是伺服器,由於有“不是伺服器的機器必須恰好與一個伺服器相鄰”的條件,又由於它是一棵樹,所以我們借用每個節點的父節點,來表示狀態。即分為三種情況

  • u是伺服器。那麼與u管聯的任何機器可以是伺服器也可以不是
  • u不是伺服器,u的父節點是伺服器。那麼u的所有子節點都不是伺服器
  • u不是伺服器,u的父節點不是伺服器。那麼u必須恰好有一個子節點是伺服器

動態轉移:

d[u][0] = sum{ min(d[v][0],d[v][1])}

d[u][1] = sum{d[v][2]}

d[u][2] = min(d[u][1]-d[v][2]+d[v][0])

第三個含義是這樣的:因為要求v是伺服器的情況的最小值,並且u的其他子節點都不是伺服器。d[u][0]儲存了u子節點都不是伺服器的最優解,d[v][2]儲存了v不是伺服器的最優解,d[v][0]儲存了v是伺服器的最優解。所以有上述式子可得d[u][2]的最優解

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3fffffff
#define maxn 10010
vector<int> v[maxn];
int n,ans;
int d[maxn][3];
void dp(int x,int p)
{
    d[x][0] = 1;
    d[x][1] = 0;
    d[x][2] = maxn;
    int l = v[x].size();
    for(int i=0;i<l;i++)
    {
        int y = v[x][i];
        if(y==p)continue;
        dp(y,x);
        d[x][0] += min(d[y][0],d[y][1]);
        d[x][1] += d[y][2];
    }
    //因為計算d[x][2]要用到d[x][1],而d[x][1]是累加計算,所以要分開兩次遍歷
    for(int i=0;i<l;i++)
    {
        int y = v[x][i];
        if(y==p)continue;
        d[x][2] = min(d[x][2],d[x][1]-d[y][2]+d[y][0]);
    }
}
int main()
{
    while(~scanf("%d",&n)&&n!=-1)
    {
        if(n==0)continue;
        for(int i=1;i<=n;i++)
            v[i].clear();
        int x,y;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&x,&y);
            v[x].push_back(y);
            v[y].push_back(x);
        }
        dp(1,0);
        //最後這裡的坑點,想一想為什麼沒有d[1][1]呢?
        ans = min(d[1][0],d[1][2]);
        cout<<ans<<endl;
    }
    return 0;
}