目录
- 1. CodeForces-55D Beautiful numbers
- 2. hdu 4352 XHXJ's LIS
- 3. hdu 2089 不要62
- 4. hdu 3555 Bomb
- 5. poj 3252 Round Numbers
- 6. hdu 3709 Balanced Number
- 7. hdu 3652 B-number
- 8. hdu 4734 F(x)
- 10. hdu 4507 吉哥系列故事――恨7不成妻
- 11. SPOJ-BALNUM Balanced Numbers
1. CodeForces-55D Beautiful numbers
- 此题非常经典,思路应该记住,问区间范围内能够整除它自己每一位的数有多少个
- 首先我们知道如果一个数能整除某些数,那么它一定能够整除这些数的最小公倍数,然后注意到 1 − 9 1-9 1−9任意两个数的最小公倍数最多 48 48 48个,最大 2520 2520 2520,所以考虑一个三维的数位 d p dp dp
- 设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为当前位置为 p p p,前面位的最小公倍数乘积除以 2520 2520 2520余数为 j j j,前面位的最小公倍数为 k k k,其中 k k k需离散化处理,细节见代码
#include
using namespace std;
typedef long long ll;
int digit[30];
ll GCD(ll a, ll b){
return b == 0 ? a : GCD(b, a % b);
}
ll LCM(ll a, ll b){
return a / GCD(a, b) * b;
}
ll dp[30][2525][50];
int has[2525];
ll Dfs(int p, int prelcm, int rem, bool limit){
assert(prelcm <= 2520 && rem < 2520);
if(p < 0) return rem % prelcm == 0;
if(!limit && dp[p][rem][has[prelcm]] != -1) return dp[p][rem][has[prelcm]];
ll ans = 0;
int up = (limit ? digit[p] : 9);
for(int i=0;i<=up;i++){
int now = prelcm;
if(i > 0) now = LCM(i, now);
ans += Dfs(p - 1, now, (rem * 10 + i) % 2520, limit && i == up);
}
if(!limit) dp[p][rem][has[prelcm]] = ans;
return ans;
}
ll solve(ll n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 1, 0, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
int cnt = 0;
memset(dp, -1, sizeof dp);
for(int i=1;i<=2520;i++){
if(2520 % i == 0){
has[i] = ++cnt;
}
}
while(t--){
ll l, r;
cin >> l >> r;
cout << solve(r) - solve(l - 1) << '\n';
}
return 0;
}
2. hdu 4352 XHXJ’s LIS
- 将数看成字符串,让你找 L I S LIS LIS长度为 k k k的数有多少个
- 核心思想是使用一个二进制数来记录当前的 L I S LIS LIS状态,这里和 L I S LIS LIS的更新思想是一样的,同时注意前导 0 0 0等细节
- 设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示当前是第 i i i位, L I S LIS LIS状态为 j j j,长度为 k k k的数字个数
#include
using namespace std;
typedef long long ll;
int digit[30];
int k;
ll bit(int state){
int cnt = 0;
while(state > 0){
state -= (state & -state);
cnt += 1;
}
return cnt;
}
ll dp[30][1 << 10][12];
int upd(int x, int s){
for(int i=x;i<10;i++){
if(s & (1 << i)){
return (s ^ (1 << i)) | (1 << x);
}
}
return s | (1 << x);
}
ll Dfs(int p, int state, int lead, bool limit){
if(p < 0) return bit(state) == k;
if(!limit && dp[p][state][k] != -1) return dp[p][state][k];
int up = (limit ? digit[p] : 9);
ll ans = 0;
for(int i=0;i<=up;i++){
ans += Dfs(p - 1, (lead && i == 0) ? 0 : upd(i, state), lead && i == 0, limit && i == up);
}
if(!limit) dp[p][state][k] = ans;
return ans;
}
ll solve(ll n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 0, 1, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
memset(dp, -1, sizeof dp);
for(int kase=1;kase<=t;kase++){
ll l, r;
cin >> l >> r >> k;
cout << "Case #" << kase << ": " << solve(r) - solve(l - 1) << '\n';
}
return 0;
}
3. hdu 2089 不要62
- 排除掉4和62,设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前为第 i i i位,前一位是 j j j的方案数
- 按照常规记忆化搜索进行数位 d p dp dp
#include
using namespace std;
typedef long long ll;
int digit[20];
int dp[30][30];
ll Dfs(int p, int pre, int limit){
if(p < 0){
return 1ll;
}
if(!limit && dp[p][pre] != -1) return dp[p][pre];
int up = (limit ? digit[p] : 9);
ll ans = 0;
for(int i=0;i<=up;i++){
if(i == 2 && pre == 6) continue;
if(i == 4) continue;
ans += Dfs(p - 1, i, limit && i == up);
}
return ans;
}
ll solve(ll n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 0, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll n, m;
memset(dp, -1, sizeof dp);
while(cin >> n >> m && n + m > 0){
cout << solve(m) - solve(n - 1) << '\n';
}
return 0;
}
4. hdu 3555 Bomb
- 求 [ 1 , n ] [1,n] [1,n]中不含49的数的个数
- 设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前为第 i i i位,前一个是 j j j的方案数,是上一题的简单版本
#include
using namespace std;
typedef long long ll;
ll dp[20][10];
int digit[20];
ll Dfs(ll p, ll pre, bool limit){
if(p < 0) return 1ll;
if(!limit && dp[p][pre] != -1) return dp[p][pre];
int up = (limit ? digit[p] : 9);
ll ans = 0;
for(int i=0;i<=up;i++){
if(i == 9 && pre == 4) continue;
ans += Dfs(p - 1, i, limit && i == up);
}
if(!limit) dp[p][pre] = ans;
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
memset(dp, -1, sizeof dp);
cin >> t;
while(t--){
ll n;
cin >> n;
ll tmp = n;
int p = 0;
while(tmp > 0){
digit[p++] = tmp % 10;
tmp /= 10;
}
cout << n + 1 - Dfs(p - 1, 0, true) << '\n';
}
return 0;
}
5. poj 3252 Round Numbers
- 问你范围内有多少个数二进制表示中 0 0 0的个数不少于 1 1 1的个数
- 设 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示当前位置为 i i i,当前有 j j j个 0 0 0, k k k个 1 1 1
- 将数字转换为二进制处理
- 此题需要注意前导 0 0 0的问题
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
int digit[70];
ll dp[70][70][70];
ll Dfs(int p, int one, int zero, bool lead, bool limit){
if(p < 0){
return zero >= one;
}
if(!limit && dp[p][one][zero] != -1) return dp[p][one][zero];
ll ans = 0;
int up = (limit ? digit[p] : 1);
for(int i=0;i<=up;i++){
if(i > 0){
ans += Dfs(p - 1, one + 1, zero, i, limit && i == up);
}else{
ans += Dfs(p - 1, one, zero + (lead ? 1 : 0), lead, limit && i == up);
}
}
if(!limit) dp[p][one][zero] = ans;
return ans;
}
ll solve(ll n){
int p = 0;
while(n > 0){
digit[p++] = (n & 1);
n >>= 1;
}
return Dfs(p - 1, 0, 0, 0, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll l, r;
memset(dp, -1, sizeof dp);
cin >> l >> r;
cout << solve(r) - solve(l - 1) << '\n';
return 0;
}
6. hdu 3709 Balanced Number
- 注意到每个数最多只能有一个这样的中心点,考虑枚举每一个中心点,然后利用左右两侧点缀和互为相反数,可得 d p dp dp设法
- 但这样考虑忽略了前导0可能带来的问题,实际上对于 00012323 00012323 00012323这样的是不影响的,但是对于 000000 000000 000000这样的实际上是不合法的,所以需要把这些全是 0 0 0的减掉,只保留一个 0 0 0
#include
using namespace std;
typedef long long ll;
ll dp[30][30][18 * 18 * 10];
int digit[30];
ll Dfs(int p, int center, int pre, bool limit){
if(p < 0){
return pre == 0;
}
if(pre < 0) return 0;
if(!limit && dp[p][center][pre] != -1) return dp[p][center][pre];
int up = (limit ? digit[p] : 9);
ll ans = 0;
for(int i=0;i<=up;i++){
ans += Dfs(p - 1, center, pre + (p - center) * i, limit && i == up);
}
if(!limit) dp[p][center][pre] = ans;
return ans;
}
ll solve(ll n){
if(n < 0) return 0ll;
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
ll ans = 0;
for(int i=0;i<p;i++){
ans += Dfs(p - 1, i, 0, true);
}
return ans - p + 1;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
memset(dp, -1, sizeof dp);
while(t--){
ll l, r;
cin >> l >> r;
cout << solve(r) - solve(l - 1) << '\n';
}
return 0;
}
7. hdu 3652 B-number
- 问数字中有 13 13 13且能够被 13 13 13整除的数字的个数,一定要注意 d p dp dp方程的设法,我一开始想的是记录前一个数字,但是这样的状态转移是错误的,因为只考虑前一个数字是有后效性的,换而使用 0 , 1 , 2 0,1,2 0,1,2三个状态记录当前字符串的状况,这样转移才是对的
#include
using namespace std;
typedef long long ll;
int digit[35];
int dp[35][3][13];
int Dfs(int p, int pre, int md, bool limit){
if(p < 0){
return pre == 2 && md == 0;
}
if(!limit && dp[p][pre][md] != -1) return dp[p][pre][md];
int up = (limit ? digit[p] : 9);
int ans = 0;
for(int i=0;i<=up;i++){
int tmp = pre;
if(pre == 0 && i == 1) tmp = 1;
else if(pre == 1){
if(i == 3) tmp = 2;
else if(i == 1) tmp = 1;
else{
tmp = 0;
}
}
ans += Dfs(p - 1, tmp, (md * 10 + i) % 13, limit && i == up);
}
if(!limit) dp[p][pre][md] = ans;
return ans;
}
int solve(int n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 0, false, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
memset(dp, -1, sizeof dp);
while(cin >> n){
cout << solve(n) << '\n';
}
return 0;
}
8. hdu 4734 F(x)
- 让你找权重不大于 F ( A ) F(A) F(A)的数字的个数
- 如果设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前为第 i i i位,权重为 j j j的方案数,应对单组数据还行,多测无法清空
- 考虑一种 d p dp dp方式,能够让它的每一个状态能够应对所有数字而不是单一范围
- 设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前位是 i i i, j = s u m a − s u m j=sum_a-sum j=suma−sum, s u m sum sum表示当前前缀的权重,这样是一般化的 d p dp dp方程,对于所有满足情况的方案数都是唯一的
#include
using namespace std;
typedef long long ll;
int digit[35];
int dp[35][42000];
int now_a;
int Dfs(int p, int now, bool limit){
if(now_a < now) return 0;
if(p < 0) return now <= now_a;
assert(now_a - now < 42000);
if(!limit && dp[p][now_a - now] != -1) return dp[p][now_a - now];
int up = (limit ? digit[p] : 9);
int ans = 0;
for(int i=0;i<=up;i++){
ans += Dfs(p - 1, now + (1 << p) * i, limit && i == up);
}
if(!limit) dp[p][now_a - now] = ans;
return ans;
}
int solve(int n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 0, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
memset(dp, -1, sizeof dp);
for(int kase=1;kase<=t;kase++){
int a, b;
cin >> a >> b;
now_a = 0;
int k = 1;
while(a > 0){
now_a += k * (a % 10);
a /= 10;
k <<= 1;
}
cout << "Case #" << kase << ": " << solve(b) << '\n';
}
return 0;
}
10. hdu 4507 吉哥系列故事――恨7不成妻
- 求平方和的数的个数,数位 d p dp dp关键在于加上一位之后的影响,我们要记录之前枚举过了的位的和,以及平方和,再根据满足条件的数的个数来计算加上这一位对于答案的影响
#include
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int N = 20;
int digit[N];
ll pow10[N];
struct st{
ll cnt;
ll sum;
ll sum2;
st(){}
st(ll cnt, ll sum, ll sum2){
this->cnt = cnt;
this->sum = sum;
this->sum2 = sum2;
}
}dp[N][N][N];
st Dfs(int p, int md, int tot, bool limit){
if(p < 0){
return st(md != 0 && tot != 0, 0, 0);
}
if(!limit && dp[p][md][tot].cnt != -1){
return dp[p][md][tot];
}
int up = (limit ? digit[p] : 9);
st ans(0, 0, 0);
for(int i=0;i<=up;i++){
if(i == 7) continue;
auto tmp = Dfs(p - 1, (i + md * 10) % 7, (i + tot) % 7, i == up && limit);
ans.cnt += tmp.cnt;
ans.sum += (tmp.sum + 1ll * i * pow10[p] % MOD * tmp.cnt % MOD) % MOD;
ans.sum2 += ((tmp.sum2 + 2ll * i * pow10[p] % MOD * tmp.sum % MOD) % MOD + 1ll * i * tmp.cnt % MOD * pow10[p] % MOD * i % MOD * pow10[p] % MOD) % MOD;
ans.cnt %= MOD;
ans.sum %= MOD;
ans.sum2 %= MOD;
}
if(!limit) dp[p][md][tot] = ans;
return ans;
}
ll solve(ll n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 0, 0, true).sum2;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
pow10[0] = 1ll;
for(int i=1;i<=18;i++){
pow10[i] = pow10[i - 1] * 10 % MOD;
}
memset(dp, -1, sizeof dp);
cin >> t;
while(t--){
ll l, r;
cin >> l >> r;
cout << ((solve(r) - solve(l - 1)) % MOD + MOD) % MOD << '\n';
}
return 0;
}
11. SPOJ-BALNUM Balanced Numbers
- 问你有多少个数的每个数位奇数出现偶数次,偶数出现奇数次
- 关键点在于如何去记录这个信息,实际上可以使用一个三进制数,很巧妙,如果某一位是 1 1 1说明这位出现了奇数次, 2 2 2说明出现偶数次, 0 0 0说明没出现过
- 然后我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示当前位为 i i i,前面的三进制数的状态为 j j j,因为 3 10 < 60000 3^{10}\lt60000 310<60000,所以完全开的下
#include
using namespace std;
typedef long long ll;
int digit[20];
ll dp[20][60000];
bool ck(int st){
int p = 0;
while(st > 0){
if(st % 3 == 1 && p % 2 == 1){
return false;
}
if(st % 3 == 2 && p % 2 == 0){
return false;
}
p += 1;
st /= 3;
}
return true;
}
int modify(int st, int pos){
int p = 0;
int ans = 1;
int x = st;
while(x > 0){
if(pos == p){
break;
}
x /= 3;
p += 1;
ans *= 3;
}
while(pos != p){
ans *= 3;
p += 1;
}
if(x % 3 <= 1) st += ans;
else st -= ans;
return st;
}
ll Dfs(int p, int st, bool lead, bool limit){
if(p < 0){
return ck(st);
}
if(!limit && dp[p][st] != -1) return dp[p][st];
int up = (limit ? digit[p] : 9);
ll ans = 0;
for(int i=0;i<=up;i++){
ans += Dfs(p - 1, (lead && i == 0 ? st : modify(st, i)), lead && i == 0, limit && i == up);
}
if(!limit) dp[p][st] = ans;
return ans;
}
ll solve(__int128_t n){
int p = 0;
while(n > 0){
digit[p++] = n % 10;
n /= 10;
}
return Dfs(p - 1, 0, true, true);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
memset(dp, -1, sizeof dp);
while(t--){
__int128_t a = 0, b = 0;
string s;
cin >> s;
int len = s.length();
for(int i=0;i<len;i++){
a *= 10;
a += s[i] - '0';
}
cin >> s;
len = s.length();
for(int i=0;i<len;i++){
b *= 10;
b += s[i] - '0';
}
cout << solve(b) - solve(a - 1) << '\n';
}
return 0;
}