w

前言

几乎送我退役的一道题

仔细想想其实不难 水一篇博客吧

题目

样例输入

样例输入1

5
2 1 1 0
1 3 0 1
2 4 1 2
5 2 1 1

样例输出1

1 2

样例解释1

操作路径 {2, 1, 3}。

样例输入2

3
1 3 1 2
2 1 0 0

样例输出2

0 0

样例输入3

6
1 3 0 1
1 2 0 2
2 4 1 0
4 5 1 0
5 6 0 2

样例输出3

1 4

什么破题,OJ上都没有!

讲解

首先我们可以很容易证明一个结论:每条边最多只会被翻转一次

为了讲解方便,我们令边权为0,1的边分别为不需要和需要翻转的边,2为无所谓的边

我们来从易到难解决问题

对于第四组数据,不用脑子,输出0即可

对于第三组数据,由于我们优先要满足操作次数少,所以我们要将被1边夹住的2边当做1边,而与0边相邻的2边当做0边,即可最优

对于第五组数据,是一个比较简单的树形DP

根据我们之前得到的结论,令dp[x]为使得x的子树内的边全部到达目标状态需要的操作数量和翻转长度

红色:不需要翻转的链,橙色:需要翻转的链

我们发现每两条需要翻转的链可以合并成一条,所以我们再用一个数组统计是否有从下面来的橙链,然后合并累加答案即可

对于第一组数据,我们可以枚举翻转的边,然后用上述方法求答案即可

对于第二组数据,同理,枚举是否翻转d=2的边,然后求答案

80pts了!

其实正解已经不远了,甚至跟部分分很相似

我们仍然可以从组合橙链来思考,令(dp[x][0/1])表示在节点x,是否翻转父边的答案(操作数与长度都要存)

转移详情见代码

代码

struct node
{
	int f,len;
	node(){}
	node(int f1,int len1){
		f = f1;
		len = len1;
	}
	node operator + (const node &a){
		return node(f+a.f,len+a.len);
	}
	bool operator < (const node &a)const{
		if(f < a.f || (f == a.f && len < a.len)) return 1;
		return 0;
	}
}dp[MAXN][2];

int head[MAXN],tot;
struct edge
{
	int v,w,nxt;
}e[MAXN << 1];
void Add_Edge(int x,int y,int z)
{
	e[++tot].v = y;
	e[tot].w = z;
	e[tot].nxt = head[x];
	head[x] = tot;
}
void Add_Double_Edge(int x,int y,int z)
{
	Add_Edge(x,y,z);
	Add_Edge(y,x,z);
}

void dfs(int x,int fa,int d)//当前节点,父节点,父边的翻转状态 
{
	node t1 = node(INF,INF),t2 = node(0,0);//t1:有剩余橙链,t2:无剩余橙;链 
	//这个循环是在组合子节点的状态 
	for(int i = head[x]; i ;i = e[i].nxt)
	{
		if(e[i].v == fa) continue;
		dfs(e[i].v,x,e[i].w);
		node tmp1 = Min(t1+dp[e[i].v][0],t2+dp[e[i].v][1]);//1条橙链与0条橙链,0条橙链与1条橙链 组合是1条橙链 
		node tmp2 = Min(t1+dp[e[i].v][1],t2+dp[e[i].v][0]);//1条橙链与1条橙链,0条橙链与0条橙链 组合是0条橙链 
		t1 = tmp1;
		t2 = tmp2;
	}
	//更新父边不翻转状态 : 
	if(d == 1) dp[x][0] = node(INF,INF);
	//父边必须被翻转,不能不翻,GG 
	else dp[x][0] = Min(node(t1.f+1,t1.len),t2);
	//父边不一定被翻转,即可从 “有一条剩余橙链并把它翻转”与 “无剩余橙链 ”转移而来 
	
	//更新父边要翻转状态 : 
	if(!d) dp[x][1] = node(INF,INF);
	//父边不能翻转,GG 
	else dp[x][1] = Min(node(t1.f,t1.len+1),node(t2.f+1,t2.len+1));
	//父边不一定不翻转,即可从“与儿子的一条剩余橙边组合” 与 “无剩余橙链,翻转自己” 转移而来 
}

int main()
{
//	freopen("w.in","r",stdin);
//	freopen("w.out","w",stdout);
	n = Read();
	for(int i = 1;i < n;++ i)
	{
		int u = Read(),v = Read(),c = Read(),d = Read();
		if(d == 2) Add_Double_Edge(u,v,2);
		else Add_Double_Edge(u,v,c^d);
	}
	dfs(1,0,2);
	Put(dp[1][0].s >> 1,' ');Put(dp[1][0].len);
	//由于我们在合并两条橙链的时候,两条链都被统计进了方案,但是我们是要合并的,所以要除以二 
	return 0;
}
原文地址:https://www.cnblogs.com/PPLPPL/p/13567154.html