0
点赞
收藏
分享

微信扫一扫

牛客月赛31-A|B

爱喝酒的幸福人 2022-02-07 阅读 50

A

题意:
就是给你两个数n和m,问你从1到n中有多少个数k满足k|m==k+m。

思考:
数据范围比较大,肯定不能暴力枚举,一看| == +的这种,就是m为1的位,k必须是0,m为0的位无所谓,但是0是不满足条件的,所以最后要减去。所以就用数位dp,其实第一眼就看出来是数位dp了,但是感觉好久没用了有点生疏,又看了一遍数位dp的介绍又想起来了:数位dp。这个题就是处理呗,正常的数位dp,把每一位拆开,然后去dfs,如果当前的满足条件就走下去,不满足就continue。
值得要说的呢就是limit和lead这俩,特别是lead总想着他会去掉0的情况,其实只是去掉有前导零的正数罢了,0没有前导零,因为0就是一个数,只是在这个题目中不满足,所以再-1而已。对于limit呢,因为数位dp的本质就是记忆化搜索嘛,当这个某个状态你记录下俩的时候,下次有人再次达到这种状态就可以直接返回值了,不用再去搜了,但是有时候是不满足的,因为这个状态的维度太少了,比如你只用3个维度,dp[pos][limti][lead],有时候是不满足条件的,因为如果咱俩尽管这三个维度是一样的,也有可能其他的地方不同,所以我不能用你记录的值。但是这样的情况一般只会出现在limit和lead身上,因为limit经常导致位数可以取到的值是多少这种情况。所以当我们要去记忆的时候,一定要注意只有limit和lead合法的时候我们才去记忆,否则我们不记忆,因为有时候会出错。

代码:

int T,n,m,k;
int va[N];
int dp[100][100];

int dfs(int pos,int limit,int lead,int can)
{
	if(pos<0) return can;
	if(!limit&&!lead&&dp[pos][can]!=-1) return dp[pos][can];
	int up = limit?va[pos]:1,res = 0;
	for(int i=0;i<=up;i++)
	{
		if(i&&(n>>pos&1)) continue;
		int limit_t = limit&&i==up,lead_t = lead&&i==0;
		res += dfs(pos-1,limit_t,lead_t,can||1);
	}
	if(!limit&&!lead) dp[pos][can] = res;
	return res;
}

int f(int x)
{
	mem(dp,-1);
	int pos = 0;
	while(x) va[pos++] = x%2,x /= 2;
	return dfs(pos-1,1,1,0);
}

signed main()
{
	IOS;
	cin>>T;
	while(T--)
	{
		cin>>n>>m;
		cout<<f(m)-1<<"\n";
	}
	return 0;
}

总结:
多多回顾以前的算法,多尝试尝试积累积累。

举报

相关推荐

0 条评论