ACM之路(16)—— 数位DP
题目就是kuangbin的数位DP。
先讲C题,不要62,差不多就是一个模板题。要注意的是按位来的话,光一个pos是不够的,还需要一维来记录当前位置是什么数字,这样才能防止同一个pos不同数字的dp值混在一起。直接丢代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <vector>
#include <queue>
#include <math.h>
using namespace std;
typedef long long ll; // pos , 记录当前位是什么
// 第二维是为了防止同一个pos的不同which的dp混淆在一起,因为是记忆化搜索的
int bit[],dp[][]; int dfs(int pos,int which,bool have_six,bool flag)
{
if(pos == -) return ;
int& ans = dp[pos][which];
if(flag && ans!=-) return ans;
int d = flag?:bit[pos]; int ret = ;
for(int i=;i<=d;i++)
{
if(i==) continue;
if(have_six && i==) continue;
ret += dfs(pos-,i,i==,flag||i<d);
}
if(flag) ans = ret;
return ret;
} int solve(int x)
{
//if(x==0) return 0;
int pos = ;
while(x)
{
bit[pos++] = x % ;
x /= ;
} int ans = ;
ans += dfs(pos-,,false,false);
return ans;
// 因为0也是一个值
// 所以solve(5)=5是因为0.1.2.3.5
} int main()
{
int x,y;
memset(dp,-,sizeof(dp));
//printf("%d !!\n",solve(5));
while(scanf("%d%d",&x,&y)==)
{
if(x== && y==) break;
else printf("%d\n",solve(y)-solve(x-));
}
}
那么如果是求区间内还有62的呢?可以是在上面的基础上,用总个数减去;也可以再开一维have,表示是否拥有了62。这样变化一下就是D题了,丢代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <vector>
#include <queue>
#include <math.h>
using namespace std;
typedef long long ll; int bit[];
ll dp[][][]; /*ll dfs(int pos,bool state,bool flag)
{
if(pos == -1) return 1;
ll& ans = dp[pos][state];
if(flag && ans!=-1) return ans;
int d = flag?9:bit[pos]; ll ret = 0;
for(int i=0;i<=d;i++)
{
if(state && i==9) continue;
ret += dfs(pos-1,i==4,flag||i<d);
}
if(flag) ans = ret;
return ret;
}*/ ll dfs(int pos,bool state,bool have,bool flag)
{
if(pos == -) return have;
ll& ans = dp[pos][have][state];
if(flag && ans!=-) return ans;
int d = flag?:bit[pos]; ll ret = ;
for(int i=;i<=d;i++)
{
if(state && i==) ret += dfs(pos-,i==,,flag||i<d);
else ret += dfs(pos-,i==,have,flag||i<d);
}
if(flag) ans = ret;
return ret;
} ll solve(ll x)
{
//if(x==0) return 0;
int pos = ;
while(x)
{
bit[pos++] = x % ;
x /= ;
} ll ans = ;
ans += dfs(pos-,false,false,false);
return ans;
} int main()
{
int T;
scanf("%d",&T);
memset(dp,-,sizeof(dp)); while(T--)
{
ll x;
scanf("%I64d",&x);
//printf("%I64d\n",x-(solve(x)-1));
cout<<solve(x)<<endl;
}
}
另外还需要注意的是上面的问题并不需要记录当前一位是哪个数字,只要记录是不是需要的数字即可。比方说62,我只要用一个state保存这一位是不是6即可。
以上就是数位dp的基本套路,但是还会遇到一种情况,比方说让你统计区间内数的二进制表示的0的个数,这样子在dfs时需要再加一个参数first来表示有没有前导0。举个例子,比如说10010,在dfs以后,如果变成了0XXXX的情况,显然第一个0是不能算在0的个数之内的。因此,如果数位dp的过程中,有无前导0会对结果造成影响的,就要再加一个参数first来辅助完成dp过程。具体见E题代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std; int bit[];
int dp[][][];
//int len; /*
单纯的用len来记录总位数是不行的,因为比方说5是101,
比它小的数可能len不等于3
也就是说在递推的过程中len是会发生变化的 因此,换一个方法,用一个bool变量first记录之前位是否存在
*/ int dfs(int pos,int num_zero,int num_one,bool flag,bool first)
{
if(pos == -) return num_zero >= num_one;
int& ans = dp[pos][num_zero][num_one];
if(flag && ans!=-) return ans; int d = flag?:bit[pos];
int ret = ;
for(int i=;i<=d;i++)
{
ret += dfs(pos-,(first||i?num_zero+(i==):),(first||i?num_one+(i==):),flag||i<d,first||i);
}
if(flag) ans = ret;
return ret;
} int solve(int x)
{
int pos = ;
while(x)
{
bit[pos++] = x%;
x/=;
} //len = pos;
return dfs(pos-,,,false,false);
} int main()
{
int x,y;
memset(dp,-,sizeof(dp));
while(scanf("%d%d",&x,&y)==)
{
printf("%d\n",solve(y)-solve(x-));
}
return ;
}
然后几题是比较有意思的。
A题,问区间内的美丽数的个数,美丽数指的是:这个数能被各个位置上的数字整除。这题的方法是直接找他们的最小公倍数,然后当做状态的状态的一维即可。要注意的是如果最小公倍数开最大的话,会超内存,那么只要将这一系列的最小公倍数离散化即可。其实这题似乎还可以优化,我没有深究了。具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <vector>
#include <queue>
#include <math.h>
using namespace std;
typedef long long ll; int gcd(int a,int b) {return a%b?gcd(b,a%b):b;}
int lcm(int a,int b) {return a*b/gcd(a,b);}
// pos,离散化后的公约数的位置,各个位置上数字的和
int a[+];
int bit[];
ll dp[][][+]; ll dfs(int pos,int _lcm,int sum,bool flag)
{
if(pos == -) return (ll)(sum%_lcm==);
ll& ans = dp[pos][a[_lcm]][sum];
if(flag && ans!=-) return ans;
int d = flag?:bit[pos]; ll ret = ;
for(int i=;i<=d;i++)
{
ret += dfs(pos-,i?lcm(_lcm,i):_lcm,(sum*+i)%,flag||i<d);
}
if(flag) ans = ret;
return ret;
} ll solve(ll x)
{
int pos = ;
while(x)
{
bit[pos++] = x % ;
x /= ;
} ll ans = ;
ans += dfs(pos-,,,false);
return ans;
} int main()
{
// 离散化
for(int i=,j=;i<=;i++)
{
a[i] = %i?:(++j);
//printf("%d !!\n",j );
} // max = 48 int T;
scanf("%d",&T);
memset(dp,-,sizeof(dp)); while(T--)
{
ll x,y;
scanf("%I64d%I64d",&x,&y);
printf("%I64d\n",solve(y)-solve(x-));
}
}
B题,问区间内满足以下条件的数,这个数字内的LIS等于K。那么只要模拟LIS用二进制储存一个状态码当做一维的内容即可。具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <stack>
using namespace std;
const int inf = 0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;
const int N = + ; int bit[],K;
ll dp[][<<][];
ll L,R; /*
state是一个状态码,
二进制状态下,各位如果是0表示LIS中没有这个数,
否则,就有。
例如100110,那么表示当前这个数的LIS为125
如果我要插入一个数字是3,那么3将5替换掉就可以了,
否则,如果插入的是一个6,比5都大,只要将6放在5后面即可。
下面的代码就是实现的这一过程。
*/
int newState(int state,int x)
{
// 找到第一个大于x的数将它替换掉
// 是对LIS的nlog(n)的体现
for(int i=x;i<;i++)
{
if(state & (<<i)) return (state^(<<i))|(<<x);
}
return state | (<<x);
} int getLen(int state)
{
int cnt = ;
while(state)
{
if(state&) cnt++;
state >>= ;
}
return cnt;
} ll dfs(int pos,int state,bool first,bool flag)
{
if(pos == -) return getLen(state)==K;
ll& ans = dp[pos][state][K];
if(flag && ans!=-) return ans; int d = flag?:bit[pos];
ll ret = ;
for(int i=;i<=d;i++)
{
ret += dfs(pos-,first||i?newState(state,i):,first||i,flag||i<d);
}
if(flag) ans = ret;
return ret;
} ll solve(ll x)
{
int pos = ;
while(x)
{
bit[pos++] = x % ;
x /= ;
} return dfs(pos-,,false,false);
} int main()
{
int T;
scanf("%d",&T);
memset(dp,-,sizeof(dp));
for(int kase=;kase<=T;kase++)
{
scanf("%I64d%I64d%d",&L,&R,&K);
printf("Case #%d: %I64d\n",kase,solve(R)-solve(L-));
}
}
讲到状态码形式的数位dp,可以再看看最后一题。大意是找出区间内平衡数:任何奇数只要出现了,就必须出现偶数次;任何偶数,只要出现了就必须出现奇数次。不出现的数字不做讨论,同时是任意一个奇数都要出现偶数次,比方说1333,1和3它们都出现了奇数次,而不是所有奇数出现的次数和是偶数次,因此这个数字不满足平衡数的要求。具体实现的话也是转化成3进制的状态码即可,具体见代码:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <stack>
using namespace std;
const int inf = 0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;
const int N = + ; int bit[];
ll dp[][];
int pw[];
int f[][];
// f表示的是在这个状态码下各个数字的出现次数
// 是预处理好的
// 比如f[12345][6]表示在12345这个状态码下的6的出现的次数的奇偶 bool check(int state)
{
int x = ; // 1表示出现奇数次,2表示出现偶数次
for(int i=;i>=;i--)
{
if(f[state][i])
{
if(f[state][i] + x != ) return false;
}
x = - x;
}
return true;
} int newstate(int state,int i)
{
if(f[state][i] <= )
{
//f[state][i]++;
return state + pw[i]; // 如果出现了0次或者奇数次,就次数加1,相当于那个数字的那一位的三进制加1
}
//f[state][i]--;
return state - pw[i]; // 如果出现了偶数次,让状态码对应位置的三进制减1,表示变成了出现奇数次
} ll dfs(int pos,int state,bool first,bool flag)
{
if(pos == -) return check(state);
ll& ans = dp[pos][state];
if(flag && ans!=-) return ans; int d = flag?:bit[pos];
ll ret = ;
for(int i=;i<=d;i++)
{
ret += dfs(pos-,first||i?newstate(state,i):,first||i,flag||i<d);
}
if(flag) ans = ret;
return ret;
} ll solve(ll x)
{
int pos = ;
while(x)
{
bit[pos++] = x % ;
x /= ;
} return dfs(pos-,,false,false);
} void init()
{
memset(dp,-,sizeof(dp));
memset(f,,sizeof(f));
pw[] = ;
for(int i=;i<=;i++) pw[i] = *pw[i-]; // 下面是预处理出f的值
for(int i=;i<=pw[]-;i++)
{
int now = i;
for(int j=;j>=;j--)
{
if(now >= pw[j])
{
int t;
f[i][j] = t = now/pw[j];
now -= t*pw[j];
}
}
}
} int main()
{
int T;
scanf("%d",&T);
init();
while(T--)
{
ll l,r;
cin >> l >> r;
cout << solve(r)-solve(l-) << endl;
}
}
最后想讲的是J题,求的是区间内的与7无关的数字的平方和。因为涉及到平方,所以涉及到展开的问题。具体见代码注释:
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <stack>
using namespace std;
const int inf = 0x3f3f3f3f;
const int mod = (int)1e9 + ;
typedef long long ll;
typedef pair<int,int> pii;
const int N = + ; struct node
{
ll n,s,sq;
}dp[][][];
int bit[];
ll L,R,pw[]; node dfs(int pos,int sum,int digit_sum,bool flag)
{
if(pos == -) return (node){sum&&digit_sum,,};
node& ans = dp[pos][sum][digit_sum];
if(flag && ans.n!=-) return ans; int d = flag?:bit[pos];
node ret = (node){,,};
for(int i=;i<=d;i++)
{
if(i == ) continue;
node temp = dfs(pos-,(sum*+i)%,(digit_sum+i)%,flag||i<d);
ret.n = (ret.n + temp.n) % mod; // 别忘了乘以个数n
ret.s += (temp.s + (pw[pos] * i) % mod * temp.n) % mod;
ret.s %= mod; // 到这一位需要增加的sq是(i*pw[i]+temp.s)^2,拆开累加即可
// temp.sq 是 temp.s 的平方
// 在处理(i*pw[i])的平方时需要乘以个数n。
// 而处理2倍它们的乘积时不用是因为temp.s中已经乘过n了
ret.sq += (temp.sq + * pw[pos] * i % mod * temp.s % mod) % mod;
ret.sq %= mod;
ret.sq += (temp.n * pw[pos] % mod * ((pw[pos]*i*i) % mod)) % mod;
ret.sq %= mod;
}
if(flag) ans = ret;
return ret;
} ll solve(ll x)
{
int pos = ;
while(x)
{
bit[pos++] = x % ;
x /= ;
} return dfs(pos-,,,false).sq % mod;
} int main()
{
int T;
scanf("%d",&T);
memset(dp,-,sizeof(dp));
pw[] = ;
for(int i=;i<;i++) pw[i] = (pw[i-] * ) % mod;
while(T--)
{
scanf("%I64d%I64d",&L,&R);
// 对mod以后的别忘记先加mod再mod不然可能是负数
printf("%I64d\n",(solve(R)-solve(L-) + mod) % mod);
}
}
最后想补充的一点是,很多的solve求的都是0~指定位置满足条件的和,但是没有关系,只要相减以后就都抵消了,但是单单使用solve的话就可能会出现问题。
ACM之路(16)—— 数位DP的更多相关文章
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP)
2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位DP) 链接:https://ac.nowcoder.com/acm/contest/163/ ...
- 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 J Beautiful Numbers (数位dp)
题目链接:https://ac.nowcoder.com/acm/contest/163/J 题目大意:给定一个数N,求区间[1,N]中满足可以整除它各个数位之和的数的个数.(1 ≤ N ≤ 1012 ...
- 哈尔滨工程大学ACM预热赛 G题 A hard problem(数位dp)
链接:https://ac.nowcoder.com/acm/contest/554/G Now we have a function f(x): int f ( int x ) { if ( ...
- 2018牛客网暑假ACM多校训练赛(第四场)C Chiaki Sequence Reloaded (组合+计数) 或 数位dp
原文链接https://www.cnblogs.com/zhouzhendong/p/NowCoder-2018-Summer-Round4-C.html 题目传送门 - https://www.no ...
- 【ACM】不要62 (数位DP)
题目:http://acm.acmcoder.com/showproblem.php?pid=2089 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer).杭州交通管理局经常会扩充一些的士车牌照,新 ...
- ACM学习历程—HDU5587 Array(数学 && 二分 && 记忆化 || 数位DP)(BestCoder Round #64 (div.2) 1003)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5587 题目大意就是初始有一个1,然后每次操作都是先在序列后面添加一个0,然后把原序列添加到0后面,然后 ...
- 【好好补题,因为没准题目还会再出第三遍!!】ACM字符串-组合数学(官方题解是数位DP来写)
ACM字符串 .长度不能超过n .字符串中仅包含大写字母 .生成的字符串必须包含字符串“ACM”,ACM字符串要求连在一块! ok,是不是很简单?现在告诉你n的值,你来告诉我这样的字符串有多少个 输入 ...
- 2019长安大学ACM校赛网络同步赛 L XOR (规律,数位DP)
链接:https://ac.nowcoder.com/acm/contest/897/L 来源:牛客网 XOR 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32768K,其他语言6 ...
- HDU(4734),数位DP
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=4734 F(x) Time Limit: 1000/500 MS (Java/Others) ...
随机推荐
- DateTime.TryParse 日期时间字符串验证
DateTime applicationDatetime = new DateTime(); bool applicationDate = DateTime.TryParse("2019-0 ...
- VBA学习资料分享-2
想利用VBA自动创建/发送OUTLOOK邮件,可以借助MailItem的Body属性或HTMLBody属性,代码模板如下: Dim objOutlook As Outlook.Application ...
- TypeScript入门二:基本数据类型
浅析基本数据类型 TypeScript类型解析 一.浅析基本数据类型 首先有一个问题TypeScript是一门编译型语言?还是解释性语言?显然已经不能被这两个分类来区分,TypeScript的并不是为 ...
- oracle的listagg函数
今天需要将 BDST_ID相同的PROJECT_ID用逗号分隔拼成一个字符串,于是想到了oracle的listagg函数 表名为PM_BDST_PROJECT select tt.BDST_ID, l ...
- Dubbo 配置参数
关闭启动检查 在dubbo多模块项目启动的时候为了并行启动多个服务,缩短启动时间,需要解除模块之间的依赖检测 dubbo.consumer.check=false @Reference(check = ...
- 【Struts2】拦截器
一.概述 二.在Struts2中使用拦截器 2.1 步骤 2.2 分析拦截器原理 2.3 关于interceptor与Filter区别: 三.案例 一.概述 介绍拦截器: struts2拦截器使用的是 ...
- 在cmd下import cv2报错——OpenCV实现BRISK
平台:win10 x64 +JetBrains PyCharm 2018.2.4 x64 +Anaconda3(python3.7.0+opencv3.4.5) Issue说明:同学发了个python ...
- 如何将公式插入到word
平台:win10 x64+ office 2010+ Mathpix Snipping Tool +mathtype6.9b 直接安装就行,下载好了以后,要和word连接起来还需要下载一个插件,有 ...
- Mysql(五):索引原理与慢查询优化
一 介绍 为何要有索引? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句 ...
- MPU6050应用
@2019-08-07 [小记] MPU6050开发 -- 基本概念简介 MPU6050原理详解及实例应用 详解卡尔曼滤波原理 卡尔曼算法精讲与C++实现