2020牛客多校第五场B题Graph(最小异或和生成树 字典树 贪心 分治)
题目链接 https://ac.nowcoder.com/acm/contest/5670/B
题意:输入一个n点不含环的图,你可以加、删边不限次数,但是要保证1.图是连通的2.成环的边异或值为0,输出最小的图的边值和。
题解:我们以第一个点为根求最小异或和生成树。首先,我们以第一个点数字0为根,dfs求出下面所有边与父亲边的异或和关系,套用最小异或生成树板子就可以过题。
最小异或生产树,是对于每个边的值按照二进制建立01字符串Trie树,利用贪心的思想分治解决问题。
贴一个大佬 https://blog.csdn.net/qq_43857314/article/details/107645865
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=1e9+7; const int maxn=2e5+7; struct node{ int val; int v ; //下一个店 int next; //next[i]表示与第i条边同起点的上一条边的储存位置 }edge[maxn]; int cnt; int head[maxn]; //从1点开始 void add(int u,int v,int val){ edge[cnt].val=val; edge[cnt].v=v; //edge[i]表示第i条边的终点 edge[cnt].next=head[u]; //head[i]表示以i为起点的最后一条边的储存位置 head[u]=cnt++; } const int base=30; struct Trie { int tr[maxn * base][2]; vector<int> e[maxn * base]; int tot; void insert(int x) { int u = 0; for (int i = base; i >= 0; i--) { int t = (x >> i) & 1; if (!tr[u][t]) tr[u][t] = ++tot; u = tr[u][t]; e[u].push_back(x); } } int ask(int id, int d, int x) { //返回位置 if (d < 0) return 0; int t = (x >> d) & 1; if (tr[id][t]) return ask(tr[id][t], d - 1, x); return ask(tr[id][t ^ 1], d - 1, x) + (1 << d); } ll solve(int id, int d) { int l = tr[id][0], r = tr[id][1]; ll ans = 0; if (l && r) { if (e[l].size() > e[r].size()) swap(l, r); int mi = numeric_limits<int>::max(); //inf for (int i = 0; i < e[l].size(); ++i) mi=min(mi, ask(r, d - 1, e[l][i])); ans += mi + (1 << d); } if (l) ans += solve(l, d - 1); if (r) ans += solve(r, d - 1); return ans; } } tri; int col[maxn]; void dfs(int u,int fa){ for(int i=head[u];~i;i=edge[i].next){ if(edge[i].v!=fa){ col[edge[i].v]=col[u]^edge[i].val; dfs(edge[i].v,u); } } } int main(){ cnt=0; int n; memset(head,-1,sizeof(head)); scanf("%d",&n); int x,y,w; for(int i=1;i<n;i++){ scanf("%d%d%d",&x,&y,&w); add(x,y,w); add(y,x,w); } dfs(0,0); for(int i=0;i<n;i++){ tri.insert(col[i]); } printf("%lld\n",tri.solve(0,base)); return 0; }