P7317 [COCI2018-2019#3] Praktični 题解
很好的一道题,但我没做出来。
先假设我们已经决定好边权值的最终值了,此时要用最少次数,很容易想到要用线性基。
那怎么决定最终值呢?首先选择在图上随便选一个生成树,如果一条非树边和它的两个端点路径的回路满足条件的话,那么这个图一定满足条件。
为什么呢?因为一边非树边和它的两个端点路径异或值相等,可以把非树边换成这条路径,那么任何一个回路,都会经过同一条边两次或不经过。
这是只改非树边是最优的,为什么呢?一个回路上更改的值的异或值是一定的,如果只更改非树边,变化量只有一个,线性基的结果也是最小的。那么求出每个非树边要更改的值即可。
时间复杂度
#include<cstdio>
#include<vector>
#include<cstring>
#define N 100010
using namespace std;
int n, m, vis[N], d[N];
struct edge
{
int to, w, id;
};
vector<edge>e[N];
int vid[N];
int xe[N], xid[N], tot = 0;
int bit[100];
vector<int>ans[100];
void dfs(int now)
{
vis[now] = 1;
for(int i = 0; i < e[now].size(); i++)
if(!vis[e[now][i].to])
{
d[e[now][i].to] = d[now] ^ e[now][i].w;
dfs(e[now][i].to);
}
}
void dfs1(int now, int fa)
{
vis[now] = 1;
for(int i = 0; i < e[now].size(); i++)
{
int to = e[now][i].to;
if(!vis[to])
{
dfs1(to, now);
}
else if(to != fa && !vid[e[now][i].id])
{
tot++;
vid[e[now][i].id] = 1;
xe[tot] = d[now] ^ d[to] ^ e[now][i].w;
xid[tot] = e[now][i].id;
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
{
int a, b, p;
scanf("%d%d%d", &a, &b, &p);
e[a].push_back((edge){b, p, i});
e[b].push_back((edge){a, p, i});
}
dfs(1);
memset(vis, 0, sizeof vis);
dfs1(1, 0);
for(int i = 1; i <= tot; i++)
{
int now = xe[i];
for(int j = 30; j >= 0; j--)
if(now & (1 << j))
{
if(bit[j])now ^= bit[j];
else
{
bit[j] = now;
break;
}
}
}
for(int i = 1; i <= tot; i++)
{
int now = xe[i];
for(int j = 30; j >= 0; j--)
if(now & (1 << j))
{
ans[j].push_back(xid[i]);
now ^= bit[j];
}
}
int sum = 0;
for(int i = 30; i >= 0; i--)
if(bit[i])
sum++;
printf("%d\n", sum);
for(int i = 30; i >= 0; i--)
if(bit[i])
{
printf("%d %d ", bit[i], ans[i].size());
for(int j = 0; j < ans[i].size(); j++)printf("%d ", ans[i][j]);
printf("\n");
}
return 0;
}