正题
题目链接:http://www.ybtoj.com.cn/contest/118/problem/3
解题思路
给出nnn个点,mmm次动态插入一条无向边询问:割掉一些边使得图中至少两点不连通,并且割掉的边异或和最大。
询问之间相互独立
1≤n≤500,1≤m≤10001\leq n\leq 500,1\leq m\leq 10001≤n≤500,1≤m≤1000
边权以二进制形式给出,长度不超过100010001000
解题思路
要求分隔两个点,看起来很麻烦,其实有个结论。先定义wiw_iwi表示连接iii的所有边的异或和,如果选出了一个点集UUU和外面的所有点都隔绝,那么割就是点集UUU中所有点的wiw_iwi值异或和。
其实挺显然的,因为如果两个点集中的点x,yx,yx,y之间的边被异或了两次就抵消掉了。
那么现在问题就变为了每次修改两个数,求最大异或和。
然后就是带修线性基的裸题了,有两种方法
在线做法是先删除再插入,就是开一个0行储存所有的没有成功插入线性基的元素,然后还要对于每个元素维护一个它插入的时候异或了哪些元素。
每次你删除一个元素xxx的时候,假设集合SSS中储存了所有插入的时候异或了xxx的元素(包括xxx本身),那么我们找出一个最小的y∈Sy\in Sy∈S(异或后),让所有SSS中的其他元素异或上yyy之后再将yxorcy\ xor\ cy xor c插入(ccc表示你要让xxx异或的值)
此时就相当于你将之前插入xxx时本应该异或的数变成了异或xxorcx\ xor\ cx xor c的,选出最小的yyy防止对后面的元素产生影响,然后修改后让yyy代替xxx成为新的主元插入。
加一个bitsetbitsetbitset优化,时间复杂度O(m(n+L)Lw)O(\frac{m(n+L)L}{w})O(wm(n+L)L)
离线的做法是线段树分治,一个xxx的取值会被分为不同的时间段,每次将xxx的固定的时间段插入到线段树的对应区间,然后分治下去的时候维护一个撤销线性基就好了。
时间复杂度O(mL2logmw)O(\frac{mL^2\log m}{w})O(wmL2logm)(也许?)
这里写的是在线的做法
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
using namespace std;
const int N=1010;
bitset<N>w[N],v[N],c,ans;
int n,m,p[N];char s[N];
void Insert(int x){for(int i=N-1;i>=0;i--)if(w[x][i]){if(p[i])w[x]^=w[p[i]],v[x]^=v[p[i]];else{p[i]=x;return;}}return;
}
void Change(int x){int pos=0;for(int i=1;i<=n;i++)if(v[i][x]&&!w[i].any()){pos=i;break;}if(!pos)for(int i=0;i<N;i++)if(p[i]&&v[p[i]][x]){pos=p[i];p[i]=0;break;}for(int i=1;i<=n;i++)if(v[i][x]&&i!=pos)w[i]^=w[pos],v[i]^=v[pos];w[pos]^=c;Insert(pos);return;
}
int main()
{freopen("cut.in","r",stdin);freopen("cut.out","w",stdout);scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)v[i][i]=1;for(int i=1;i<=m;i++){int x,y;scanf("%d%d",&x,&y);scanf("%s",s);int l=strlen(s);c.reset();for(int j=0;j<l;j++)c[j]=s[l-j-1]-'0';Change(x);Change(y);bool flag=0;ans.reset();for(int i=N-1;i>=0;i--){if(p[i]&&!ans[i])ans^=w[p[i]];if(ans[i])flag=1;if(flag)printf("%d",ans[i]?1:0);}if(!flag)puts("0");else putchar('\n');}return 0;
}