bzoj 4644 经典傻逼题(线段树分治 + 线性基 + bitset优化)

在这里插入图片描述


根据定义,若选择了一条边的一个端点,这条边就要选上,若这条边的两个点都选上了,这条边就不能选。

将边权异或到点权上,这样处理后,若选择一个点,则连接这个点的边都会被考虑,若一条边的两个端点都被选中,这条边会因为被异或两次而消失。

这样问题转化为每次加入一条边,对原图求线性基。
线性基不支持删除修改,由于位数有1000位,如果每加入一条边都暴力重新构造线性基,复杂度为 O ( 500 ∗ 1000 ∗ 1000 ∗ 30 ) O(500 * 1000 * 1000 * 30) O(5001000100030),30是使用 bitset 进行异或运算的常数,1000位异或 相当于进行30次 int 型异或运算。

来看看如何优化:

先观察问题的性质:暴力构造线性基时,由于每一次加入一条边都重新将所有点加入到线性基中,而每插入一条边,只会引起两个点权改变,M条边最多改变 2M个点的点权。在点 p 的两次改变的时间间隔之间,这个 p 点 只需要插入线性基一次。

考虑用线段树分治,按时间分治,线段树的每个节点维护这个时间段权值不会改变的点,在计算线性基时这些点只需要插入一次即可。而 dfs 时子节点的线性基又可以继承父节点的线性基,大大减少了修改时线性基的插入次数,当dfs 到叶子节点时,就得到了这个时间点的线性基。

计算一下这样处理的复杂度:M 次 加边至多修改 2M 个点的点权,每一次修改要在线段树上更新,复杂度为上限为 2 M log ⁡ N 2M\log N 2MlogN。遍历线段树计算线性基时,每一个被修改的点会插入 log 次,因此总共插入 2 M log ⁡ N 2M\log N 2MlogN 次,每一次插入的复杂度上限为为 O ( 1000 ∗ 30 ) O(1000 * 30) O(100030)

因此复杂度上限大约为 O ( 1000 ∗ 30 ∗ 2 M log ⁡ N ) O(1000 * 30 * 2M \log N) O(1000302MlogN),上限时间大概 3 ∗ 1 0 8 3 * 10^8 3108,实际上这个上限跑不满。


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3 + 10;
typedef bitset<1010> bit;
int n,m,lst[maxn];
char s[maxn];
bit val[maxn];
bit ans[maxn];
void print(bit x) {
	int p = -1;
	for(int i = 1000; i >= 0; i--) {
		if(x[i] == 1) {
			p = i;
			break;
		}
	}
	if(p == -1) putchar('0');
	else {
		for(int i = p; i >= 0; i--)
			putchar(x[i] + '0');
	}
	putchar('\n');
}
struct line_basis{
	bit a[maxn];			//线性基向量
	void init()  {
		for(int i = 0; i <= 1000; i++)
			a[i].reset();
	}
	void add(bit x) {
		for(int i = 1000; i >= 0; i--) {
			if(x[i] == 1) {
				if(a[i].any()) x ^= a[i];
				else {
					a[i] = x;
					break;
				}
			}
		}
	}
	bit getval() {
		bit t;
		t.reset();
		for(int i = 1000; i >= 0; i--)
			if(!t[i] && a[i].any())
				t ^= a[i];
		return t;
	}
};
struct seg_tree{
	#define lson rt << 1,l,mid
	#define rson rt << 1 | 1,mid + 1,r
	vector<bit> pot[maxn << 2];
	void build(int rt,int l,int r) {
		pot[rt].clear();
		if(l == r) return;
		int mid = l + r >> 1;
		build(lson); build(rson);
	}
	void insert(int L,int R,bit v,int rt,int l,int r) {
		if(L <= l && r <= R) {
			pot[rt].push_back(v);
			return ;
		}
		int mid = l + r >> 1;
		if(L <= mid) insert(L,R,v,lson);
		if(mid + 1 <= R) insert(L,R,v,rson);
	}
	void solve(int rt,int l,int r,line_basis t) {
		for(auto it : pot[rt])
			t.add(it);
		if(l == r) {
			ans[l] = t.getval();
			return ;
		}
		int mid = l + r >> 1;
		solve(lson,t); solve(rson,t);
	}
}seg;
int main() {
	scanf("%*d%d%d",&n,&m);
	for(int i = 1; i <= n; i++)
		val[i].reset();						//每一位清0 
	for(int i = 1; i <= m; i++)
		ans[i].reset();
	seg.build(1,1,m);
	for(int i = 1,u,v; i <= m; i++) {
		scanf("%d%d%s",&u,&v,&s);
		int len = strlen(s);
		reverse(s,s + len);
		bit w; w.reset();
		for(int j = 0; j < len; j++)
			w.set(j,s[j] - '0');
		if(lst[u])
			seg.insert(lst[u],i - 1,val[u],1,1,m);
		val[u] ^= w;
		lst[u] = i;
	
		if(lst[v])
			seg.insert(lst[v],i - 1,val[v],1,1,m);
		val[v] ^= w;
		lst[v] = i;
	}
	for(int i = 1; i <= n; i++)
		if(lst[i])
			seg.insert(lst[i],m,val[i],1,1,m);
	line_basis t;
	t.init();
	seg.solve(1,1,m,t);
	for(int i = 1; i <= m; i++)
		print(ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值