1. 程式人生 > 實用技巧 >#樹形dp#nssl 1469 W From 2020年8月12日提高組A組

#樹形dp#nssl 1469 W From 2020年8月12日提高組A組


分析

首先一些結論,每條邊最多被翻一次,而且由翻的邊所構成的連通塊答案就是度數為奇數的點的個數的一半,
因為在連通塊內必然選擇兩個葉子節點間的路徑翻是最優的,所以也就是選擇兩個度數為奇數的點,所以結論很顯然
\(dp[i][0/1]\)表示第\(i\)個點與其父親的邊翻或不翻時,以第\(i\)個點為根的子樹的最小代價(運算元\(x\)和路徑長度\(y\)
\(f[0/1]\)表示第\(i\)個點的兒子們是否影響第\(i\)個點的度數的最小代價(兒子們的答案之和)
那麼\(f[0]=\min \{f[0]+dp[son][0],f[1]+dp[son][1]\},f[1]=\min\{f[0]+dp[son][1],f[1]+dp[son][0]\}\)


考慮用\(f[0],f[1]\)更新\(dp[i][0/1]\),那麼\(dp[i][0]=\min\{(f[1].x+1,f[1].y),f[0]\},dp[i][1]=\min\{(f[1].x,f[1].y+1),(f[0].x+1,f[0].y+1)\}\)
最後輸出\(dp[1][0]\)


程式碼

#include <cstdio>
#include <cctype>
#include <cstdlib>
#define rr register
using namespace std;
const int inf=1e9,N=100011;
struct rec{
	int x,y;
	rec operator +(const rec &t)const{
	    return (rec){x+t.x,y+t.y};
	}
	bool operator <(const rec &t)const{
	    return x<t.x||(x==t.x&&y<t.y);
	}
}dp[N][2];
struct node{int y,w,next;}e[N<<1];
int as[N],k=1,n;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline rec Min(rec x,rec y){return x<y?x:y;}
inline void dfs(int x,int fa,int w){
	rr rec f1=(rec){0,0},f2=(rec){inf,inf},F1,F2;
	for (rr int i=as[x];i;i=e[i].next)
	if (e[i].y!=fa){
		dfs(e[i].y,x,e[i].w);
		F1=Min(f1+dp[e[i].y][0],f2+dp[e[i].y][1]);
		F2=Min(f1+dp[e[i].y][1],f2+dp[e[i].y][0]);
		f1=F1,f2=F2;
	}
	if (w==1) dp[x][0]=(rec){inf,inf};
	    else dp[x][0]=Min(f1,(rec){f2.x+1,f2.y});
	if (w==0) dp[x][1]=(rec){inf,inf};
	    else dp[x][1]=Min((rec){f1.x+1,f1.y+1},(rec){f2.x,f2.y+1});
}
signed main(){
	n=iut();
	for (rr int i=1;i<n;++i){
	    rr int x=iut(),y=iut();
		rr int z1=iut(),z2=iut();
	    rr int z=z2==2?z2:(z1^z2);
		e[++k]=(node){y,z,as[x]},as[x]=k;
		e[++k]=(node){x,z,as[y]},as[y]=k;
	}
	dfs(1,0,0);
	return !printf("%d %d",dp[1][0].x>>1,dp[1][0].y);
}