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

题意

自己看

分析

其实统计出现次数与出现在矩阵的那个位置无关.所以我们定义f(i)f(i)表示ii的出现次数.那么就有转移方程式f(i)=1+j+rev(j)=if(j)f(i)=1+sum_{j+rev(j)=i}f(j)但是这样的话jj可以取的值太多了,无法DP,怎么办呢?题解给出了巧妙的优化.我们只考虑那些形如"x+rev(x)x+rev(x)“的jj来转移,那么剩下的不形如”x+rev(x)x+rev(x)“的数一定在矩阵中只出现了一次.定义形如”x+rev(x)x+rev(x)"的并集为SS.那么转移就可以写成:
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)=i1egin{aligned}f(i)-1&=sum_{j+rev(j)=i}f(j)\&=sum_{j+rev(j)=i,jsub S}f(j) + sum_{j+rev(j)=i,jsub ot S}1\&=sum_{j+rev(j)=i,jsub S}left(f(j)-1 ight) + sum_{j+rev(j)=i}1end{aligned}
那么转移数就等于状态数了,然而搜出来可以发现,在[1,1010][1,10^{10}]SS的元素大约是25411962541196个,所以我们只需要爆搜预处理然后DP就行了,+号右边的部分是可以在爆搜过程中处理出来的.最后求一个ff的前缀和,那么对于[L,R][L,R]区间内的数总出现次数就是f(upper_bound(R)1)f(upper_bound(L1)1)+(RL+1)f(upper\_bound(R)-1)-f(upper\_bound(L-1)-1)+(R-L+1).这里的upper_bound1upper\_bound-1表示找到数组中小于等于当前值的最靠后的下标.

那到底怎么爆搜呢?要找形如x+rev(x)x+rev(x)的数,首先枚举xx的位数分别搜索.举个栗子,x位数为5.设x=abcde(a>0)x=overline{abcde}(a>0).所以x+rev(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)}.当然这是要进位的,为了方便不写了.两个位数加起来值域是[0,18][0,18],那么我们只需要枚举这个和爆搜,然后顺便算一下方案就行了.这样爆搜可能有相同的数,把相同的数分开存也只有25412582541258个,所以搜出来排序去重就行了,注意去重要把方案数加起来.

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
", ans);
}
原文地址:https://www.cnblogs.com/Orz-IE/p/12039386.html