**NOIP2018模擬賽3:異或和和和(xor) **
【問題描述】
小 F 種了一棵香蕉樹,小 D 想知道樹上每條簡單路徑上權值的異或和,因爲樹上的簡單路徑很多,你只需要輸出這些簡單路徑權值的異或和的和對 998,244,353取模後的結果即可。
分析
容易想到壓位後樹上Dp,但是複雜度是的,仍難難以接受。
考慮轉化,每個節點記錄到根節點路徑異或和,一條路徑就是兩個點到根路徑異或和的異或。問題轉化爲個數,兩兩異或和。
考慮分成個位數。把在每位上的相同的數統計一下。
這樣的好處是,由於每個位是獨立的,所以壓位了之後,轉化爲了級別(因爲相同的數可以合併)。
然後對每一位做狀壓Dp即可。
複雜度
壓位大法好。
代碼
#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;
}