CF1614C - Divan and bitwise operations
阿新 • • 發佈:2021-11-27
一眼解法
構造序列
對於序列中的一個數,將覆蓋它的所有的區間的值設為 \(x_i\),則我們可以構造這個數為 \(x_1\&x_2\&...\&x_n\),使用線段樹來維護區間且。
#define le (i << 1) #define ri (i << 1 | 1) void andset(int i, int l, int r){ if(l >= L && r <= R){ tree[i] &= X; return; } int mid = (l + r) >> 1; if(mid >= L) andset(le, l, mid); if(mid < R) andset(ri, mid + 1, r); } void pushall(int i, int l, int r){ if(l == r){ a[l] = tree[i]; return; } tree[le] &= tree[i]; tree[ri] &= tree[i]; int mid = (l + r) >> 1; pushall(le, l, mid); pushall(ri, mid + 1, r); } memset(tree + 1, 0xff, sizeof(unsigned int) * (n << 2));
求解答案
按位考慮。設 \(f[i][j][k(=0\text{或}1)]\) 表示前 \(i\) 個數,第 \(j\) 位上的值為 \(k\) 的序列的個數。寫出轉移方程:
for(int i = 1; i <= n; i++) for(int j = 0; j < 30; j++){ if((a[i] >> j) & 1){ cnt[i][j][1] = (cnt[i - 1][j][1] + cnt[i - 1][j][0] + 1) % p; cnt[i][j][0] = (cnt[i - 1][j][0] + cnt[i - 1][j][1]) % p; }else{ cnt[i][j][1] = (cnt[i - 1][j][1] << 1) % p; cnt[i][j][0] = ((cnt[i - 1][j][0] << 1) + 1) % p; } }
完整程式碼
#include <cstdio> #include <cstring> #define le (i << 1) #define ri (i << 1 | 1) using namespace std; const int N = 2e5 + 1, p = 1e9 + 7; const unsigned int INF = 0xFFFFFFFF; unsigned int tree[N << 2], L, R, X, a[N]; void andset(int i, int l, int r){ if(l >= L && r <= R){ tree[i] &= X; return; } int mid = (l + r) >> 1; if(mid >= L) andset(le, l, mid); if(mid < R) andset(ri, mid + 1, r); } void pushall(int i, int l, int r){ if(l == r){ a[l] = tree[i]; return; } tree[le] &= tree[i]; tree[ri] &= tree[i]; int mid = (l + r) >> 1; pushall(le, l, mid); pushall(ri, mid + 1, r); } int t, n, m, cnt[N][30][2], ans; int main(){ scanf("%d", &t); while(t--){ ans = 0; memset(cnt + 1, 0, sizeof(int) * 30 * 2 * n); scanf("%d%d", &n, &m); memset(tree + 1, 0xff, sizeof(unsigned int) * (n << 2)); while(m--){ scanf("%u%u%u", &L, &R, &X); andset(1, 1, n); } pushall(1, 1, n); for(int i = 1; i <= n; i++) for(int j = 0; j < 30; j++){ if((a[i] >> j) & 1){ cnt[i][j][1] = (cnt[i - 1][j][1] + cnt[i - 1][j][0] + 1) % p; cnt[i][j][0] = (cnt[i - 1][j][0] + cnt[i - 1][j][1]) % p; }else{ cnt[i][j][1] = (cnt[i - 1][j][1] << 1) % p; cnt[i][j][0] = ((cnt[i - 1][j][0] << 1) + 1) % p; } } for(int j = 0; j < 30; j++) ans = (ans + (1ll << j) * cnt[n][j][1]) % p; printf("%d\n", ans); } return 0; }
大佬做法
先說結論
對於某一位,不論這一位上為 \(1\) 的數有多少個,答案相同。
感性證明
先假設所有數這一位上的值為 \(0\)。此時一個數翻轉為 \(1\),那麼它會導致一半的子序列的異或和這一位上翻轉,而這兩半的區別僅僅在於有沒有異或上該數。那麼此時若另一個數翻轉,它也會導致一半的子序列這一位上翻轉,而由於這兩半的分佈與第一個翻轉的數有沒有被異或上無關,因此正好是一半的 \(0\) 被翻轉為 \(1\),一半的 \(1\) 被翻轉為 \(0\)。依此類推,不論有多少個 \(1\),一定恰好一半的子序列這一位上是 \(1\)。