题意:
给定一颗树,可以往树上的每条边添加一个权值(这个权值可以非常非常大,不限范围),使得任意两个叶子节点之间的连线上的所有边权异或得到的结果都为0.
问这棵树上最少出现多少种不同的边权,最多出现多少种不同的边权
思路:
最小解是好找的,先说结论,如果这张图上存在任意两个叶子的路径长度为奇数(因为题干给定n>=3因此不去考虑n=2仅存在一条边的情况),则这张图的边权不能只通过一个数来实现。如果这张图上所有两个叶子节点的距离都为偶数,则这张图可以通过一个数值达到要求。
这个最小解只能为1或者3。
那么怎么来判断呢?我们先找到任意的一个叶子节点,从这个结点出发,dfs找到每个叶子节点到该点的距离,找是否存在d(st,x)为奇数的情况,若存在这样的节点x,则说明最小解为3。若不存在这样的节点,则说明最小解可以为1.
如果有d(st,x),d(st,y)都为偶数,设存在中转节点z在这两条路径上,则有一条x到y经过z的路径,d(x,y)=d(st,x)+d(st,y)-2*d(st,z)偶数减去偶数依旧为偶数。
最大解怎么找?
先看最普通的一条线的情况,我们给标上二进制的边权
如图所示,根据这样的分法,我们可以使得n个节点的(n-1)条边拥有n-1个不同赋值。以此类推,当长度增加时,我们只需要增加一位即可。
如果在中间连上叶子节点,如图
即一个节点上连接着的多个叶子节点的边权只能是相同的值。
如果排除掉这种情况,我们可以通过在前面多加一位数的方法,来使得这张图每条边权相异。
以此类推,如果排除掉一个节点连接着多个叶子的情况,则不同边权的数量就是图的边数。
因为一个节点上连接着的多个叶子节点的边权只能是相同的值,所以可以划掉那些重复的边,即结果=边数(n-1) - 重复的叶子的数量
代码实现:
我们通过DFS从任意叶子节点st出发,给每个节点x标上st到该点的距离d(st,x)用数组d[x]记录。
通过检查叶子节点的d[x]是否为单数,如果出现了单数的情况,则用flag标记,之后输出的最小解为3,否则为1.
我们通过cnt[x]来记录连接到x上的叶子节点的数量。
如果这个数量>1,则说明这个节点连接着重复的叶子节点。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;
const int MAXN = 1e5+5;
vector<int>edges[MAXN];
int deg[MAXN]={0};//记录每个节点的度
int cnt[MAXN]={0};//记录这个节点上挂了几个"叶子"
int d[MAXN];//记录当前节点到开始节点的距离
int n;
long long res=0;//记录最大结果
bool flag;//flag,标记是否存在奇数的叶子节点
int dfs(int x,int prev){//当前节点x,上个节点prev
if(deg[x]==1&&d[x]%2){//如果有距离为奇数的叶子节点,则用flag标记
flag = 1;
}
int siz = edges[x].size();
for(int v=0;v<siz;v++){//遍历从该点出发的所有边
if(edges[x][v]!=prev){//防止走回头路
res++;
d[edges[x][v]]=d[x]+1;//标记下个节点到起始节点st的距离
dfs(edges[x][v],x);//下个节点继续dfs
if(deg[edges[x][v]]==1) cnt[x]++;//res记录边数
}
}
if(d[x]==1) cnt[x]++;//因为开始节点st其实也是一个叶子节点,所以如果当前节点与st相邻,则cnt要++
res-=max(cnt[x]-1,0);//如果与当前节点相连的叶子节点大于1,则要减去重复的这些边
}
int main(){
scanf("%d",&n);
int u,v;
for(int i=1;i<n;i++){//正常读图
scanf("%d%d",&u,&v);
edges[u].push_back(v);
deg[u]++;
edges[v].push_back(u);
deg[v]++;
}
int st;
for(int i=1;i<=n;i++){//找到任意一个叶子节点作为起始节点st
if(deg[i]==1){
st = i;
break;
}
}
flag = 0;
dfs(st,-1);//从起始节点st开始进行dfs
if(flag) cout<<3<<' '<<res<<endl;
else cout<<1<<' '<<res<<endl;
}