Uva 7344 Numbered Cards (状压DP)

本文介绍了一种利用状态压缩动态规划解决特定数位DP问题的方法,具体为求解1到N范围内,所有子集中任意两数任意位不相同的情况总数。通过预处理和搜索策略,实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/**
题意:从1 ~ N,选出若干个子集,满足子集里面任意两个数的任意两个数位没有相同的,一个数自身可以有相同的数位,求这样的子集的个数

思路:因为数字只有九个,考虑状压,先看状态为S的数(状态为0000000101代表只有2和0的数,比如200,20,220),设dp[S]:N以内子集状态为S
的数量,ans[S]:N以内的一个数的状态恰为S的数量,当选择状态为S的添加进集合时,那么有dp[T] += dp[K] * ans[S] (其中K交S为空集,
K|S为T),剩下就是计算ans[S],预处理出0~9,00~99,000~999...包含前导0的状态为S的数量res[S][1],res[S][2],res[S][3]....
剩下的是搜索,高位往低位搜索,当选择的位小于当前位的时候后面不用再去搜索了,因为怎么选择都可以,用res数组统计一下即可,当等于
的时候
**/
#include<bits/stdc++.h>
typedef long long ll;
const ll mod = 1e9 + 7;
const int maxn = 1200;
using namespace std;

struct P {
    int S, num;
    P() {}
    P(int s, int n) : S(s), num(n) {}
    bool operator < (P p) const {
        if(num != p.num) return num < p.num;
        return S < p.S;
    }
    void init() {
        int s = S; num = 0;
        while(s) {
            if(s & 1) num++;
            s >>= 1;
        }
    }
} p[maxn];
ll res[maxn][11], bit[11];
ll dp[maxn], ans[maxn];
int n, T, kase = 1;

void init() {
    memset(res, 0, sizeof res);
    for(int i = 0; i < 10; i++) res[1 << i][1] = 1;
    for(int i = 2; i < 11; i++) {
        for(int x = 0; x < 10; x++) {
            int S = 1 << x;
            for(int s = 0; s < 1024; s++) {
                int T = s | S;
                res[T][i] += res[s][i - 1];
                res[T][i] %= mod;
            }
        }
    }
    for(int i = 0; i < 1024; i++) {
        p[i] = P(i, 0);
        p[i].init();
    }
    sort(p, p + 1024);
}

int solve(int n) {
    int len = 0;
    while(n) {
        bit[++len] = n % 10;
        n /= 10;
    }
    return len;
}

void dfs(int l, int S, int limit) {
    if(!l) { ans[S]++; return ; }
    if(!limit && S > 1) {
        for(int x = 1; x < 1024; x++) {
            int T = x | S;
            ans[T] = (ans[T] + res[x][l]) % mod;
        }
        return ;
    }

    int up = limit ? bit[l] : 9;
    for(int i = 0; i < up; i++) {
        int newS = (S <= 1 && !i) ? 0 : S | (1 << i);
        dfs(l - 1, newS, 0);
    }
    int newS = (S <= 1 && !up) ? 0 : S | (1 << up);
    dfs(l - 1, newS, limit ? 1 : 0);
}

int main() {
    init();
    scanf("%d", &T);
    while(T--) {
        scanf("%d", &n);
        int len = solve(n);
        memset(ans, 0, sizeof ans);
        memset(dp, 0, sizeof dp);
        dfs(len, 0, 1);
        dp[0] = 1;
        for(int i = 2; i < 1024; i++) {
            int S = p[i].S;
            for(int j = 1023; j >= 0; j--) {
                int T = p[j].S;
                if((S | T) != T) continue;
                int K = T & (~S);
                dp[T] = (dp[T] + dp[K] * ans[S] % mod) % mod;
            }
        }
        ll t = 0;
        for(int i = 2; i < 1024; i++) t += dp[i];
        printf("Case %d: %lld\n", kase++, t % mod);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值