[hdu5270]按位统计,容斥,归并
题意:给两个序列[a, a + n), [b, b + n),求所有数(ai + bj)的异或和,i,j∈[0,n)。
思路:这个题思路比较巧妙,不难但是难想到。BC上的题解讲得非常清楚了,我就直接copy过来了吧
我们考虑两个数A ,B 。 为了描述方便,我们设[P]的值为:当表达式P的值为真时,[P]=1,否则[P]=0 我们现在考虑计算[(A+B)and(2i)>0] 首先我们将A,B都对2i+1 取模,显然这样是不会影响答案的 则有一个十分显然的等式:[(A+B)and(2i)>0]=[(A+B)≥(2i)]−[(A+B)≥(2i+1)]+[(A+B)≥(3∗2i)] 这个式子相当容易理解,这里不多述了 考虑每一位对答案的贡献是独立的,我们每一位分开做 于是现在问题变成了:给定数组A,B ,求满足Ai+Bj≥limit 的数对个数 我们可以将A,B 排序后,直接O(n) 计算即可 然而排序是O(nlogn) 的,这样总复杂度就是O(nlognlogA) 了,无法通过此题 于是这里有个小技巧 我们从高位往低位做,现在我们要实现的是:将A 中每个数对P 取模后将A 排序 我们发现A 会被分成两段,一段小于P ,一段大于等于P ,只有后面一段要取模,我们可以取模后直接将这两段归并,复杂度是O(n) 的 时间复杂度:O(nlogA+nlogn)
下面的代码就是根据题解写的,个人感觉也非常清晰了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #pragma comment(linker, "/STACK:10240000,10240000") #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <vector> #include <algorithm> #include <queue> using namespace std; const int maxn = 1e5 + 7; int n; long long a[maxn], b[maxn]; void sort( long long *a, long long md) { int pos = n; for ( int i = 0; i < n; i ++) { if (pos == n && a[i] >= md) pos = i; a[i] = a[i] & (md - 1); } inplace_merge(a, a + pos, a + n); } bool solve( long long limit) { long long ans = 0; int that = n - 1; for ( int i = 0; i < n; i ++) { while (that >= 0 && a[i] + b[that] >= limit) that --; ans += n - 1 - that; } return ans & 1; } int main() { #ifndef ONLINE_JUDGE freopen ( "in.txt" , "r" , stdin); #endif // ONLINE_JUDGE int T, cas = 0; cin >> T; while (T --) { cin >> n; for ( int i = 0; i < n; i ++) { scanf ( "%I64d" , a + i); } for ( int i = 0; i < n; i ++) { scanf ( "%I64d" , b + i); } sort(a, a + n); sort(b, b + n); long long ans = 0; for ( int i = 61; i >= 0; i --) { sort(a, ( long long )2 << i); sort(b, ( long long )2 << i); long long res = solve(( long long )1 << i) ^ solve(( long long )2 << i) ^ solve(( long long )3 << i); ans |= res << i; } printf ( "Case #%d: %I64d\n" , ++ cas, ans); } return 0; } |