[CF1670E]Hemose on the Tree 题解
作者:互联网
Preface
还是不会构造题啊>_<
发现性质的能力还是弱了点。
Analysis
直接说这题的结论:异或和的最小最大值为 \(n\)。
很简单,只要存在一个点和它连出去的边,两个的权值一个 \(\ge n\),一个 \(\lt n\),由于 \(n = 2^p\),珂以推出两者的异或和 \(\ge n\)。
而这样的一个点显然存在,不然所有点和边的权值没法构成一个 \(1\sim 2n-1\) 的排列。
题目中两个样例的异或和最小最大值均为 \(n\),那么我们可以考虑构造一个图使其满足这点。
首先思考异或和 \(\le n\) 的条件,对于相连的点和边,它们的权值肯定满足以下两者之一:
-
两个权值都小于 \(n\) 或不小于 \(n\)。
-
一个权值不小于 \(n\),一个权值小于 \(n\)。
因为要构造 \(1\sim 2n-1\) 的排列,如果都小于 \(n\) 后续会比较难处理,而且第二个条件看着就比较像构造题常用的套路,循环往复,一个不小于 \(n\),一个小于 \(n\),接下来的一个又不小于 \(n\),如此往后推导。
所以考虑让所有点边满足第二个条件。
下面就需要一些经验和大胆的猜测与尝试了。
如果我们直接把根节点权值设为 \(n\) 会怎样?
显然接下来的边权就要大于 \(n\) 了,设为 \(n+k(1 \le k\lt n)\),那么下面连着的点权我们可以将其设为 \(k\)。
推一推可以发现,有一种构造方法能满足题目要求:
-
当前点异或和为 \(n\) 时,下面的边权设为 \(n+k\),连着的点为 \(k\)。
-
当前点异或和为 \(0\) 时,下面的边权设为 \(k\),连着的点为 \(n + k\)。
(注意,上述 \(k\) 是动态更新的,即每次递归到下一层 \(k\gets k+1\))。
只要这样构造,异或和只有三种情况:\(0,k,n\),
这样就满足了最小最大值为 \(n\),而且根节点没有限制,直接指定 \(1\) 为根节点就好啦~
时间复杂度 \(O(N)\)。
Code
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
#define mp make_pair
using namespace std;
const int maxn = 1e6 + 5;
int p,n;
vector<pair<int,int> > g[maxn];
int ans1[maxn],ans2[maxn],tot;
void dfs(int u,int flag,int fa) {
for(auto p : g[u]) {
int v = p.fir,w = p.sec;
if(v == fa)continue ;
++ tot;
ans2[w] = flag ^ tot;
ans1[v] = flag ^ tot ^ n;
dfs(v , flag ^ n , u);
}
return ;
}
void work() {
scanf("%d",&p);
n = 1 << p;
tot = 0;
for(int i = 1;i <= n;++ i)g[i].clear();
for(int i = 1;i < n;++ i) {
int u,v;
scanf("%d%d",&u,&v);
g[u].pb(mp(v , i));
g[v].pb(mp(u , i));
}
ans1[1] = n;
dfs(1 , n , 0);
puts("1");
for(int i = 1;i <= n;++ i)printf("%d ",ans1[i]);
puts("");
for(int i = 1;i < n;++ i)printf("%d ",ans2[i]);
puts("");
return ;
}
int main() {
int T;
scanf("%d",&T);
while(T --)work();
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿
标签:小于,int,题解,CF1670E,Tree,tot,异或,权值,define 来源: https://www.cnblogs.com/663B/p/16466053.html