【51Nod1623】完美消除-数位DP+状态压缩+单调栈

本文深入探讨了如何运用数位DP、状态压缩和单调栈解决完美消除问题,通过详细解释算法原理和步骤,提供了一种高效计算数字消除次数的方法。并附带了实现代码,帮助读者理解并实践算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试地址:完美消除
做法: 本题需要用到数位DP+状态压缩+单调栈。
对于一个数字,如何求出它的最小消除次数?把每一位一一推入单调栈(栈顶元素最大),并在最后把所有的元素都出栈,那么元素出栈的总次数就是最小消除次数。这一点做过单调栈题的同学应该很容易能看出来了。
那么对于这一题,显然看出是数位DP,而且数位DP也是从高位到低位转移。我们又发现数位只有 0 0 0 ~ 9 9 9 10 10 10个数字,于是我们令 f ( i , j , k ) f(i,j,k) f(i,j,k)为前 i i i位中,单调栈中元素的状态为 j j j,当前已经出栈的次数为 k k k的数有多少个,然后再注意一下正常数位DP的上界问题,直接转移即可。如果担心每次都用最暴力的计算转移的方法会超时,可以先预处理出某一种单调栈状态在加入某个数字后的状态,以及中间会出栈的元素个数,这样DP时就可以 O ( 1 ) O(1) O(1)转移了。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l,r,ans=0,f[21][2010][21]={0};
int k,n,s[21],nxt[2010][11],cnt[2010][11]={0};

void init()
{
	for(int i=0;i<(1<<10);i++)
		for(int j=9;j>=0;j--)
		{
			cnt[i][j]=cnt[i][j+1];
			if (i&(1<<j)) cnt[i][j]++;
			nxt[i][j]=(i&((1<<j)-1))|(1<<j);
		}
}

ll solve()
{
	int lasts=1,lastk=0;
	
	for(int i=n;i>=1;i--)
	{
		memset(f[i],0,sizeof(f[i])); 
		for(int j=0;j<(1<<10);j++)
			for(int p=0;p<=18;p++)
				for(int q=0;q<=9;q++)
					f[i][nxt[j][q]][p+cnt[j][q+1]]+=f[i+1][j][p];
		
		if (i<n)
		{
			for(int j=0;j<s[i];j++)
				f[i][nxt[lasts][j]][lastk+cnt[lasts][j+1]]++;
		}
		
		for(int j=1;j<=((i==n)?(s[i]-1):9);j++)
			f[i][(1<<j)+1][0]++;
		
		lastk+=cnt[lasts][s[i]+1];
		lasts=nxt[lasts][s[i]];
	}
	
	ll ret=0;
	if (cnt[lasts][1]+lastk==k) ret++;
	for(int i=0;i<(1<<10);i++)
	{
		int x=k-cnt[i][1];
		if (x>=0) ret+=f[1][i][x];
	}
	return ret;
}

int main()
{
	init();
	scanf("%lld%lld%d",&l,&r,&k);
	l--;
	
	if (l)
	{
		n=0;
		while(l)
		{
			s[++n]=l%10ll;
			l/=10;
		}
		ans-=solve();
	}
	
	n=0;
	while(r)
	{
		s[++n]=r%10ll;
		r/=10;
	}
	ans+=solve();
	printf("%lld",ans);
	
	return 0; 
}
### 关于51Nod 3100 上台阶问题的C++解法 #### 题目解析 该题目通常涉及斐波那契数列的应用。假设每次可以走一步或者两步,那么到达第 \( n \) 层台阶的方法总数等于到达第 \( n-1 \) 层和第 \( n-2 \) 层方法数之和。 此逻辑可以通过动态规划来解决,并且为了防止数值过大,需要对结果取模操作(如 \( \% 100003 \)[^1])。以下是基于上述思路的一个高效实现: ```cpp #include <iostream> using namespace std; const int MOD = 100003; long long f[100010]; int main() { int n; cin >> n; // 初始化前两项 f[0] = 1; // 到达第0层有1种方式(不移动) f[1] = 1; // 到达第1层只有1种方式 // 动态规划计算f[i] for (int i = 2; i <= n; ++i) { f[i] = (f[i - 1] + f[i - 2]) % MOD; } cout << f[n] << endl; return 0; } ``` 以上代码通过数组 `f` 存储每层台阶的结果,利用循环逐步填充至目标层数 \( n \),并最终输出结果。 --- #### 时间复杂度分析 由于仅需一次线性遍历即可完成所有状态转移,时间复杂度为 \( O(n) \)。空间复杂度同样为 \( O(n) \),但如果优化存储,则可进一步降低到 \( O(1) \): ```cpp #include <iostream> using namespace std; const int MOD = 100003; int main() { int n; cin >> n; long long prev2 = 1, prev1 = 1, current; if (n == 0 || n == 1) { cout << 1 << endl; return 0; } for (int i = 2; i <= n; ++i) { current = (prev1 + prev2) % MOD; prev2 = prev1; prev1 = current; } cout << prev1 << endl; return 0; } ``` 在此版本中,只保留最近两个状态变量 (`prev1`, `prev2`) 来更新当前值,从而节省内存开销。 --- #### 输入输出说明 输入部分接受单个整数 \( n \),表示台阶数量;程序会返回从地面走到第 \( n \) 层的不同路径数目,结果经过指定模运算处理以适应大范围数据需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值