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()]);
}
posted @ 2021-08-02 11:32  C202044zxy  阅读(106)  评论(0编辑  收藏  举报