NOIP2018模擬賽3:異或和和和(xor)壓位

**NOIP2018模擬賽3:異或和和和(xor) **

【問題描述】

小 F 種了一棵香蕉樹,小 D 想知道樹上每條簡單路徑上權值的異或和,因爲樹上的簡單路徑很多,你只需要輸出這些簡單路徑權值的異或和的和對 998,244,353取模後的結果即可。
n5106,w<264n\le 5*10^6,w<2^{64}

分析

容易想到壓位後樹上Dp,但是複雜度是O(nlogw)O(nlogw)的,仍難難以接受。
考慮轉化,每個節點記錄到根節點路徑異或和,一條路徑就是兩個點到根路徑異或和的異或。問題轉化爲O(n)O(n)個數,兩兩異或和。
考慮分成441616位數。把在每位上的相同的數統計一下。
這樣的好處是,由於每個位是獨立的,所以壓位了之後,nn轉化爲了42164*2^{16}級別(因爲相同的數可以合併)。
然後對每一位做狀壓Dp即可。
複雜度O(4n+416216)O(4n+4*16*2^{16})
壓位大法好。

代碼

#include<cstdio>
typedef unsigned long long ULL;
const int N = 5e6 + 10, S = 65536, P = 998244353;
ULL ri() {
	ULL x=0;char c=getchar(); while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x;
}
ULL seed;
ULL get_next() {
	seed=seed^(seed<<13);
	seed=seed^(seed>>17);
	seed=seed^(seed<<5);
	return seed;
}
int f[S], s[S], b[N], pr[N], nx[N << 1], to[N << 1], c[N], tp, A, n; ULL w[N << 1], v[N], B;
void add(int u, int v, ULL W) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp; w[tp] = W;}
void adds(int u, int v, ULL w) {add(u, v, w); add(v, u, w);}
void Up(int &a, int b) {a += b; if(a >= P) a -= P;}
void Dfs(int u, int fa) {
	for(int i = pr[u]; i; i = nx[i]) if(to[i] != fa)
		v[to[i]] = v[u] ^ w[i], Dfs(to[i], u);
}
void Work(int x) {
	for(int i = 1;i <= n; ++i) ++s[(v[i] >> 16 * x) & S - 1];
	for(int i = 0;i < 16; ++i) c[i] = 0; B %= P; 
	for(int t = 0;t < S; ++t) {
		for(int k = 0;k < 16; ++k)
			if(t & b[k]) c[k] += s[t];
		s[t] = 0;
	}
	for(int t = 0;t < S; ++t) {
		f[t] = 0;
		for(int k = 0;k < 16; ++k)
			Up(f[t], B * b[k] % P * ((t & b[k]) ? n - c[k] : c[k]) % P);
	}
	for(int i = 1;i <= n; ++i) Up(A, f[(v[i] >> 16 * x) & S - 1]);
}
int main() {
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout);
	b[0] = 1; for(int i = 1;i <= 16; ++i) b[i] = b[i - 1] << 1;
	n = ri(); seed = ri();
	for(int i = 2, u;i <= n; ++i) adds(ri(), i, get_next());
	Dfs(1, 0); B = 1;
	for(int j = 0;j < 4; ++j, B <<= 16) Work(j);
	printf("%d\n", A);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章