悬两关求调,注释详细
查看原帖
悬两关求调,注释详细
174477
sdyzpf楼主2024/8/9 11:51
//#pragma GCC optimize("Ofast,unroll-loops")
#include<bits/stdc++.h>
#define int long long
using namespace std;
int q,l,r,lim;
__int128 W=72,U;//分别为状态的二进制位数,状态的上限,W开到90也能过 
int f[19][10][20005];//f[i][j][mask]:在mask的基础上再填i位,最终的差不超过j的方案数(方案数指新增的i位的填法个数)
int nxt[20005][10];//nxt[mask][c]:当前的状态用数字c进行一次转移,转移到的状态
map<__int128,int> id;//记录某个状态对应的编号
int tot;//编号的上限
__int128 val[20005];//val[i]:编号i对应的状态本身 
vector<int> vec[10];//vec[i]:差值最小为i的状态集合 
inline int getnxt(__int128 u,int c){
	__int128 g=(u<<c)|(u>>c);//(u<<c)对应将c加入当前较大的数中,(u>>c)对应将c加入当前较小的数中,且当前较小的数仍为它
	for(int i=0;i<c;i++)//处理将c加入当前较小的数中, 它变为较大的数的情况
		if((u>>i)&1) g|=(1<<(c-i));//此时c-i小于等于9,不用担心炸int
	g&=U;//除去(u<<c)溢出的高于W的位数上的1
	return g;
}
inline int getans(__int128 x){
	for(int i=0;i<=W;i++)
		if((x>>i)&1) return i;
	return 724;//小鹿生日 
}
void init(){
	U=1;U<<=(W+1);U--;//处理U 
	queue<pair<int,int> > q;//bfs预处理nxt数组,pair的first存搜索到的状态编号,second存当前搜索的深度
	q.push({1,0});id[1]=++tot;val[tot]=1;//初始状态入队列(一个数字都没有,即差为0) 
	while(!q.empty()){
		int u=q.front().first,dep=q.front().second;
		q.pop();
		if(dep>=18) continue;//搜18层,即转移18次可以覆盖所有我们想要的状态 
		for(int c=0;c<10;c++){//我们拿0~9分别对u进行一次转移
			int v=getnxt(val[u],c);
			if(id[v]!=0) nxt[u][c]=id[v];//如果已经有过编号了 
			else{
				id[v]=++tot;val[tot]=v;nxt[u][c]=id[v];
				q.push({nxt[u][c],dep+1});//新状态入队 
			}
		}
	}
	for(int i=1;i<=tot;i++) vec[getans(val[i])].push_back(i);//最低位1的位置即为该状态的最小差值 
	for(int j=0;j<9;j++){//枚举差值,任何数都能构造出差值小于等于9的方案,所以枚举到8即可
		for(int t=0;t<=j;t++)//差值为0~j的都符合差值小于等于j的条件
			for(int mask:vec[t]) f[0][j][mask]=1;//初始化,有0位要填自然只有1种情况 
		for(int i=1;i<=18;i++)//枚举填哪一位
			for(int mask=1;mask<=tot;mask++)//枚举状态 
				for(int c=0;c<10;c++) f[i][j][mask]+=f[i-1][j][nxt[mask][c]];//枚举填的数字
	}
}
inline int calc(int x){
	if(lim>=9) return x+1;//任何一个数都满足条件,记得算上0
	x++;//为了方便计算x本身是否合法,因为下面只会计算到每一位顶满的贡献
	string s=to_string(x);//存储lim拆分后的数位
	int len=s.length(),ans=0,u=1;//len为lim的位数,ans为答案,u为初始转态 
	for(int i=0;i<len;i++){//前i-1位已经顶满
		for(int c=0;c<s[i]-'0';c++) ans+=f[len-i-1][lim][nxt[u][c]];//计算第i位不顶满的贡献
		u=nxt[u][s[i]-'0'];//循环到下一层,计算第i位顶满的贡献 
	}
	return ans;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	init();
	cin>>q;
	while(q--){
		cin>>l>>r>>lim;
		cout<<calc(r)<<' '<<calc(l-1)<<'\n';//calc(x):0~x之间的合法数个数 
	}
	return 0;
}
2024/8/9 11:51
加载中...