Codevs 1421:秋靜葉&秋穣子——題解

勇者時常喜歡玩遊戲,還喜歡用路由器的錢買遊戲,還喜歡用路由器的電腦玩遊戲。
有一天,他玩到了東方project的同人遊(題)戲(目)
——————————————————————

題目描述 Description

在幻想鄉,秋姐妹是掌管秋天的神明,作爲紅葉之神的姐姐靜葉和作爲豐收之神的妹妹穰子。如果把紅葉和果實聯繫在一起,自然會想到烤紅薯。烤紅薯需要很多的葉子,才能把紅薯烤得很香,所以秋姐妹決定比比誰能夠收集到最多的紅葉。靜葉將紅葉分成了N堆(編號1..N),並且規定了它們的選取順序,剛好形成一顆有向樹。在遊戲過程中,兩人從根節點開始,輪流取走紅葉,當一個人取走節點i的紅葉後,另一個人只能從節點i的兒子節點中選取一個。當取到某個葉子時遊戲結束,然後兩人會比較自己得到的紅葉數量。已知兩人採用的策略不一樣,靜葉考慮在讓穰子取得儘可能少的前提下,自己取的最多;而穰子想得是在自己儘可能取得多的前提下,讓靜葉取得最少。在兩人都採取最優策略的情況下,請你計算出遊戲結束時兩人的紅葉數量。
遊戲總是靜葉先取,保證只存在一組解。

輸入描述 Input Description
第1行:1個正整數N,表示紅葉堆數
第2行:N個整數,第i個數表示第i堆紅葉的數量num[i]
第3..N+1行:2個正整數u,v,表示節點u爲節點v的父親

輸出描述 Output Description
第1行:2個整數,分別表示靜葉取到的葉子數和穰子取到的葉子數

樣例輸入 Sample Input
6
4 16 16 5 3 1
1 2
2 4
1 3
3 5
3 6

樣例輸出 Sample Output
7 16

數據範圍及提示 Data Size & Hint
數據範圍
  對於30%的數據:1 ≤ N ≤ 100,1 ≤ num[i] ≤ 100
  對於60%的數據:1 ≤ N ≤ 10,000,1 ≤ num[i] ≤ 10,000
  對於100%的數據:1 ≤ N ≤ 100,000,1 ≤ num[i] ≤ 10,000

提示
 樣例解釋:
 首先靜葉一定能取得節點1的4片紅葉,留給穰子的是節點2和3,均爲16片紅葉。
 若選取節點2則靜葉下一次可以最多得到5片紅葉,而選擇3靜葉最多也只能得到3片紅葉,
 所以此時穰子會選擇節點3,故靜葉最後得到的紅葉數爲7,穰子爲16。

注意:
 保證兩人得到的紅葉數在[0, 2^31-1]。


勇者玩了2^31-1次都沒有成功,於是找到了路由器。

路由器表示,這題剛開始想的思路還是蠻對的,這題是一道dp+樹+博弈論的題。

dp的含義也很簡單想,數組dp[i][0]表示以i爲根先手能獲得的葉子數量,數組dp[i][1]表示以i爲根後手能獲得的葉子數量。

然後爲了知道當前實際操作的人是誰,我們用dep記錄當前的深度,dep%2==1就是靜葉走,反之爲穰子。

從下往上搜,一直推到dp[root(根節點)]就行啦!

……好的,到這裏路由器就不會往下推了,看了題解。
尼瑪怎麼題解都是清一色的!

這裏複製一下給大家看,能看懂的人就不用往下看了(都是巨佬TAT):

當dep爲奇[靜葉] (設dep[root]=1)
dp[i][0]=dp[k][1]+num[i]
dp[i][1]=f[k][0]
k是i的兒子,且代表dp[k][0]取最大時的k,當dp[k][0]相同時,取dp[k][1]最小。(穰子最優)
當dep爲偶時[穰子]
dp[i][0]=dp[k][1]+num[i]
dp[i][1]=dp[k][0]
k是i的兒子,且代表dp[k][1]取最小時的k,當dp[k][1]相同時,取dp[k][0]最大。(靜葉最優)

還有一個版本可能更好理解?也粘在下面了(至少我是看這個看懂的):

對於depth&1==1的情況:
dp[i][0]=Num[i]+dp[k][1];
dp[i][1]=dp[k][0];
k是i的兒子,k爲max{dp[k][0]}取得最大時的k,dp[k][0]相同時,取dp[k][1]最小的;
即我是先手(全局的先手),我只能取奇數深度的根節點,然後我的對手便會按照他的準則來取。他的準則便是讓自己儘量多,所以他會在保證dp[k][0](他是全局的後手,那麼他便是以k爲根子樹的先手)最大的情況下,來讓dp[k][1]最小,即讓我儘量少。

對於depth&1==0的情況:
同樣地:dp[i][0]=Num[i]+dp[k][1];
dp[i][1]=dp[k][0];
k是i的兒子,k爲min{dp[k][1]}取得最小時的k,dp[k][1]相同時,取dp[k][0]最大的。
即我是後手(全局的後手),我只能取偶數深度的根節點,而當前節點深度正好爲偶數。我對手的準則讓我儘量少,所以他會在保證dp[k][1]儘量小的情況下(我是全局後手,又k爲奇數節點,所以我爲k的後手),讓dp[k][0]儘量大,即讓我的對手儘量多。

——————————————————

大佬們看懂了嗎(至少我開始時沒聽懂)?

好的如果沒懂的話,我們來複述一遍過程。

我現在在k節點。k的兒子統一叫i。

假設我是靜葉(全局的先手),我知道對手的策略是:在自己儘可能取得多的前提下,讓靜葉取得最少。那麼她一定會走我所在節點的兒子當中葉子最多的(如果有多個,則走會使得我最終得到葉子最少的節點)。那麼對應的,就是先保證dp[i][0]是所有i中最大的,如果有多個最大,那麼讓dp[i][1]最小。走符合上述條件的點i即可

假設我是穰子(全局的後手),我知道對手的策略是:在讓穰子取得儘可能少的前提下,自己取的最多。那麼她一定走會使得我最終得到葉子最少的節點(如果有多個,則走我所在節點的兒子當中葉子最多的)。那麼對應的,就是先保證dp[i][1]最小,如果有多個最小,那麼讓dp[i][0]是所有i中最大的。走符合上述條件的點i即可

那麼我們就確定了我們接下來要走的節點i,有如下關係:

dp[k][0]=dp[i][1]+num[k];//繼承下次自己的操作所獲得的的葉子+自己所在的節點的全部葉子。
dp[k][1]=dp[i][0];//繼承下次自己的操作所獲得的葉子。

好的我們來處理幾個小事情。
1.dp初始化什麼呢?
答:你有兩個選擇:
1.你可以按照下面的代碼一樣,所有dp都是0,然後我們存節點數的t=0;//這個不太好想,推薦第二種
2.當我們到最後一個節點即走不下去的時候,我們很明顯的發現,dp[i][0]=num[i],dp[i][1]=0;

2.如何找根節點呢?
答:很簡單,我們可以找到沒有父親的那個節點,那個節點就是root。

3.我莫名其妙的就TLE怎麼辦?
答:多半是由於你使用了找父親的方法找兒子所以會TLE,所以果斷使用鏈式前向星。

4.有兩個點死活沒過去,和標準答案差了兩位數怎麼辦
答:INF開大點,開到九位數。

那麼來看代碼吧。
以上,就是路由器兩週的成果……(路由器想罵人怎麼辦?)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;
int fa[100001]={0},num[100001];
int dp[100001][2]={0};
int n;
int cnt=0,head[100001]; 
struct{
    int to;
    int next; 
}edge[100001];
void add(int u,int v){//u起點v終點
    cnt++;
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
const int INF=999999999;//注意開大點!!!
void dfs(int k,int dep){
    int minn=INF,maxn=-1;
    int l=0;
    if(dep%2==1){
        for(int i=head[k];i!=0;i=edge[i].next){
            int t=edge[i].to;
            dfs(t,dep+1);
            if(dp[t][0]>maxn||(dp[t][0]==maxn&&dp[t][1]<minn)){
                maxn=dp[t][0];
                l=t;
                minn=dp[t][1];
            }
        }
        dp[k][0]=dp[l][1]+num[k];
        dp[k][1]=dp[l][0];
    }
    else{
        for(int i=head[k];i!=0;i=edge[i].next){
            int t=edge[i].to;
            dfs(t,dep+1);
            if(dp[t][1]<minn||(dp[t][0]>maxn&&dp[t][1]==minn)){
                minn=dp[t][1];
                l=t;
                maxn=dp[t][0];
            }
        }
        dp[k][0]=dp[l][1]+num[k];
        dp[k][1]=dp[l][0];
    }
    return;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
    }
    for(int i=1;i<=n-1;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        fa[v]=1;
        add(u,v);
    }
    int qi;
    for(int i=1;i<=n;i++){
        if(fa[i]==0){
            qi=i;
            break;
        }
    }
    dfs(qi,1);
    printf("%d %d",dp[qi][0],dp[qi][1]);
    return 0;
}
發佈了59 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章