zoukankan      html  css  js  c++  java
  • 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);
    }
    
  • 相关阅读:
    宝塔相关问题
    免费xshell下载
    服务器断电mysql无法恢复
    mysql相关知识
    svn
    tortoisesvn下载 和svn 安装
    nginx+lua乐观锁实现秒杀
    c# asp.net 生成唯一订单号
    c# 关闭软件 进程 杀死进程
    国内开源软件镜像地址搜集
  • 原文地址:https://www.cnblogs.com/Orz-IE/p/12039386.html
Copyright © 2011-2022 走看看