雅禮 noip2018 模擬賽 day3 T3
典型樹形dp
這裡,我們應該看到一些基本性質:
①:如果這個邊不能改(不是沒有必要改),我們就不改,因為就算改過去還要改回來,顯然不是最優的
注意:“不能改”是指邊的性質和要求的相同而不包括對邊的顏色沒有要求的情況!
②:如果我們每翻轉一條邊,就認為將這條邊的兩個端點度數+1,那麼不難看到,最後翻轉的所有邊構成的路徑總數就是度數為奇數點個數的1/2
(性質②的證明:一條路徑只會對兩端的點產生度數上的影響,而中間的點都是+2,還是偶數,所以無影響)
接下來,我們進行dp:
記狀態f[i][0/1]代表以i為根節點的子樹,0/1代表根節點與父親的邊是否翻轉
那麼我們可以看到,這個dp包含兩部分內容,一部分要保證路徑數最少,另一部分要保證在滿足路徑數最小的前提下路徑總長度最小,所以我們需要同時更新這兩個值,這樣用pair就是一個比較好的選擇
接下來我們考慮更新:
在更新時我們使用兩個參量:p和q,作為更新dp的中間步驟,用p代表不以i為端點做鏈,q代表以i為端點做鏈,設i的某個子節點為to,於是有:
其中p初始化為(0,0),q初始化為(INF,INF)
解釋一下:這裡pair的add就是對應元素相加(手寫!非內建!)
而min操作表示先按pair第一關鍵字比較,再按第二關鍵字比較
那麼這一步就是一個合併的過程:
首先,i不作為鏈的端點:分兩類來合併:如果子節點與i的邊翻轉了,那麼就要累在以i為端點的鏈裡(因為i與父親的邊不翻轉,那麼i就將是個端點),如果子節點與i的邊沒有翻轉,那麼僅針對這棵子樹而言,i並沒有作為路徑的端點,所以更新沒有以i為端點鏈的代價
如果i作為鏈的端點,同樣分兩類來合併:如果子節點與i的邊翻轉了,那麼i顯然可以成為鏈的端點,前提是在此之前i並不是鏈的端點,所以用之前i不是鏈的端點的代價來更新;反之,如果子節點與i的邊沒有翻轉,那麼就此而言i並不是端點,可要求i是鏈的一個端點,這樣就必須用i原先就是鏈的端點的代價來更新
遍歷根節點所有子節點後,更新dp:
如果i與父親的邊沒有翻轉。即狀態dp[i][0]:
首先,i不作為鏈的端點肯定是一種可能性,直接比較
接著,如果i是鏈的一個端點,i和父節點的邊還沒有翻轉,那麼說明在這個狀態下i是真正的奇度點,所以將狀態q的first+1後更新
如果i與父親的邊翻轉了,同樣分兩類更新:
首先,i本身作為了鏈的端點,而由於i本身就是奇度點,所以僅需將q.second+1來更新即可
還有,如果i本身在下面並沒有作為鏈的端點,而i卻與父節點的邊發生了翻轉,所以i就成為了新的奇度點,同時鏈長還增加了,所以p.first,p.second均增加即可
最後答案即為dp[1][0].first/2,dp[1][0].second
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define INF 0x3f3f3f3f
using namespace std;
struct Edge
{
int next;
int to;
int val;
}edge[200005];
int head[100005];
int cnt=1;
pair <int,int> dp[100005][2];
int n;
void init()
{
memset(head,-1,sizeof(head));
cnt=1;
}
void add(int l,int r,int w)
{
edge[cnt].next=head[l];
edge[cnt].to=r;
edge[cnt].val=w;
head[l]=cnt++;
}
pair<int,int> addp(pair<int,int> a,pair<int,int> b)
{
return make_pair(a.first+b.first,a.second+b.second);
}
void dfs(int x,int fx,int typ)
{
pair <int,int> p,q;
p=make_pair(0,0);
q=make_pair(INF,INF);
for(int i=head[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(to==fx)
{
continue;
}
dfs(to,x,edge[i].val);
pair <int,int> temp1,temp2;
temp1=min(addp(p,dp[to][0]),addp(q,dp[to][1]));
temp2=min(addp(p,dp[to][1]),addp(q,dp[to][0]));
p=temp1;
q=temp2;
}
if(typ==2||typ==0)
{
dp[x][0]=min(p,make_pair(q.first+1,q.second));
}else
{
dp[x][0]=make_pair(INF,INF);
}
if(typ==2||typ==1)
{
dp[x][1]=min(make_pair(p.first+1,p.second+1),make_pair(q.first,q.second+1));
}else
{
dp[x][1]=make_pair(INF,INF);
}
}
int main()
{
freopen("w.in","r",stdin);
freopen("w.out","w",stdout);
scanf("%d",&n);
init();
for(int i=1;i<n;i++)
{
int x,y,z,w;
scanf("%d%d%d%d",&x,&y,&z,&w);
if(w==2)
{
add(x,y,w);
add(y,x,w);
}else
{
add(x,y,w^z);
add(y,x,w^z);
}
}
dfs(1,1,0);
printf("%d %d\n",dp[1][0].first/2,dp[1][0].second);
return 0;
}