[HAOI2017]八纵八横 线性基
题面
题解
观察到题目中的 “内陆经济环” 不好处理,因此我们把它拆成 “内陆经济链”。
对于1号节点,我们创建一个它的复制节点n + 1号节点,这个节点继承1号节点的所有边,可以发现,一个1到1的内陆经济环,和一个1到n + 1的内陆经济链是等价的,因此我们只需要考虑如何在一个变化的图上维护一个点到另一个点的最大xor和即可。
观察到删边只会删去后来加入的边,所以就很好处理了,我们用线段树分治(时间分治)来维护。
具体求从1到n + 1的最大xor和的方法参见此题(是一个常见套路,就不在赘述了):[WC2011]最大XOR和路径 线性基
于是我们的目的仅仅是维护当前图中所有简单环构成的线性基。
因为线段树分治最深也就log,所以同时维护log个线性基的时间空间都还是可承受的。
因此我们每到一个新节点就建一个新的线性基,然后加入新增的环,退出时再删去即可。
考虑如何快速求新增的环。
因为在dfs树上,一条非树边代表一个简单环,所以一个新增的,连接x ---> y 的边最多带来一个新环,而这个新环,肯定是经过这条新边,从x走到y,再走回x的路径所构成的一个环。
因为xor的特性,所以我们可以用x到rot的路径 + y到rot的路径 + 新边来异或出这个环的异或值。
因此我们只需要知道一个点新增了哪些边就可以了。
#include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 550
#define ac 2100
#define maxn 8000
#define maxm 2010000
int n, m, all, cnt;
int dead[ac], start[ac], id[ac];
int Head[AC], date[ac], Next[ac], tot;
bitset<1100> len[ac], ans[ac], val[AC], link[maxn];
struct road{int x, y, id;bitset<1100> v;}way[ac];//注意一条边会且只会带来一个环(因为原图联通)
bool vis[AC];
char ss[ac];
//int s[AC], top;//存下待分配的线性基编号
struct basis{//第几层就用第几个线性基
bitset<1100> f[1100];//第1000位为最高位
void clear() {for(R i = 1; i <= 1000; i += 2) f[i] = f[i + 1] = 0;}
void ins(bitset<1100> x)
{
for(R i = 1000; i; i --)
{
if(!x[i]) continue;
if(f[i] == 0) {f[i] = x; break;}
else x ^= f[i];
}
}
}f[50];//最多同时用到log个线性基
inline int read()
{
int x = 0;char c = getchar();
while(c > '9' || c < '0') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x;
}
/*inline void add_new(int f, int w)//f为线段树上的一个点,w是一条边的编号
{date_[++ tot_] = w, Next_[tot_] = Head_[f], Head_[f] = tot_;}*/
inline void add(int f, int w, bitset<1100> S)
{
date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot, len[tot] = S;
date[++ tot] = f, Next[tot] = Head[w], Head[w] = tot, len[tot] = S;
}
void dfs(int x, bitset<1100> have)//找最初的环,
{
vis[x] = true, val[x] = have;
for(R i = Head[x]; i; i = Next[i])
{
int now = date[i];
if(vis[now]) f[0].ins(have ^ len[i] ^ val[now]);
else dfs(now, have ^ len[i]);
}
}
void change(int x, int l, int r, int ll, int rr, int w)//给[ll, rr]加入一条边,其编号为w
{
if(l == ll && r == rr){link[x][w] = true; return ;}
int mid = (l + r) >> 1;
if(rr <= mid) change(x << 1, l, mid, ll, rr, w);
else if(ll > mid) change((x << 1) + 1, mid + 1, r, ll, rr, w);
else
{
change(x << 1, l, mid, ll, mid, w);
change((x << 1) + 1, mid + 1, r, mid + 1, rr, w);
}
}
void solve(int x, int l, int r, int dep)
{
f[dep] = f[dep - 1];
for(R i = 1; i <= all; i ++)
if(link[x][i]) f[dep].ins(val[way[i].x] ^ val[way[i].y] ^ way[i].v);
if(l == r)
{
ans[l] = val[n + 1];
for(R i = 1000; i; i --)
if(ans[l][i] == 0) ans[l] ^= f[dep].f[i];
return ;
}
int mid = (l + r) >> 1;
solve(x << 1, l, mid, dep + 1);
solve((x << 1) + 1, mid + 1, r, dep + 1);
}
bitset<1100> get()
{
bitset<1100> tmp;
scanf("%s", ss + 1); int t = strlen(ss + 1);
for(R j = t, l = 1; j; j --, l ++) tmp[l] = (ss[j] == '1'); //存下边权
return tmp;
}
void pre()
{
n = read(), m = read(), all = read();
bitset<1100> tmp;
for(R i = 1; i <= m; i ++)
{
int x = read(), y = read();
if(x > y) swap(x, y);
tmp = get();
add(x, y, tmp);
if(x == 1) add(n + 1, y, tmp);
}
}
void build()
{
for(R i = 1; i <= all; i ++)
{
scanf("%s", ss + 1);
if(ss[1] == 'A') //加边
{
id[++ cnt] = i;//id[x]存下边x当前是谁在承受修改
start[i] = i, dead[i] = all;
way[i].x = read(), way[i].y = read(), way[i].v = get();
}
else if(ss[2] == 'a') {dead[id[read()]] = i - 1;}//删边
else //修改边权,看做删去一条旧边生成一条新边,并且新边承担后面旧边的修改
{
int x = read();//承担旧边的修改,并删除旧边
way[i] = way[id[x]], way[i].v = get();
start[i] = i, dead[i] = all, dead[id[x]] = i - 1, id[x] = i;//除了初始化id[x]外,其他的地方都要用到id[x]
}//所以id[x] = i放在最后面修改,在前面的x都要用id[x]代替
}
for(R i = 1; i <= all; i ++)
if(start[i]) change(1, 1, all, start[i], dead[i], i);
}
void work()
{
for(R i = 1000; i; i --)
if(!ans[0][i]) ans[0] ^= f[0].f[i];
for(R i = 0; i <= all; i ++)
{
int l = 1000;
while(!ans[i][l] && l) -- l;
for(R j = l; j; j --) printf("%c", (ans[i][j] == 1) ? '1' : '0');
if(!l) printf("0");
printf("\n");
}
}
int main()
{
// freopen("in.in", "r", stdin);
pre();
dfs(1, ans[0]);//反正此时ans[0]是空的
build();
if(all) solve(1, 1, all, 1);
work();
// fclose(stdin);
return 0;
}