题目
思路
看了看大多数题解,似乎都是神仙的数位 d p dp dp ?
本蒟蒻实在是没办法往那个方面想,所以给出了一种更加 令人迷惑 美好的做法。
声明:下文中的区间都只涉及整数。例如, [ a , b ] [a,b] [a,b] 实际指 [ a , b ] ∪ Z [a,b]\cup\Z [a,b]∪Z 。
简单的小情况
如果 n = 2 k n=2^k n=2k ,是否有非常舒服的做法呢?
给出这样一个定理: ∀ x ∈ [ 0 , 2 k ) , { x ⊕ r ∣ r ∈ [ 0 , 2 k ) } = [ 0 , 2 k ) \forall x\in[0,2^k),\{x\oplus r|r\in[0,2^k)\}=[0,2^k) ∀x∈[0,2k),{x⊕r∣r∈[0,2k)}=[0,2k)
用大白话来说,一个小于 2 k 2^k 2k 的数,异或上 2 k − 1 2^k-1 2k−1 以内的所有数,仍然得到这些数。
证明是极其简易的:异或的结果小于 2 k 2^k 2k ,且互不相同,只好是遍布 [ 0 , 2 k ) [0,2^k) [0,2k) 。
所以,如果 n = 2 k n=2^k n=2k ,可以直接发现,表中的所有值就是( a × b a\times b a×b 表示出现了 a a a 次 b b b ):
m × 0 + m × 1 + m × 2 + ⋯ + m × ( 2 k − 1 ) m\times 0+m\times 1+m\times 2+\cdots+m\times (2^k-1) m×0+m×1+m×2+⋯+m×(2k−1)
复杂的小局面
如果 n ≠ 2 k n\ne 2^k n=2k ,这能够难倒一直受虐、死者之心的小蒟蒻吗?
可以考虑将 n n n 拆分一下,按照最高位分一个类。或者说,把这 n n n 个数字,像在 0 / 1 T r i e 0/1\;Trie 0/1Trie 树上一样,放在左右子树中。
m
m
m 当然也可以这么做。然后绝招来了:两两匹配,四个递归!(还挺押韵的)
显然,两两匹配之后,结果是正确的。也就是说,将 [ 0 , n ) [0,n) [0,n) 分成了 A , B A,B A,B , m m m 则是 C , D C,D C,D ,那么最后的答案,可以转化为 ⟨ A , C ⟩ , ⟨ A , D ⟩ , ⟨ B , C ⟩ , ⟨ B , D ⟩ \langle A,C\rangle,\langle A,D\rangle,\langle B,C\rangle,\langle B,D\rangle ⟨A,C⟩,⟨A,D⟩,⟨B,C⟩,⟨B,D⟩ 四个相似的问题。
那么,我们选择最大的 k k k ,满足 2 k < n 2^k<n 2k<n ,然后切开!
于是,最高位的异或值就不归它管了。这种在 0 / 1 T r i e 0/1\;Trie 0/1Trie 树上的行走,当然是有这样性质的。
直接把高位已经得到的异或值丢进递归里面就可以了。
综合的中和
我们已经有了思路,用 f ( x , n , m ) f(x,n,m) f(x,n,m) 表示,已经得到的异或值是 x x x , [ 0 , n ) ⊕ [ 0 , m ) [0,n)\oplus[0,m) [0,n)⊕[0,m) 作出的贡献( k k k 是需要被考虑的,作为全局变量)。
不妨设 n ≥ m n\ge m n≥m 。如果 n = 2 k n=2^k n=2k ,那么可以直接计算出答案:因为得到的结果一定是 m × ( 0 + x ) , m × ( 1 + x ) , m × ( 2 + x ) , ⋯ , m × ( n − 1 + x ) m\times(0+x),m\times (1+x),m\times(2+x),\cdots,m\times(n-1+x) m×(0+x),m×(1+x),m×(2+x),⋯,m×(n−1+x)
如果 2 k < n < 2 k + 1 2^k<n<2^{k+1} 2k<n<2k+1 ,那么将 [ 0 , n ) [0,n) [0,n) 分解为 [ 0 , 2 k ) , [ 2 k , n ) [0,2^k),[2^k,n) [0,2k),[2k,n) ,再令 r = min ( 2 k , m ) r=\min(2^k,m) r=min(2k,m) ,将 [ 0 , m ) [0,m) [0,m) 分解为 [ 0 , r ) , [ r , m ) [0,r),[r,m) [0,r),[r,m) ,两两组合。在组合的时候,注意要给 x x x 加上一个 2 k 2^k 2k ,并且将 [ 2 k , n ) [2^k,n) [2k,n) 这种超过了 2 k 2^k 2k 的区间减掉一个 2 k 2^k 2k ——毕竟这个异或值已经累加到 x x x 里了。
可是复杂度呢?这不是一个 T ( n ) = 4 T ( n 2 ) + O ( 1 ) \mathcal T(n)=4\mathcal T(\frac{n}{2})+\mathcal O(1) T(n)=4T(2n)+O(1) 吗?
不是这样的。拆解一次,会拆出来 2 k 2^k 2k ,就变成 O ( 1 ) \mathcal O(1) O(1) 的了。
所以,我们每次只会实际递归一次,复杂度仍然是 O ( log n ) \mathcal O(\log n) O(logn) 的。
代码
- 计算等差数列要除以二,所以只能取模 2 P 2P 2P 。
-
m
=
0
m=0
m=0 只会是因为递归之前,
n
≤
r
∨
m
≤
r
n\le r\vee m\le r
n≤r∨m≤r,没有实际意义,
只能使代码变短。 - i i i 相当于 2 k 2^k 2k 中的 k k k ,亦可以理解为 0 / 1 T r i e 0/1\;Trie 0/1Trie 上面的行走。
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline long long readint(){
long long a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(long long x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x%10)^48);
}
# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }
const int LogN = 60;
typedef long long int_;
int P; int_ k;
int_ ans; // 答案
void solve(int_ now,int_ n,int_ m,int i=LogN){
const int_ r = 1ll<<i>>1, all = (1ll<<i)-1;
if(now+all <= k) return ; // 一定小于k
if(n < m) swap(n,m); // 保持 n 大 m 小
if(m <= 0) return ; // 不可能的情况!
if(n == all+1){ // 简单的小情况
if(now < k) // 有一些是不能用的
n -= k-now, now = k;
m %= P, n %= P<<1;
ans = ((n*(n-1)>>1)%P*m+ans)%P;
ans = ((now-k)%P*m%P*n+ans)%P;
return ;
}
solve(now,n-r,m-r,i-1);
solve(now^r,n-r,min(m,r),i-1);
solve(now^r,min(n,r),m-r,i-1);
solve(now,min(n,r),min(m,r),i-1);
}
int main(){
int_ n, m;
for(int T=readint(); T; --T){
n = readint(), m = readint();
k = readint(), P = readint();
ans = 0, solve(0,n,m);
printf("%lld\n",ans);
}
return 0;
}