JZOJ 5352. 【NOIP2017提高A組模擬9.7】計數題
阿新 • • 發佈:2020-10-22
題目
分析
考慮 \(kruskal\) 的過程
我們選邊從高位開始
當前位為 \(0\) 的放一邊,為 \(1\) 的放另一邊
將 \(0\) 的建一棵字典樹, \(1\) 的匹配
因為是異或,那就走相同值的位,算能匹配到的最小值的個數
和與方案數都可以在這裡計算
\(Code\)
#include<cstdio> using namespace std; typedef long long LL; const LL P = 1e9 + 7; const int N = 100005; int n , cnt , su , a[N] , c[N] , d[N] , ts[N * 30] , t[N * 30][2]; LL ans = 1; LL fpow(LL x , LL y) { LL res = 1; while (y) { if (y & 1) res = res * x % P; y >>= 1 , x = x * x % P; } return res; } void insert(int x) { int u = 0 , ch; for(register int i = 30; i >= 0; i--) { ch = (x >> i) & 1; if (!t[u][ch]) t[u][ch] = ++cnt; u = t[u][ch] , ts[u]++; } } int find(int x) { int u = 0 , ch , res = 0; for(register int i = 30; i >= 0; i--) { ch = (x >> i) & 1; if (t[u][ch]) u = t[u][ch]; else u = t[u][ch ^ 1] , res = res + (1 << i); } su = ts[u]; return res; } LL solve(int l , int r , int w) { if (l >= r) return 0; if (w == -1) { if (r - l - 1 > 0) ans = ans * fpow(r - l + 1 , r - l - 1) % P; return 0; } int tl = 0 , tr = 0; for(register int i = l; i <= r; i++) if (a[i] & (1 << w)) d[++tr] = a[i]; else c[++tl] = a[i]; for(register int i = 1; i <= tl; i++) a[l + i - 1] = c[i]; for(register int i = 1; i <= tr; i++) a[l + tl - 1 + i] = d[i]; int tmp; if (!tl || !tr) tmp = 0; else { int num = 0 , f; tmp = 2147483647 , cnt = 0; for(register int i = 1; i <= tl; i++) insert(c[i]); for(register int i = 1; i <= tr; i++) { su = 0 , f = find(d[i]); if (f < tmp) tmp = f , num = su; else if (tmp == f) num += su; } ans = ans * num % P; for(register int i = 0; i <= cnt; i++) ts[i] = t[i][0] = t[i][1] = 0; } return 1LL * tmp + solve(l , l + tl - 1 , w - 1) + solve(l + tl , r , w - 1); } int main() { freopen("jst.in" , "r" , stdin); freopen("jst.out" , "w" , stdout); scanf("%d" , &n); for(register int i = 1; i <= n; i++) scanf("%d" , &a[i]); printf("%lld\n" , solve(1 , n , 30)); printf("%lld" , ans); }