玩个树
题解
很容易发现,如果有一条边需要保持不变,那么我们一定不会翻他。因为任意一个覆盖它的翻法一定可以被不覆盖它的翻法代替。
而需要使得所有的选择路径数量最少,所以我们如果可以将两条需要翻转的链一起翻转,那就将它们连在一起。于是,我们就得到了一个类似长链剖分的做法,在的时间复杂度内求出当前树的答案。可是我们有有一部分边的目标是随意的,于是我们便想到枚举这些边的目标,时间复杂度
,其中
表示
值为2的边的边数。
这样的做法可以过掉subtask1,2,5,而subtask3的链与subtask全部值为2的情况也很好想。
至于subtask6,这样的做法显然是不行的,但是我们可以从上面的思路得到一个新的树形dp的做法,将当前点与其父亲的边的翻转与否作为dp状态,设为在第i个点,与父亲的边的状态为0或1时当前子树的答案。
由于对于一个点,它子节点数奇偶是有对其有影响的。我们需要设置两个临时变量,分别记录有一条多余的链与没有多余链的两种情况下其的dp值,再用其去更新当前点的dp值。
最后的答案就是的值。
源码
#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;
}