51nod 1989 竞赛表格 (爆搜+DP算方案)

题意

自己看

分析

其实统计出现次数与出现在矩阵的那个位置无关.所以我们定义 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)=if(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)=if(j)=j+rev(j)=i,jSf(j)  +j+rev(j)=i,j̸S1=j+rev(j)=i,jS(f(j)1)  +j+rev(j)=i1
那么转移数就等于状态数了,然而搜出来可以发现,在 [ 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(L1)1)+(RL+1).这里的 u p p e r _ b o u n d − 1 upper\_bound-1 upper_bound1表示找到数组中小于等于当前值的最靠后的下标.

那到底怎么爆搜呢?要找形如 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值