【2011集训队出题】Digit

Description:

  在数学课上,小T又被老师发现上课睡觉了。为了向全班同学证明小T刚才没有好好听课,数学老师决定出一道题目刁难一下小T,如果小T答不出,那么……
  情节就按照俗套的路线发展下去了,小T显然无法解决这么复杂的问题,可怜的小T只能向你求助:
  题目是这样的:
  求一个满足条件的n位数A(不能有前导0),满足它的数字和为s1,并且,A*d的数字和为s2.
  1≤n≤100,0≤s1≤n*9,0≤s2≤(n+1)*9,0≤d≤9

题解:

考虑从高位到低位确定每一个数字,设f[i][j][k][p]表示:当前到第i位,前i位没有乘d的数字和是j,乘了d且加上后面的进位的数字和是k,后面没有确定的位乘了d给当前第i位进p。

对于每一个状态f[i][j][k][p],可以枚举下一位以后乘上d的高位s,由于下一位以后必须进给第i位p,所以s=p*10->p*10+9,那么下一位的数字x=s/d(下取整),下两位需进给下一位p’=s-x*d。
于是得出转移f[i][j][k][p]->f[i+1][j+x][k+s%10][p’]。

以上就是最直接的dp做法。

这样直接dp,状态有100*900*909*10=818100000,再算上转移,似乎怎么优化都不可过。

然而题解给优化过去了(%%%)。

由于出题人的方法过于牛逼,所以弱弱的我们只能另辟蹊径了~~。

随便想想就知道有很多状态是无用的,并且需要的只是最小的解,这启发我们用搜索。

按照一定顺序去搜索,搜到一组解就退了,这样是很快的,当然还不足以让我们过,需要加一些剪枝。

剪枝0:如果当前的数字和已经超过s1、s2,……

剪枝1:假设剩下的位全部是9,如果依然不够s1、s2,那么直接退了。

剪枝2:记忆化
因为是按照从小到大的顺序搜索的,所以一个状态走过了可以不用再走。

但是状态数有100*900*909*10=818100000那么多,空间炸了怎么办?

可以用压位的方法来存,比如说一个32位整数,占4字节,就可以存32个布尔了,因此真正的空间是818100000/32=25565625个32位整形,c++选手直接用bitset就行了。

时间复杂度是玄学的,但是可以感受到是很难有数据把搜索卡到T。

Code:

#include<cstdio>
#include<bitset>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

int n, s1, s2, d, a[105], bz;

int sum(int x) {
    int s = 0;
    while(x) s += x % 10, x /= 10;
    return s;
}

int tot;

bitset<1000000000> b;

void dg(int o, int p1, int p2, int p) {
    if(bz) return;
    if(p1 > s1 || p2 > s2) return;
    if((n - o + 1) * 9 + p1 < s1) return;
    if((n - o + 1) * 9 + p2 < s2) return;
    if(p > d) return;
    int num = p + p2 * 11 + p1 * 9999 + o * 8999100;
    if(b[num]) return;
    b[num] = 1;
    if(o > n) {
        if(p != 0) return;
        if(p1 == s1 && p2 == s2 && p == 0) {
            fo(i, 1, n) printf("%d", a[i]);
            printf("\n");
            bz = 1;
        }
        return;
    }
    fo(i, p * 10, p * 10 + 9) {
        int x = i / d, np = i - x * d;
        a[o] = x;
        dg(o + 1, p1 + x, p2 + i % 10, np);
        if(bz) return;
    }
}

int main() {
    scanf("%d %d %d %d", &n, &s1, &s2, &d);
    fo(i, 1, 9) fo(p, 0, d - 1)
        a[1] = i, dg(2, i, sum(i * d + p), p);
    if(!bz) printf("-1");
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值