数位dp专题


这个题的数据特别大,很容易想到数位dp,但是判断条件是啥不清楚
打表发现这个函数f(n)就是二进制下的翻转每个位
要想f(n)=n,n必须为回文数

很明显的数位dp 发现正向推判断的时候会超时 再看见至少这两个字 引导你去往容斥的方向去想

至少重复出现一次的反面就是均不重复出现 这样我们dfs的时候用个2048的数来状压记录一下就好了

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=40;
const int maxx=2050;
int n,cnt;
void calc(int);
int a[maxn];
ll dp[maxn][maxx][2][2]; 
ll dfs(int pos,int mp,int limit,int lead){
	if(pos==cnt+1)return 1;
	if(dp[pos][mp][limit][lead]!=-1)return dp[pos][mp][limit][lead];
	ll ans=0;
	int up=limit?a[pos]:9;
	for(int i=0;i<=up;i++){
		if(lead&&i==0)ans+=dfs(pos+1,0,limit&&i==a[pos],1);
		else if(!((mp>>(i+1))&1))
		ans+=dfs(pos+1,mp|(1<<(i+1)),limit&&i==a[pos],0);
	}
	return dp[pos][mp][limit][lead]=ans;
}
int main(){
	cin>>n;
	calc(n);
     return 0;
}
void calc(int x){
	while(x){
		a[++cnt]=x%10;
		x/=10;
	}
	reverse(a+1,a+1+cnt);
	memset(dp,-1,sizeof(dp));
	cout<<n-dfs(1,0,1,1)+1;
}

这个题和上面一题类似 可以状压 但是唯一不同的就是这个题要求和

找每个位置是i的贡献 即可

对于每一个位置pos 枚举到的i 对答案产生的贡献为 10的pos次方乘i

下面的代码应该是写复杂了

其实只用维护一个个数 然后在相应位置对答案累加即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e6 + 10;
const int mod = 998244353;
typedef pair<ll, ll> pii;
ll dp[10005][2048];
ll dp2[10005][2048];
int a[10005], st;
ll base[10005];
pii dfs(int pos, int state, bool limit, bool lead) {
    if (pos == -1) {
        if ((state & st) == st) return {1,0};
        else return {0,0};
    }
    if (!limit && !lead && dp[pos][state] != -1) return {dp[pos][state], dp2[pos][state]};
    int End = limit ? a[pos] : 9;
    ll ans1 = 0, ans2 = 0;
    for (int i = 0; i <= End; i++) {
        if (lead&&i==0) {
                pii tmp = dfs(pos-1, state, limit && i == End, 1);
                ans1 = (ans1 + tmp.first) % mod;
                ans2 = (ans2 + i * base[pos] % mod * tmp.first % mod + tmp.second) % mod;
            }
            else {
                pii tmp = dfs(pos-1, state | (1<<i), limit && i == End, 0);
                ans1 = (ans1 + tmp.first) % mod;
                ans2 = (ans2 + i * base[pos] % mod * tmp.first % mod + tmp.second) % mod;
            }
    }
    if (!limit && !lead) dp[pos][state] = ans1, dp2[pos][state] = ans2;
    return {ans1, ans2};
}
int main() {
    base[0] = 1;
    for (int i = 1; i <= 10000; i++) base[i] = base[i-1] * 10 % mod;
    string s; cin >> s;
    int len = s.size();
    int m; cin >> m;
    for (int i = 1; i <= m; i++) {
        int x; cin >> x;
        st |= 1 << x;
    }

    for (int i = 0; i < s.size(); i++) a[i] = s[len-1-i]-'0';
    memset(dp, -1, sizeof dp);
    memset(dp2, -1, sizeof dp2);
    cout << dfs(len-1, 0, true, true).second << endl;
}

分析:

发现 i&j=0 也就是 每个位置都不全为1 也就是 i+j 不进位!!!!

所以我们只需要维护最高位是多少 在满足 i&j 的前提下 一个数的贡献就是 mx+1

但是这样会超时 想办法优化 不再考虑每个数的贡献 而是考虑每个最高位的贡献

这样我们只需要找到以这个为最高位的数的个数有num个 用num乘即可

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

template <class T> inline void read(T &x) {
    int f = 0; x = 0; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
    for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
}

const int N = 32;
const ll mod = 1e9 + 7;

int dp[N][2][2][2];
int tot1, tot2, dig1[N], dig2[N];
int x, y, ans;

void add(int &a, int b) {
    a += b;
    if (a >= mod) a -= mod;
}

int dfs(int pos, bool lim1, bool lim2, bool ok) {
    if (pos == -1) return ok;
    if (dp[pos][lim1][lim2][ok]) return dp[pos][lim1][lim2][ok];
    int to1 = lim1? dig1[pos] : 1;
    int to2 = lim2? dig2[pos] : 1;
    int tmp = 0;
    for (int i = 0; i <= to1; ++i) {
        for (int j = 0; j <= to2; ++j) {
            if (i && j) continue;
            int num = dfs(pos - 1, lim1 && i == to1, lim2 && j == to2, ok || i || j);
            add(tmp, num);
            if (!ok && (i || j)) add(ans, (ll)num * (ll)(pos + 1) % mod);
        }
    }
    return dp[pos][lim1][lim2][ok] = tmp;
}

void calc(int x, int y) {
    int len = 0;
    memset(dig1, 0, sizeof(dig1));
    memset(dig2, 0, sizeof(dig2));
    while (x) {
        dig1[len++] = x & 1;
        x >>= 1;
    }
    len = 0;
    while (y) {
        dig2[len++] = y & 1;
        y >>= 1;
    }
    dfs(30, true, true, false);
}

void solve() {
    ans = 0;
    memset(dp, 0, sizeof(dp));
    scanf("%d %d", &x, &y);
    calc(x, y);
    printf("%d\n", ans);
}

int main() {
    int t = 1;
    scanf("%d", &t);
    while (t--) solve();
    return 0;
}

http://codeforces.com/problemset/problem/55/D

题意:

Beautiful Numbers : 这个数能整除它的全部位上非零整数。问[l,r]之间的Beautiful Numbers的个数。

分析:

若一个数能整除它的全部的非零数位。那么相当于它能整除各个位数的最小公倍数。

因此记忆化搜索中的參数除了len(当前位)和up(是否达到上界),有一个prelcm表示前面的数的最小公倍数。

推断这个数是否是Beautiful Numbers,还要有一个參数表示前面数,可是这个数太大,须要缩小它的范围。

非常巧妙的一点:

缩小前面组成的数的范围。

能够发现全部个位数的最小公倍数是2520,如果当前的Beautiful Numbers是x,

那么 x % lcm{dig[i]} = 0,

又 2520%lcm{dig[i]} = 0,

那么x%2520%lcm{ dig[i] } = 0,x范围由9*10^18变为2520。

处理超内存问题。

经过分析后能够设出dp[20][2050][2050],dp[i][j][k]表示处理到i位,前面的数的最小公倍数为j。前面的数%2520为k。

但这样明显会MLE。。

由于1~9组成的最小公倍数仅仅有48个,能够离散化,这样数组就降到了dp[20][50][2520]。

#include <stdio.h>
#include <iostream>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <vector>
#include <math.h>
#include <string.h>
#include <queue>
#include <string>
#include <stdlib.h>
#include <algorithm>
//#define LL __int64
#define LL long long
#define eps 1e-12
#define PI acos(-1.0)
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 4010;
const int max_lcm = 2520;

LL gcd(LL a, LL b)
{
    if(b == 0)
        return a;
    return gcd(b,a%b);
}
LL lcm(LL a, LL b)
{
    return a/gcd(a,b)*b;
}
int dig[25];
LL dp[25][50][2525];
int ha[2525];

LL dfs(int len, int prelcm, int prenum, int up)
{
    if(len == 0)
    {
        return prenum%prelcm == 0;
    }
    if(!up && dp[len][ha[prelcm]][prenum] != -1)
        return dp[len][ha[prelcm]][prenum];
    int n = up ?
 dig[len] : 9;
    LL res = 0;
    for(int i = 0; i <= n; i++)
    {
        int nownum = (prenum*10+i)%max_lcm;
        int nowlcm = prelcm;
        if(i)
            nowlcm = lcm(prelcm,i);
        res += dfs(len-1,nowlcm,nownum,up&&i==n);
    }
    if(!up)
        dp[len][ha[prelcm]][prenum] = res;
    return res;
}

LL cal(LL num)
{
    int len = 0;
    while(num)
    {
        dig[++len] = num%10;
        num /= 10;
    }
    return dfs(len,1,0,1);
}

int main()
{
    int test;
    LL a,b;
    int cnt = 0;
    for(int i = 1; i <= 2520; i++) //离散化
    {
        if(max_lcm % i == 0)
            ha[i] = ++cnt;
    }

    scanf("%d",&test);
    memset(dp,-1,sizeof(dp));
    for(int item = 1; item <= test; item++)
    {
        scanf("%I64d %I64d",&a,&b);
        printf("%I64d\n",cal(b) - cal(a-1));
    }

    return 0;
}

https://codeforces.com/contest/914/problem/C

分析:

非常标准的一个数位dp题目 注意特殊条件 如果都是0 返回0

如果k=1 那就要-1 因为判断的时候是算上1了的 但是实际1的操作是0

如果k=0 那就要+1 判断的时候没有算上1

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e3+5;
const int mod=1e9+7;
string s;
int k,len;
ll pd[maxn];
ll dp[maxn][maxn][2];
void solve();
ll dfs(int pos,int lim,int sum){
	ll res=0;
	if(pos==len){
		if(!sum)return 0;
		return (pd[sum]==k-1);
	}
	if(dp[pos][sum][lim]!=-1)return dp[pos][sum][lim];
	int up=1;
	if(lim)up=s[pos]-'0';
	for(int i=0;i<=up;i++)
	res=(res+dfs(pos+1,lim&&(i==up),sum+(i==1)))%mod;
	return dp[pos][sum][lim]=res;
}
int main(){
	int T;T=1; 
	while(T--)solve();
     return 0;
}
void solve(){
	memset(dp,-1,sizeof(dp));
    cin>>s>>k;
    len=s.size();
    for(int i=1;i<=1001;i++){
    	int res=0,x=i;
    	while(x!=1){
    	res++;
		x=__builtin_popcount(x);
		}
		pd[i]=res;
	}
	ll ans=dfs(0,1,0);
	if(k==1)
    cout<<(ans-1ll+mod)%mod;
    else if(k==0)cout<<(ans+1ll)%mod;
    else cout<<ans;
}

posted @ 2022-01-19 18:02  wzx_believer  阅读(23)  评论(0编辑  收藏  举报