题目描述
有一串长度为 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 n≤105,ai≤109
考虑前缀和数组 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 bl−1⨁br
转化到图上等价于加了一条 ( l − 1 , r ) (l-1,r) (l−1,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
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;
}