CF1428G Lucky Numbers
一、题目
二、解法
首先观察权值完全是由数位决定的,我们考虑按位规划,每一位可以选不超过 \(3k\) 个 \(3\),每一个 \(3\) 贡献是 \(F_i\)
这样算出的结果很可能不合法,有些数位是会为了满足题目限制而选取 \(0,3,6,9\) 以外的数。
结论:最优解中每个数位最多有一个选取 \(0,3,6,9\) 以外的数。因为如果出现了两个及以上可以通过调整只剩一个,而且调整之后的解一定更优。
那么把所有特别的数位都集中在一个数里,设 \(dp[i]\) 为总和为 \(i\) 的最优解,那么初始化 \(dp[x]\) 为数字 \(x\) 的权值,也就是直接把这个数考虑了。那么剩下的问题可以按位规划了,每一位可以选不超过 \(3k-3\) 个 \(3\),每一个 \(3\) 贡献是 \(F_i\)
不难发现这是一个多重背包问题,可以二进制优化多重背包,时间复杂度 \(O(n\log n)\)
三、总结
还是解决限制类问题,可以通过特殊考虑使问题变简单。
背包问题的特殊结论:有一个物品是特殊的,它的选取方式和其他物品不一样,特殊考虑它就能让问题变简单。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,k,q,a[M],dp[M];
void get(int v,int c)
{
for(int i=n;i>=v;i--)
dp[i]=max(dp[i],dp[i-v]+c);
}
void work(int v,int c)
{
int now=min(k,n/v);
for(int i=1;i<now;i<<=1) get(v*i,c*i),now-=i;
get(v*now,c*now);
}
signed main()
{
n=1e6;k=3*read()-3;
for(int i=0;i<=5;i++)
a[i]=read();
for(int i=1;i<=n;i++)
{
int now=0,x=i;
while(x)
{
int t=x%10;
if(t%3==0) dp[i]+=(t/3)*a[now];
now++;x/=10;
}
}
for(int i=0,sz=1;i<=5;i++)
work(sz*3,a[i]),sz*=10;
q=read();
while(q--) printf("%lld\n",dp[read()]);
}