玩个树

玩个树

题解

很容易发现,如果有一条边需要保持不变,那么我们一定不会翻他。因为任意一个覆盖它的翻法一定可以被不覆盖它的翻法代替。

而需要使得所有的选择路径数量最少,所以我们如果可以将两条需要翻转的链一起翻转,那就将它们连在一起。于是,我们就得到了一个类似长链剖分的做法,在O\left(n \right )的时间复杂度内求出当前树的答案。可是我们有有一部分边的目标是随意的,于是我们便想到枚举这些边的目标,时间复杂度O\left(n2^s \right ),其中s表示d值为2的边的边数。

这样的做法可以过掉subtask1,2,5,而subtask3的链与subtask全部d值为2的情况也很好想。

至于subtask6,这样的做法显然是不行的,但是我们可以从上面的思路得到一个新的树形dp的做法,将当前点与其父亲的边的翻转与否作为dp状态,设dp_{i,0/1}为在第i个点,与父亲的边的状态为0或1时当前子树的答案。

由于对于一个点,它子节点数奇偶是有对其有影响的。我们需要设置两个临时变量,分别记录有一条多余的链与没有多余链的两种情况下其的dp值,再用其去更新当前点的dp值。

最后的答案就是dp_{1,0}的值。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<map>
using namespace std;
#define MAXN 100005
typedef pair<int,int> pii;
const int INF=0x3f3f3f3f;
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
} 
int n,head[MAXN],tot,ans1,ans2,d[MAXN];
bool vis[MAXN];pii dp[MAXN][2];
struct edge{int from,to,nxt,paid;}e[MAXN<<1];
void addEdge(int u,int v,int w){e[++tot]=(edge){u,v,head[u],w};head[u]=tot;}
pii operator + (const pii &a,const pii &b){
	return make_pair(a.first+b.first,a.second+b.second);
}
bool operator < (const pii &a,const pii &b){
	if(a.first==b.first)return a.second<b.second;
	return a.first<b.first;
}
pii Min(pii x,pii y){return x<y?x:y;}
void dfs(int u,int fa,int opt){
	pii t1=make_pair(INF,INF),tmp1;
	pii t2=make_pair(0,0),tmp2;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;dfs(v,u,e[i].paid);
		tmp1=Min(t1+dp[v][0],t2+dp[v][1]);
		tmp2=Min(t1+dp[v][1],t2+dp[v][0]);
		t1=tmp1;t2=tmp2;
	}
	if(!opt)dp[u][1]=make_pair(INF,INF);
	else dp[u][1]=Min(make_pair(t1.first,t1.second+1),make_pair(t2.first+1,t2.second+1));
	if(opt==1)dp[u][0]=make_pair(INF,INF);
	else dp[u][0]=Min(make_pair(t1.first+1,t1.second),t2);
}
int main(){
	read(n);
	for(int i=1;i<n;i++){
		int a,b,c,d;
		read(a);read(b);read(c);read(d);
		if(c==d)addEdge(a,b,0),addEdge(b,a,0);
		if(c!=d&&d!=2)addEdge(a,b,1),addEdge(b,a,1);
		if(c!=d&&d==2)addEdge(a,b,2),addEdge(b,a,2);
	}
	dfs(1,0,2);
	printf("%d %d",dp[1][0].first/2,dp[1][0].second);
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值