方伯伯的商场之旅

http://www.lydsy.com/JudgeOnline/problem.php?id=3598

能做的最后一道方伯伯~~


看了一下,一开始以为是区间dp,后来才知道是数位dp.


考虑这样一种做法,先枚举当前石子是全部搬到mid处最优,然后计算,这样貌似对于每一个数都需要枚举。

那么假设我们处理搬到mid处最优的数有多少个,能计算出来,答案也就出来了。


怎么搞呢?貌似不太好搞。


参考了省队rank3的思想。

首先初始把所有石子搬到1,算出代价,这一般不是最优解。

考虑把把搬到i的所有石子,转而搬到i+1,这样的代价是 sum[i] - 后缀(i+1)  如果这个<0,就更优。

于是我们考虑枚举i从1到n-1,计算从i搬到i+1能优化的代价有多少。

可以用dp[pos][change]表示处理了前pos位且所有的数前面pos<极限,这样的所有数在i搬到i+1的时候 优化了change ,最后能优化的总代价。

(有点晕,还是看代码,自己理解吧~!)

简单点就是 对于每一次i搬到i+1考虑前pos位已经优化了change,计算出当前状态后续拓展状态一共能优化多少。 记录的时候不要触界(比如前3为极限123,你考虑第3位只能维护<122的,因为如果你达到123,后面有的数本来越界了但是没考虑到,就多计算了)      其实也可以多开一维,dp[pos][change][bool p] p表示是否触界。

但是这样新增一维空间就变大了(虽然不开时间可能会加长~~~~)


然后计算一下就好了。

(我的数位dp版本和网上大神的不太一样,我的bool a表示是否在界内,a=1表示前面的已经比极限小了,可以枚举到上限K-1       ;a=0表示刚好触界。下位只能枚举到bit[n])


(有的地方用ll没考虑清楚,估计爆int了; 不想调,一键改成了ll,可能慢了一些,但还是A了。)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
#define ll long long
using namespace std;
ll L,R,K;
ll pos[52];
ll dp[52][1600],num[52][2];//num[i][j]表示前i位 是否越界的数字有多少 
//dp表示代价 首先全部为1时代价 
ll dfs(ll n,bool a)
{
	if(!n)return num[n][a]=1,0;//到最后
	if(~dp[n][a])return dp[n][a];
	ll ed=a?K-1:pos[n];
	ll &f=dp[n][a]=0,&sum=num[n][a]=0;
	for(ll i=0;i<=ed;i++)
	{
		f+=dfs(n-1,a||i<pos[n])+num[n-1][a||i<pos[n]]*i*(n-1);
		sum+=num[n-1][a||i<pos[n]];
	} 
	return f;
} 
ll dfs2(ll n,ll change,ll mid,bool a)//dp[i][j]表示前i位,优化了change的能优化多少 
{
	if(!n)return change;
	if(a&&(~dp[n][change]))return dp[n][change];
	ll ed=a?K-1:pos[n];
	ll ff=(n>mid)?1:-1;
	ll ans=0;
	for(ll i=0;i<=ed;i++)
	{
		ll next=change+(ll)i*ff;
		if(next<0)break;
		ans+=dfs2(n-1,next,mid,a||i<pos[n]);
	}
	if(a)dp[n][change]=ans;
	return ans;
}
ll solve(ll x)
{
	ll i;
	for(i=1;;i++)
	{
		if(x)
		{
			pos[i]=x%K;
			x/=K;
		}
		else break;
	}
	i--;
	memset(dp,-1,sizeof(dp));
	ll res=dfs(i,0);
	for(ll j=1;j<i;j++)
	{
		memset(dp,-1,sizeof(dp));
		res-=dfs2(i,0,j,0);
	}
	return res;
}
int main()
{
	scanf("%lld%lld%lld",&L,&R,&K);
	printf("%lld\n",solve(R)-solve(L-1));
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值