题解 P7802 [COCI2015-2016#6] SAN

题目传送门

算法分析:数位 dp

在讲正解之前先说一下部分分。

注意到对于 \(50\%\) 的数据,\(1\le L,R\le10^6\),似乎可以暴力做出来。但由于我们不知道每个数可能出现的位置,直接开二维数组模拟非常容易炸(事实证明它就炸了……),因此我们要换一种方式枚举。注意到每一个数字仅能转变成为另一种数字,且每个数字至少出现一次(在第一列)。于是可以递推得到 \(cnt_{rev(i)}+=cnt_i​\) 然后前缀和统计即可。

给出 \(50\%\) 的代码。可以获得 \(80pts\)成绩。

namespace p50 {
	const int N=1e6;
	int cnt[N+5];
	inline void init() {
		F(i,1,N)++cnt[i];
		int p;
		F(i,1,N) {
			p=i+rev(i);
			if(p<=N)cnt[p]+=cnt[i];
		}
		F(i,1,N)cnt[i]+=cnt[i-1];
	}
	int main() {
		init();
		n=read();
		int l,r;
		F(i,1,n) {
			l=read(),r=read();
			printf("%d\n",cnt[r]-cnt[l-1]);
		}
	}
}

接下来讲一下正解。

对于一个 \(x\),记 \(y=x+rev(x)\)。直接枚举 \(x\) 肯定超时,因此考虑枚举 \(y\)。在 \(y\)\(10\) 位时,先不计算进位,那么 \(y\) 的前五位与后五位是对称的。除最高位不为 \(0\)(那最低位也不为 \(0\)),其余每一位可能是 \(0\sim 18\) 的一种。那么总共有 \(18\times 19^4=2345778\) 种情况。是很小的,因此考虑用 dfs,先确定位数,再数位 dp 枚举 \(y\) 的每一位。

有了情况,接下来考虑统计次数。事实上,dfs 时已经算出了出现在第二列的次数,因为枚举的时候枚举的就是 \(x+rev(x)\)。对于更后面的列,由于后面每个数都是由前面的数转移来的,那么可以枚举每一个满足要求的数。由于所有的可能的数都已经处理出来了,就可以用一个数组 \(sum\) 统计一个数出现的次数。和上面类似的,\(sum_{x+rev(x)}+=sum_x-1\)。因为第二行已经统计过了,所以减一。对 \(sum\) 做前缀和即可做到快速查询。

代码在细节处略有不同,注意一下即可。

#include<bits/stdc++.h>
#define ll long long
#define reg register
#define F(i,a,b) for(reg int i=(a);i<=(b);++i)
using namespace std;
bool beginning;
inline ll read();
const int M=4e6+5,N=20;
int n,cnt,p[N],q[N];
ll pw[N] {1},sum[M],a[M],tot[M],b[M];
inline ll rev(ll x) {
	ll ans=0;
	while(x)ans=ans*10+x%10,x/=10;
	return ans;
}
void dfs(int n,int x,ll s,ll t) {
	if(x==(n+1>>1)) {
		tot[++cnt]=t;
		b[cnt]=a[cnt]=s;//记录可能的情况 
		return;
	}
	F(i,(x==0),18) {
		if(n==(x<<1|1) and (i&1))continue;
		ll tmp=x?p[i]:q[i];
		if(n==(x<<1|1))tmp=1;
		if(!tmp)continue;
		if(n==(x<<1|1))dfs(n,x+1,s+pw[x]*i,t*tmp);
		else dfs(n,x+1,s+(pw[x]+pw[n-x-1])*i,t*tmp);
	}
}
inline void init() {
	F(i,1,15)pw[i]=pw[i-1]*10;
	F(i,0,9)F(j,0,9)++p[i+j],q[i+j]+=(i!=0);
	//出现某个和的不同组合情况数 
	F(i,1,10)dfs(i,0,0,1);
	//预处理 
	int m=cnt;
	sort(a+1,a+cnt+1);
	cnt=unique(a+1,a+cnt+1)-a-1;
	F(i,1,m) {
		int pos=lower_bound(a+1,a+cnt+1,b[i])-a;
		sum[pos]+=tot[i];//初值 
	}
	F(i,1,cnt) {
		ll t=a[i]+rev(a[i]);
		int pos=lower_bound(a+1,a+cnt+1,t)-a;
		if(pos!=cnt+1 and a[pos]==t)sum[pos]+=sum[i];
		++sum[i];
		sum[i]+=sum[i-1];
	}
}

inline ll cal(ll x) {
	if(!x)return 0;
	int pos=upper_bound(a+1,a+cnt+1,x)-a-1;
	return x+sum[pos]-pos;
}
bool ending;
int main() {
//	system("color fc");
// 	printf("%.2lfMB\n",1.0*(&beginning-&ending)/1024/1024);
	init();
	n=read();
	ll l,r;
	F(i,1,n) {
		l=read(),r=read();
		printf("%lld\n",cal(r)-cal(l-1));
	}
	return 0;
}
inline ll read() {
	reg ll x=0;
	reg char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x;
}

AC

欢迎交流讨论,请点个赞哦~

posted @ 2021-08-08 21:42  Maplisky  阅读(75)  评论(0编辑  收藏  举报