题意
分析
其实统计出现次数与出现在矩阵的那个位置无关.所以我们定义
f
(
i
)
f(i)
f(i)表示
i
i
i的出现次数.那么就有转移方程式
f
(
i
)
=
1
+
∑
j
+
r
e
v
(
j
)
=
i
f
(
j
)
f(i)=1+\sum_{j+rev(j)=i}f(j)
f(i)=1+j+rev(j)=i∑f(j)但是这样的话
j
j
j可以取的值太多了,无法DP,怎么办呢?题解给出了巧妙的优化.我们只考虑那些形如"
x
+
r
e
v
(
x
)
x+rev(x)
x+rev(x)“的
j
j
j来转移,那么剩下的不形如”
x
+
r
e
v
(
x
)
x+rev(x)
x+rev(x)“的数一定在矩阵中只出现了一次.定义形如”
x
+
r
e
v
(
x
)
x+rev(x)
x+rev(x)"的并集为
S
S
S.那么转移就可以写成:
f
(
i
)
−
1
=
∑
j
+
r
e
v
(
j
)
=
i
f
(
j
)
=
∑
j
+
r
e
v
(
j
)
=
i
,
j
⊂
S
f
(
j
)
+
∑
j
+
r
e
v
(
j
)
=
i
,
j
⊂
̸
S
1
=
∑
j
+
r
e
v
(
j
)
=
i
,
j
⊂
S
(
f
(
j
)
−
1
)
+
∑
j
+
r
e
v
(
j
)
=
i
1
\begin{aligned}f(i)-1&=\sum_{j+rev(j)=i}f(j)\\&=\sum_{j+rev(j)=i,j\sub S}f(j)\ \ + \sum_{j+rev(j)=i,j\sub\not S}1\\&=\sum_{j+rev(j)=i,j\sub S}\left(f(j)-1\right)\ \ + \sum_{j+rev(j)=i}1\end{aligned}
f(i)−1=j+rev(j)=i∑f(j)=j+rev(j)=i,j⊂S∑f(j) +j+rev(j)=i,j⊄S∑1=j+rev(j)=i,j⊂S∑(f(j)−1) +j+rev(j)=i∑1
那么转移数就等于状态数了,然而搜出来可以发现,在
[
1
,
1
0
10
]
[1,10^{10}]
[1,1010]内
S
S
S的元素大约是
2541196
2541196
2541196个,所以我们只需要爆搜预处理然后DP就行了,+号右边的部分是可以在爆搜过程中处理出来的.最后求一个
f
f
f的前缀和,那么对于
[
L
,
R
]
[L,R]
[L,R]区间内的数总出现次数就是
f
(
u
p
p
e
r
_
b
o
u
n
d
(
R
)
−
1
)
−
f
(
u
p
p
e
r
_
b
o
u
n
d
(
L
−
1
)
−
1
)
+
(
R
−
L
+
1
)
f(upper\_bound(R)-1)-f(upper\_bound(L-1)-1)+(R-L+1)
f(upper_bound(R)−1)−f(upper_bound(L−1)−1)+(R−L+1).这里的
u
p
p
e
r
_
b
o
u
n
d
−
1
upper\_bound-1
upper_bound−1表示找到数组中小于等于当前值的最靠后的下标.
那到底怎么爆搜呢?要找形如 x + r e v ( x ) x+rev(x) x+rev(x)的数,首先枚举 x x x的位数分别搜索.举个栗子,x位数为5.设 x = a b c d e ‾ ( a > 0 ) x=\overline{abcde}(a>0) x=abcde(a>0).所以 x + r e v ( x ) = ( a + e ) ( b + d ) ( c + c ) ( b + d ) ( a + e ) ‾ x+rev(x)=\overline{(a+e)(b+d)(c+c)(b+d)(a+e)} x+rev(x)=(a+e)(b+d)(c+c)(b+d)(a+e).当然这是要进位的,为了方便不写了.两个位数加起来值域是 [ 0 , 18 ] [0,18] [0,18],那么我们只需要枚举这个和爆搜,然后顺便算一下方案就行了.这样爆搜可能有相同的数,把相同的数分开存也只有 2541258 2541258 2541258个,所以搜出来排序去重就行了,注意去重要把方案数加起来.
CODE
#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
template<typename T>inline void read(T &num) {
char ch; while((ch=getchar())<'0'||ch>'9');
for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
}
const int MAXN = 2600005;
int cur, tot, id[MAXN];
LL num[MAXN], g[MAXN], arr[MAXN], sum[MAXN], mul[11];
LL rev(LL x) {
LL res = 0;
while(x) res = res*10 + x%10, x /= 10;
return res;
}
void ser(int i, int len, LL x, LL ways) { //way表示到当前的方案数
if(x > mul[10]) return;
if(i == (len+1)>>1) {
num[++cur] = x; id[cur] = cur; g[cur] = ways; return;
}
if(!i) {
if(i == len-i-1)
for(int digit = 1; digit <= 9; ++digit)
ser(i+1, len, x+digit*mul[i]*2, ways);
else
for(int digit = 1; digit <= 18; ++digit)
ser(i+1, len, x+digit*(mul[i]+mul[len-i-1]), ways*(digit<=9?digit:9-(digit-10)));
}
else {
if(i == len-i-1)
for(int digit = 0; digit <= 9; ++digit)
ser(i+1, len, x+digit*mul[i]*2, ways);
else
for(int digit = 0; digit <= 18; ++digit)
ser(i+1, len, x+digit*(mul[i]+mul[len-i-1]), ways*(digit<=9?digit+1:9-(digit-10)));
}
}
inline bool cmp(const int &i, const int &j) { return num[i] < num[j]; }
int main () {
mul[0] = 1; for(int i = 1; i <= 10; ++i) mul[i] = mul[i-1] * 10;
for(int len = 1; len <= 10; ++len) ser(0, len, 0, 1);
sort(id+1, id+cur+1, cmp); //排序编号
for(int i = 1; i <= cur+1; ++i)
if(num[id[i]] == num[id[i-1]]) g[id[i]] += g[id[i-1]]; //可能有相同的数
else if(i > 1) arr[++tot] = num[id[i-1]], sum[tot] = g[id[i-1]];
for(int i = 1; i <= tot; ++i)
sum[lower_bound(arr+1, arr+tot+1, arr[i]+rev(arr[i]))-arr] += sum[i], sum[i] += sum[i-1]; //DP+求前缀和
int Q; LL L, R, P, ans = 0;
read(Q);
while(Q--) {
read(L), read(R), read(P);
ans ^= (sum[upper_bound(arr+1, arr+tot+1, R)-arr-1]-sum[upper_bound(arr+1, arr+tot+1, L-1)-arr-1] + R-L+1) % P;
}//O(log)询问
printf("%lld\n", ans);
}