JZOJ 7879
小清新思维。
定义一对整数是不好的当且仅当这两个整数相加是 2 2 2 的整数次幂。 m m m 次询问, [ l , r ] [l,r] [l,r] 内最多能选多少个数组成集合 A A A,使 A A A 中任意两个数都不是不好的。对于一些数据,求 [ 1 , n ] [1,n] [1,n] 的所有子区间的答案的和。
先考虑第一问。
有简单结论:从大往小贪心选择。
证明:
对于两个数相加等于 2 k 2^k 2k,显然一个数小于 2 k − 1 2^{k-1} 2k−1,一个数大于 2 k − 1 2^{k-1} 2k−1,所以每个数 x x x 只会满足 x + a = 2 k , x > a x+a=2^k,x>a x+a=2k,x>a 一次,也就是说每个数只会成为冲突数对中的较大数一次。
那么对于
[
l
,
r
]
[l,r]
[l,r] 里的冲突数对
(
x
,
y
)
,
x
<
y
,
x
+
y
=
2
k
(x,y),x
现在对于一对 [ l , r ] [l,r] [l,r],找到离 r r r 最近且小于 r r r 的 x = 2 k x=2^k x=2k。设答案为 f ( l , r ) f(l,r) f(l,r)。
显然从大往小选的时候, [ x , r ] [x,r] [x,r] 的数都不会冲突。相对应地,在 [ 2 x − r , x ) [2x-r,x) [2x−r,x) 的数会跟选中的数一一冲突,于是显然有 f ( l , r ) = r − x + 1 + f ( l , 2 x − r − 1 ) f(l,r)=r-x+1+f(l,2x-r-1) f(l,r)=r−x+1+f(l,2x−r−1)。这样直接递归,时间复杂度 O ( m log n ) O(m\log n) O(mlogn)。边界条件是 l > r l>r l>r 的前一步。
现在考虑第二问。设 s r = ∑ l = 1 r f ( l , r ) s_r=\sum\limits_{l=1}^rf(l,r) sr=l=1∑rf(l,r),答案就是求 ∑ r = 1 n s r \sum\limits_{r=1}^n s_r r=1∑nsr。思考如何统计 s r s_r sr。对于一个确定的 r r r,讨论 l l l:
将三个贡献加起来, a n s = ( r − x + 1 ) ( r − x + 2 ) 2 + ( x − 1 ) ( r − x + 1 ) + s 2 x − r − 1 ans=\frac{(r-x+1)(r-x+2)}{2}+(x-1)(r-x+1)+s_{2x-r-1} ans=2(r−x+1)(r−x+2)+(x−1)(r−x+1)+s2x−r−1,于是 O ( n ) O(n) O(n) 递推搞定。
总时间复杂度 O ( m log n + n ) O(m\log n+n) O(mlogn+n),吊打 std!
代码奇短,非常好写。
注意递归函数中的全局变量,先计算再递归。
#include
using namespace std;
const int N = 300005;
int n, m, type, pw;
long long ans = 0, sum[N];
int solve(int l, int r) {
while (pw > r) pw >>= 1;
if (l >= 2 * pw - r) return min(r - l + 1, r - pw + 1);
return r - pw + 1 + solve(l, 2 * pw - r - 1);
}
int main() {
freopen("kuhu.in", "r", stdin);
freopen("kuhu.out", "w", stdout);
scanf("%d%d%d", &n, &m, &type);
for (int i = 1, l, r; i <= m; i++) scanf("%d%d", &l, &r), pw = 1 << 28, printf("%d\n", solve(l, r));
pw = 2, sum[1] = 1;
for (int i = 2; i <= n; i++) {
if ((pw << 1) <= i) pw <<= 1;
sum[i] = 1ll * (i - pw + 1) * (i - pw + 2) / 2 + 1ll * (pw - 1) * (i - pw + 1) + sum[pw * 2 - i - 1];
}
for (int i = 1; i <= n; i++) ans += sum[i];
printf("%lld", type * ans);
return 0;
}