FZOJ194. 「2019冬令营提高组」密文 (最小生成树,trie)

题目描述
有一串长度为 n n n 的密文,密文的每一位都可以用一个非负整数来描述,并且每一位都有一个权值 a i a_i ai 。你可以进行任意多次操作,每次操作可以选择连续一段密文,花费选择的所有位上权值的异或和的代价获得这段密文每一位的异或和。求至少需要花费多少代价才能将密文的每一位都破解出来。
n ≤ 1 0 5 , a i ≤ 1 0 9 n\le 10^5,a_i\le 10^9 n105,ai109


考虑前缀和数组 b i b_i bi
知道每一位相当于知道每一个前缀和 b i b_i bi
初始我们知道 b 0 b_0 b0
每次操作 l , r l,r l,r 相当于知道了 b l − 1 ⨁ b r b_{l-1}\bigoplus b_r bl1br
转化到图上等价于加了一条 ( l − 1 , r ) (l-1,r) (l1,r) 的边,要求花费最小使整个图联通
考虑 p r i m prim prim 算法,对每个点连出去最小的边可以用 t r i e trie trie 来找,复杂度 O ( n   l o g a i   l o g n ) O(n\ loga_i\ logn) O(n logai logn)

该算法常数较大 期望得分 80 80 80 上下,常数卡的好可能可以过去

考虑在 t r i e trie trie 树上贪心
对于一个 t r i e trie trie 上的节点,若既有左儿子又有右儿子 ,那么仅考虑左子树的点所在的联通快和右子树所在的联通块 将这两个连起来的最小代价必然是在这两个子树中选点连起来

比较容易证明

每次暴力枚举左子树中的点到右子树去找最优值

每个点至多被每个祖先搜到一次
复杂度 O ( n l o g 2 a i ) O(nlog^2a_i) O(nlog2ai)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define ll long long
namespace io {
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	// getchar
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	// print the remaining part
	inline void flush () {
		fwrite (obuf, 1, oS - obuf, stdout);
		oS = obuf;
	}
	// putchar
	inline void putc (char x) {
		*oS ++ = x;
		if (oS == oT) flush ();
	}
	// input a signed integer
	template <class I>
	inline void gi (I &x) {
		for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;
	}
	// print a signed integer
	template <class I>
	inline void print (I &x) {
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;
		while (x) qu[++ qr] = x % 10 + '0',  x /= 10;
		while (qr) putc (qu[qr --]);
	}
	//no need to call flush at the end manually!
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io :: gi;
using io :: putc;
using io :: print;
int n;
int a[101000],bin[35];
int tr[4001000][2],tot,rt,cnt[4001000];
vector<int>h;
ll ans;
void insert(int v){
	int now = rt;
	repp(i,30,0) {
		cnt[now]++;
		if(v&bin[i]) {if(!tr[now][1]) tr[now][1] = ++tot;now = tr[now][1];}
		else if(!(v&bin[i])) {if(!tr[now][0]) tr[now][0] = ++tot;now = tr[now][0];}
	}
	cnt[now]++;
}
void find(int x,int dep,int v){
	if(dep == -1) h.push_back(v);
	if(tr[x][0]) find(tr[x][0],dep-1,v);
	if(tr[x][1]) find(tr[x][1],dep-1,v+bin[dep]);
}
int query(int x,int dep,int v){
	int now = 0;
	repp(i,dep,0) {
		int k = (v&bin[i]) != 0;
		if(tr[x][k]) x = tr[x][k];
		else x = tr[x][k^1],now += bin[i];
	}
	return now;
}
void solve(int l,int r,int dep,int x,int v){
	int mid = l + cnt[tr[x][0]] - 1;
 	if(tr[x][0]) solve(l,mid,dep-1,tr[x][0],v);
 	if(tr[x][1]) solve(mid+1,r,dep-1,tr[x][1],v+bin[dep]);
    if(tr[x][0] && tr[x][1]){
    	int now = 2e9;h.clear();
    	find(tr[x][0],dep-1,v);
    	rep(i,0,((int)(h.size()))-1)
    		now = min(now,bin[dep] + query(tr[x][1],dep-1,h[i]));
    	ans += now;
	}
}
int main(){
	freopen("secret.in","r",stdin);
	freopen("secret.out","w",stdout);
	bin[0] = 1;rep(i,1,30) bin[i] = bin[i-1]<<1;rt = ++tot;
	gi(n);rep(i,1,n) gi(a[i]),a[i] ^= a[i-1];
	rep(i,0,n) insert(a[i]);
    solve(0,n,30,rt,0);
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值