勇者時常喜歡玩遊戲,還喜歡用路由器的錢買遊戲,還喜歡用路由器的電腦玩遊戲。
有一天,他玩到了東方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;
}