[SCOI2014]方伯伯的商场之旅
题意: n n 个人,每个人面前有几堆石子。第 i i 个人面前的第 j j 堆的石子的数量是 i i 写成 K K 进制后的第 j j 位。每次操作可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量 * 移动的距离。要把位置在 [L,R] [ L , R ] 中的每个人的石子都合并成一堆石子,求最小代价。
数位 dp d p 的套路。考虑已经填了最高几位数,满足这些数要求的全部的数的代价。
我们先假设把所有的石子都移动到最低位上的代价,然后依次枚举从 i−>i+1i+1 i − > i + 1 i + 1 更优的数字有多少个。(好神啊)。
// luogu-judger-enable-o2
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
ll f[61][3010];
int a[20],k;
ll dfs1(int pos,int limit,ll sum)
{
if(!pos) return sum;
if((~f[pos][sum])&&(!limit)) return f[pos][sum];
int num= limit?a[pos]:k-1;
ll ans=0;
for(int i=0;i<=num;i++)
{
ans+=dfs1(pos-1,limit&&i==a[pos],sum+i*(pos-1));
}
if(!limit) f[pos][sum]=ans;
return ans;
}
ll dfs2(int pos,int limit,int d,ll sum)
{
if(sum>0) return 0;
if(!pos) return sum;
if((!limit)&&(~f[pos][-sum])) return f[pos][-sum];
int num= limit?a[pos]:k-1;
ll ans=0;
for(int i=0;i<=num;i++)
{
if(pos>=d) ans+=dfs2(pos-1,limit&&i==a[pos],d,sum-i);
else ans+=dfs2(pos-1,limit&&i==a[pos],d,sum+i);
}
if(!limit) f[pos][-sum]=ans;
return ans;
}
ll solve(ll x)
{
int cnt=0;
while(x)
{
a[++cnt]=x%k;
x/=k;
}
memset(f,-1,sizeof(f));
ll ans=dfs1(cnt,1,0);
for(int i=2;i<=cnt;i++)
{
memset(f,-1,sizeof(f));
ans+=dfs2(cnt,1,i,0);
}
return ans;
}
int main()
{
ll l,r;
cin>>l>>r>>k;
cout<1);
return 0;
}
[CQOI2016]手机号码
题意以下的号码被称作幸运号码:号码中要出现至少 3 个相邻的相同数字,且号码中不能同时出现 8 和 4 。手机号码一定是 11 位数,不含前导的 0 。输出 [L,R] 区间内所有满足条件的号码数量。
dp的时候直接记录位置,上一个数是什么,当前连续出现了几个相同的数,先前有没有出现过连续的三个数,卡不卡上界。然后暴力转移。
注意如果l=1e10,那么l-1是十位数,注意前导0的处理。(不然会有连续的三个前导0被计算进答案)。方法是强制最高位不能是0,或者记录前面的位有没有填过数。
// luogu-judger-enable-o2
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
ll f[20][15][5][2][2];
int a[20],cnt;
ll dfs(int pos,int limit,int pre,int last,int sta4,int sta8)
{
if(sta4&&sta8) return 0;
if(!pos) return last>=3;
if((!limit)&&(~f[pos][pre][last][sta4][sta8])) return f[pos][pre][last][sta4][sta8];
int num=limit?a[pos]:9;
ll ans=0;
for(int i=pos==cnt;i<=num;i++)
{
if(last==0) ans+=dfs(pos-1,limit&&i==a[pos],i,1,sta4||i==4,sta8||i==8);
else if(last==3) ans+=dfs(pos-1,limit&&i==a[pos],i,3,sta4||i==4,sta8||i==8);
else ans+=dfs(pos-1,limit&&i==a[pos],i,i==pre?last+1:1,sta4||i==4,sta8||i==8);
}
if(!limit) f[pos][pre][last][sta4][sta8]=ans;
return ans;
}
ll solve(ll x)
{
cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
return dfs(cnt,1,0,0,0,0);
}
int main()
{
ll l,r;
cin>>l>>r;
memset(f,-1,sizeof(f));
if(l==1e10)
{
cout<return 0;
}
cout<1);
return 0;
}
[AHOI2009]同类分布
题意:给出两个数 a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
我们枚举各位数字之和a,问题转化成有多少数的各位数字之和是a且能被a整除。我们发现只需要关注对a取模的余数。
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
ll f[20][180][180],mod,a[20];
ll dfs(int pos,int limit,int r,int sum)
{
if(pos*9+sum<mod||sum>mod) return 0;
if(!pos) return r==0&&sum==mod;
if((!limit)&&(~f[pos][r][sum])) return f[pos][r][sum];
int num=limit?a[pos]:9;
ll ans=0;
for(int i=0;i<=num;i++)
{
ans+=dfs(pos-1,limit&&i==a[pos],(r*10+i)%mod,sum+i);
}
if(!limit) f[pos][r][sum]=ans;
return ans;
}
ll solve(ll x)
{
int cnt=0;
while(x)
{
a[++cnt]=x%10;
x/=10;
}
ll ans=0;
for(int i=1;i<=cnt*9;i++)
{
mod=i;
memset(f,-1,sizeof(f));
ans+=dfs(cnt,1,0,0);
}
return ans;
}
int main()
{
ll l,r;
cin>>l>>r;
cout<1);
return 0;
}