数位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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)