1. 程式人生 > >雅禮 noip2018 模擬賽 day3 T3

雅禮 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=min(add(p,dp[to][0]),add(q,dp[to][1]))

q=min(add(p,dp[to][1]),add(q,dp[to][0]))

其中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;
}