小技巧1:求区间[X, Y]可以转换为求F(Y) - F(X-1)
F(X)表示0~X中满足条件的数字个数
小技巧2:可以用树的形式来看
遍历最高位,每一位分为两种情况:未达到上界和达到上界
如果走到右边最底端需加1
度的数量
求给定区间 [X,Y]中满足下列条件的整数个数:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。
例如,设 X=15,Y=20,K=2,B=2,则有且仅有下列三个数满足题意:
17=2^4+2^0
18=2^4+2^1
20=2^4+2^2
输入格式
第一行包含两个整数 X 和 Y,接下来两行包含整数 K 和 B。
输出格式
只包含一个整数,表示满足条件的数的个数。
数据范围
1≤X≤Y≤2^31−1,
1≤K≤20,
2≤B≤10
输入样例:
15 20
2
2
输出样例:
3
B进制上放K个1,在[X, Y]范围内的数量
左节点可放0或1
右节点只能放0或1,大于1时break
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 35;
int l, r, K, B;
int f[N][N];
void init()
{
f[0][0] = 1;
for(int i = 1; i < N; i ++)
{
for(int j = 0; j <= i; j ++)
{
if(!j)f[i][j] = 1;
else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
}
int C(int a, int b)
{
if(a < b || b < 0)return 0;
return f[a][b];
}
int dp(int n)
{
if(!n)return 0;
vector<int> nums;
while(n)
{
nums.push_back(n % B);
n /= B;
}
int res = 0, last = 0;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
if(x)//x>0才能走左分支
{
res += C(i, K - last);
if(x > 1)
{
res += C(i, K - last - 1);
break;
}
else
{
last ++;
if(last > K)break; //等于K时不能break,因为下一个还可以算左分支,或者如果一直放0也会走到右分支底端
}
}
//只有走到底端(没有break出去)才能加1,不能放到循环外
if(!i && last == K)res ++;//走到最后一步可能是1也可能是0
}
return res;
}
int main()
{
IOS
init();
cin >> l >> r >> K >> B;
cout << dp(r) - dp(l - 1);
return 0;
}
数字游戏
科协里最近很流行数字游戏。
某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。
现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
注意:不降数不能包含前导零。
输入格式
输入包含多组测试数据。
每组数据占一行,包含两个整数 a 和 b。
输出格式
每行给出一组测试数据的答案,即 [a,b] 之间有多少不降数。
数据范围
1≤a≤b≤2^31−1
输入样例:
1 9
1 19
输出样例:
9
18
思路还是和模板差不多,关键点是找固定x时如何找不下降数的个数
可以用动态规划来找:
f[i][j]表示区间长度为i,第一个数字为j的不下降数个数
f[i][j] = f[i-1][k]
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 15;
int f[N][N];
void init()
{
for(int i = 0; i <= 9; i ++)f[1][i] = 1;
for(int i = 2; i < N; i ++)
{
for(int j = 0; j <= 9; j ++)
{
for(int k = j; k <= 9; k ++)
{
f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int n)
{
if(!n)return 1;
vector<int> nums;
while(n)
{
nums.push_back(n % 10);
n /= 10;
}
int res = 0, last = 0;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
for(int j = last; j < x; j ++)
{
res += f[i + 1][j];//关键点是这一步
}
if(x < last)break;
last = x;
if(!i)res ++;
}
return res;
}
int main()
{
IOS
init();
int l, r;
while(cin >> l >> r)
{
cout << dp(r) - dp(l - 1) << endl;
}
return 0;
}
Windy数
Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy 数。
Windy 想知道,在 A 和 B 之间,包括 A 和 B,总共有多少个 Windy 数?
输入格式
共一行,包含两个整数 A 和 B。
输出格式
输出一个整数,表示答案。
数据范围
1≤A≤B≤2×1e9
输入样例1:
1 10
输出样例1:
9
输入样例2:
25 50
输出样例2:
20
和上一题差不多
额外知识点为如何处理前导零,两点:
1.最高的那一位从1开始而不是从0开始
2.再枚举位数 1~总位数-1 加上f[i][1~9]
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 15;
ll f[N][10];
void init()
{
for(int i = 0; i <= 9; i ++)f[1][i] = 1;
for(int i = 2; i < N; i ++)
{
for(int j = 0; j <= 9; j ++)
{
for(int k = 0; k <= 9; k ++)
{
if(abs(j - k) >= 2)
{
f[i][j] += f[i - 1][k];
}
}
}
}
}
int dp(int n)
{
if(!n)return 0;
vector<int> nums;
while(n)
{
nums.push_back(n % 10);
n /= 10;
}
int res = 0, last = -9;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
for(int j = (i == nums.size() - 1); j < x; j ++)
{
if(abs(j - last) < 2)continue;
res += f[i + 1][j];
}
if(abs(x - last) < 2)break;
last = x;
if(!i)res ++;
}
// 特殊处理有前导零的数
//0也被当成前导零了,所以0是否算入需人为规定,如果算入则return 1 和res = 1,不算入就return 0和res = 0
for(int i = nums.size() - 1; i >= 1; i --)
{
for(int j = 1; j <= 9; j ++)
{
res += f[i][j];
}
}
return res;
}
int main()
{
IOS
init();
int x, y;
cin >> x >> y;
cout << dp(y) - dp(x - 1);
return 0;
}
数字游戏 II
由于科协里最近真的很流行数字游戏。
某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N 为 0。
现在大家又要玩游戏了,指定一个整数闭区间 [a.b],问这个区间内有多少个取模数。
输入格式
输入包含多组测试数据,每组数据占一行。
每组数据包含三个整数 a,b,N。
输出格式
对于每个测试数据输出一行结果,表示区间内各位数字和 mod N 为 0 的数的个数。
数据范围
1≤a,b≤2^31−1,
1≤N<100
输入样例:
1 19 9
输出样例:
2
f[i][j][k]表示i位,首位为j,模数为k
其他一样,这题不处理前导零也没影响,但以防万一处理一下也没毛病(不处理时n=0时返回1)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 15;
ll f[N][10][110];//i位 首位为j 模数为k
int mod;
void init()
{
for(int i = 0; i <= 9; i ++)f[1][i][i % mod] ++;
for(int i = 2; i < N; i ++)
{
for(int j = 0; j <= 9; j ++)
{
for(int k = 0; k < mod; k ++)
{
for(int u = 0; u <= 9; u ++)
{
f[i][j][(j + k) % mod] += f[i - 1][u][k];
}
}
}
}
}
int dp(int n)
{
if(!n)return 1;
vector<int> nums;
while(n)
{
nums.push_back(n % 10);
n /= 10;
}
int res = 0, last = 0;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
for(int j = 0; j < x; j ++)
{
res += f[i + 1][j][(mod - last) % mod];
}
last = (last + x) % mod;
if(!i && !last)res ++;
}
return res;
}
int main()
{
IOS
int x, y;
while(cin >> x >> y >> mod)
{
memset(f, 0, sizeof f);
init();
cout << dp(y) - dp(x - 1) << endl;
}
return 0;
}
不要62
杭州人称那些傻乎乎粘嗒嗒的人为 62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有 4 或 62 的号码。例如:62315,73418,88914 都属于不吉利号码。但是,61152 虽然含有 6 和 2,但不是 连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照号区间 [n,m],推断出交管局今后又要实际上给多少辆新的士车上牌照了。
输入格式
输入包含多组测试数据,每组数据占一行。
每组数据包含一个整数对 n 和 m。
当输入一行为“0 0”时,表示输入结束。
输出格式
对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
数据范围
1≤n≤m≤1e9
输入样例:
1 100
0 0
输出样例:
80
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 15;
ll f[N][10];
void init()
{
for(int i = 0; i <= 9; i ++)f[1][i] = 1;
f[1][4] = 0;
for(int i = 2; i < N; i ++)
{
for(int j = 0; j <= 9; j ++)
{
if(j == 4)continue;
if(j == 6)
{
for(int k = 0; k <= 9; k ++)
{
if(k == 2 || k == 4)continue;
f[i][j] += f[i - 1][k];
}
continue;
}
for(int k = 0; k <= 9; k ++)
{
if(k == 4)continue;
f[i][j] += f[i - 1][k];
}
}
}
}
int dp(int n)
{
if(!n)return 1;
vector<int> nums;
while(n)
{
nums.push_back(n % 10);
n /= 10;
}
int res = 0, last = 0;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
for(int j = 0; j < x; j ++)
{
if(j == 4 || last == 6 && j == 2)continue;
res += f[i + 1][j];
}
if(last == 6 && x == 2)break;
if(x == 4)break;
last = x;
if(!i)res ++;
}
return res;
}
int main()
{
IOS
init();
int x, y;
while(cin >> x >> y, x)
{
cout << dp(y) - dp(x - 1) << endl;
}
return 0;
}
恨7不成妻
单身!
依然单身!
吉哥依然单身!
DS 级码农吉哥依然单身!
所以,他平生最恨情人节,不管是 214 还是 77,他都讨厌!
吉哥观察了 214 和 77 这两个数,发现:
2+1+4=7
7+7=7×2
77=7×11
最终,他发现原来这一切归根到底都是因为和 7 有关!
所以,他现在甚至讨厌一切和 7 有关的数!
什么样的数和 7 有关呢?
如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:
- 整数中某一位是 7;
- 整数的每一位加起来的和是 7 的整数倍;
- 这个整数是 7 的整数倍。
现在问题来了:吉哥想知道在一定区间内和 7 无关的整数的平方和。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据占一行,包含两个整数 L 和 R。
输出格式
对于每组数据,请计算 [L,R] 中和 7 无关的数字的平方和,并将结果对 1e9+7 取模后输出。
数据范围
1≤T≤50,
1≤L≤R≤1e18
输入样例:
3
1 9
10 11
17 17
输出样例:
236
221
0
f[i][j][a][b]表示i位数字,首位为j,模数为a,各个数字之和的模数为b
存三个值:个数、a1+a2+...、
所以
推出这个公式后就能做了
取模很容易出错,建议先写完原式后再添加取模,多注意些
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 20, P = 1e9 + 7;
struct F
{
ll s0, s1, s2;
}f[N][10][7][7];
ll shi7[N], shi9[N];
int mod(ll x, int y)
{
return (x % y + y) % y;
}
void init()
{
for(int i = 0; i <= 9; i ++)
{
if(i == 7)continue;
auto &v = f[1][i][i % 7][i % 7];
v.s0 = 1;
v.s1 = i;
v.s2 = i * i;
}
ll res = 10;
for(int i = 2; i < N; i ++, res *= 10)
{
for(int j = 0; j <= 9; j ++)
{
if(j == 7)continue;
for(int a = 0; a <= 6; a ++)
{
for(int b = 0; b <= 6; b ++)
{
for(int k = 0; k <= 9; k ++)
{
if(k == 7)continue;
auto &v = f[i][j][a][b];
auto &v1 = f[i - 1][k][mod(a - j * res % 7, 7)][mod(b - j, 7)];
v.s0 = (v.s0 + v1.s0) % P;
v.s1 = (v.s1 + j * (res % P) % P * v1.s0 % P + v1.s1) % P;
v.s2 += j * j % P * (res % P) % P * (res % P) % P * v1.s0 % P + 2ll * j * (res % P) % P * v1.s1 % P + v1.s2;
v.s2 %= P;
}
}
}
}
}
shi7[0] = shi9[0] = 1;
for(int i = 1; i < N; i ++)
{
shi7[i] = (shi7[i - 1] * 10) % 7;
shi9[i] = (shi9[i - 1] * 10) % P;
}
}
F get(int i, int j, int a, int b)
{
ll s0 = 0, s1 = 0, s2 = 0;
for(int u1 = 0; u1 < 7; u1 ++)
{
if(u1 == a)continue;
for(int u2 = 0; u2 < 7; u2 ++)
{
if(u2 == b)continue;
auto &v = f[i][j][u1][u2];
s0 = (s0 + v.s0) % P;
s1 = (s1 + v.s1) % P;
s2 = (s2 + v.s2) % P;
}
}
return {s0, s1, s2};
}
int dp(ll n)
{
if(!n)return 0;
ll ttt = n % P;
vector<int> nums;
while(n)
{
nums.push_back(n % 10);
n /= 10;
}
ll res = 0, last_a = 0, last_b = 0;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
for(int j = 0; j < x; j ++)
{
if(j == 7)continue;
int a = mod(-last_a * shi7[i + 1], 7), b = mod(-last_b, 7);
auto v = get(i + 1, j, a, b);
res = mod(res + (last_a % P) * (last_a % P) % P * shi9[i + 1] % P * shi9[i + 1] % P * v.s0 % P
+ 2ll * (last_a % P) * shi9[i + 1] % P * v.s1 % P + v.s2, P);
}
if(x == 7)break;
last_a = last_a * 10 + x;
last_b += x;
if(!i && last_a % 7 && last_b % 7)res = mod(res + ttt * ttt, P);
}
return res % P;
}
int main()
{
IOS
init();
int _;
cin >> _;
while(_ --)
{
ll l, r;
cin >> l >> r;
cout << mod(dp(r) - dp(l - 1), P) << endl;
}
return 0;
}
--------------------------学完循环的数位dp后尝试了一道abc上的数位dp,发现完全不会写,又因题解全为记忆化写法,找不到循环写法的题解,因此准备重学记忆化的数位dp---------------------------------
烦人的数学作业
P4999 烦人的数学作业 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
pos为位数,sum为各位数字和,flag表示该位能否任意选
f数组的设立方式也与循环写法不同,首位为j变为了数位之和为j
确实更加方便
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 20, mod = 1e9 + 7;
ll f[N][170];//i位 数字之和为j
int a[N], len;
ll dfs(int pos, int sum, bool flag)
{
if(!pos)return sum;
if(!flag && f[pos][sum] != -1)return f[pos][sum];
int up = flag ? a[pos] : 9;
ll res = 0;
for(int i = 0; i <= up; i ++)
{
res = (res + dfs(pos - 1, sum + i, flag && i == up)) % mod;
}
if(!flag)f[pos][sum] = res;
return res;
}
ll solve(ll x)
{
len = 0;
while(x)
{
a[++ len] = x % 10;
x /= 10;
}
return dfs(len, 0, true);
}
int main()
{
IOS
int _;
cin >> _;
memset(f, -1, sizeof f);
while(_ --)
{
ll l, r;
cin >> l >> r;
ll ans = solve(r) - solve(l - 1);
ans = (ans % mod + mod) % mod;
cout << ans << endl;
}
return 0;
}
循环版:
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 20, mod = 1e9 + 7;
ll f[N][10];//i位 首位数字为j
ll cnt[N][10];
void init()
{
for(int i = 0; i <= 9; i ++)
{
f[1][i] = i;
cnt[1][i] = 1;
}
for(int i = 2; i < N; i ++)
{
for(int j = 0; j <= 9; j ++)
{
for(int k = 0; k <= 9; k ++)
{
f[i][j] += j * cnt[i - 1][k] + f[i - 1][k];
f[i][j] %= mod;
cnt[i][j] += cnt[i - 1][k];
cnt[i][j] %= mod;
}
}
}
}
ll dp(ll n)
{
if(!n)return 0;
vector<int> nums;
while(n)
{
nums.push_back(n % 10);
n /= 10;
}
int last = 0;
ll res = 0;
for(int i = nums.size() - 1; i >= 0; i --)
{
int x = nums[i];
for(int j = 0; j < x; j ++)
{
res = (res + f[i + 1][j]) % mod;
res = (res + cnt[i + 1][j] * last) % mod;
}
last += x;
if(!i)res += last;
}
return res % mod;
}
int main()
{
IOS
init();
int _;
cin >> _;
while(_ --)
{
ll l, r;
cin >> l >> r;
ll ans = dp(r) - dp(l - 1);
ans = (ans % mod + mod) % mod;
cout << ans << endl;
}
return 0;
}
数字计数
P2602 [ZJOI2010] 数字计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
f数组统计的是不考虑前导零的,所以对前导零这个变量需要狠狠的考虑考虑
比如说00135、000218中这些0都不应该计入答案,但显然不能在f数组中这么搞
所以需要在dfs中多加一个前导零的限制条件,记忆化时多加一个条件(只在dight为0时多加lead0的限制也是可以的)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 20;
ll f[N][N];//i位 dight出现了j次
int a[N], digit;
ll ans[10];
ll dfs(int pos, int cnt, bool limit, bool lead0)
{
if(!pos)return cnt;
auto &v = f[pos][cnt];
if(!limit && !lead0 && v != -1)return v;
ll res = 0;
int up = limit ? a[pos] : 9;
for(int i = 0; i <= up; i ++)
{
int tmp = cnt + (i == digit);
if(!digit && lead0 && !i)tmp = 0;
res += dfs(pos - 1, tmp, limit && i == up, lead0 && !i);
}
if(!limit && !lead0)v = res;
return res;
}
void solve(ll x, int f)
{
int len = 0;
while(x)
{
a[++ len] = x % 10;
x /= 10;
}
for(int i = 0; i <= 9; i ++)
{
digit = i;
ans[i] += f * dfs(len, 0, true, true);
}
}
int main()
{
IOS
memset(f, -1, sizeof f);
ll l, r;
cin >> l >> r;
solve(r, 1);
solve(l - 1, -1);
for(int i = 0; i <= 9; i ++)
{
cout << ans[i] << ' ';
}
return 0;
}
windy 数
f[i][j]表示有i位,上一位的结尾数字为j
本以为前导零可以忽略不记,但还是发现和循环的那种思路其实并不相同,如果有前有前导零时这一位放了0就表示下一位不能放1了,明显有问题,所以前有前导零时这位不能放0,可以放一个不影响的数进去,比如1000
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 15;
ll f[N][10];
int a[N];
ll dfs(int pos, int last, bool limit, bool lead0)
{
if(!pos)return 1;
if(last != 1000 && !limit && f[pos][last] != -1)return f[pos][last];
ll res = 0;
int up = limit ? a[pos] : 9;
for(int i = 0; i <= up; i ++)
{
if(abs(last - i) < 2)continue;
if(lead0 && !i)
{
res += dfs(pos - 1, 1000, limit && i == up, lead0 && !i);
}
else
{
res += dfs(pos - 1, i, limit && i == up, lead0 && !i);
}
}
if(last != 1000 && !limit)f[pos][last] = res;
return res;
}
ll solve(ll x)
{
int len = 0;
while(x)
{
a[++ len] = x % 10;
x /= 10;
}
return dfs(len, 1000, true, true);
}
int main()
{
IOS
memset(f, -1, sizeof f);
ll l, r;
cin >> l >> r;
cout << solve(r) - solve(l - 1) << endl;
return 0;
}
花神的数论题
P4317 花神的数论题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
把加法改成乘法就过了,最初有一点不太理解
想了想应该和状态的定义有关,定义是和就是和,定义是乘积就是乘积
把过程想象成一棵树,本来是一个点的结果是各个子节点相加,现在变成乘法就是各个子结点相乘
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 70, mod = 10000007;
int f[N][N];
int a[N];
ll dfs(int pos, int sum, int limit)
{
if(!pos)return max(sum, 1);//算到0时返回1
if(!limit && f[pos][sum] != -1)return f[pos][sum];
ll res = 1;
int up = limit ? a[pos] : 1;
for(int i = 0; i <= up; i ++)
{
res = res * dfs(pos - 1, sum + i, limit && i == up) % mod;
}
if(!limit)f[pos][sum] = res;
return res;
}
int main()
{
IOS
memset(f, -1, sizeof f);
ll n;
cin >> n;
int len = 0;
while(n)
{
a[++ len] = n % 2;
n /= 2;
}
cout << dfs(len, 0, true);
return 0;
}
Round Numbers S
P6218 [USACO06NOV] Round Numbers S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
f[pos][sum0][sum1]表示pos位,该位前已有sum0个0和sum1个1
记得额外判断前导零
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 60;
ll f[N][N][N];
int a[N];
ll dfs(int pos, int sum0, int sum1, bool lead0, bool limit)
{
if(!pos)return sum0 >= sum1;
if(!limit && f[pos][sum0][sum1] != -1)return f[pos][sum0][sum1];
int up = limit ? a[pos] : 1;
ll res = 0;
for(int i = 0; i <= up; i ++)
{
if(lead0 && !i)
{
res += dfs(pos - 1, 0, 0, lead0 && !i, limit && i == up);
}
else
{
if(!i)res += dfs(pos - 1, sum0 + 1, sum1, lead0 && !i, limit && i == up);
else res += dfs(pos - 1, sum0, sum1 + 1, lead0 && !i, limit && i == up);
}
}
if(!limit)f[pos][sum0][sum1] = res;
return res;
}
ll solve(ll x)
{
int len = 0;
while(x)
{
a[++ len] = x % 2;
x /= 2;
}
return dfs(len, 0, 0, true, true);
}
int main()
{
IOS
memset(f, -1, sizeof f);
ll l, r;
cin >> l >> r;
cout << solve(r) - solve(l - 1) << endl;
return 0;
}
Magic Numbers
因为两个数的数字位数相同,所以比他们小的位数即使算错了也没事,会抵消掉
如果数位不同则需设为f[pos][r][odd]多一维来判断是奇数位还是偶数位(因为前导零的关系不能固定地求出,因此需要用到这里),dfs中也多一个参数。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 2010, mod = 1e9 + 7;
int m, d;
ll f[N][N];
int a[N];
int len = 0;
ll dfs(int pos, int r, bool limit)
{
if(!pos)return r == 0;
if(!limit && f[pos][r] != -1)return f[pos][r];
ll res = 0;
int up = limit ? a[pos] : 9;
int t = (len - pos + 1);
if(t & 1)//当前是奇数位 不能放d
{
for(int i = 0; i <= up; i ++)
{
if(i == d)continue;
res = (res + dfs(pos - 1, (r * 10 + i) % m, limit && i == up)) % mod;
}
}
else//当前是偶数位 只能放d
{
if(d <= up)
res = (res + dfs(pos - 1, (r * 10 + d) % m, limit && d == up)) % mod;
}
if(!limit)f[pos][r] = res;
return res;
}
bool check(string &s)
{
bool f = true;
int tmp = 0;
for(int i = 0; i < s.size(); i ++)
{
tmp = tmp * 10 + s[i] - '0';
tmp %= m;
if(i % 2 == 0)
{
if(s[i] - '0' == d)
{
f = false;
}
}
else
{
if(s[i] - '0' != d)
{
f = false;
}
}
}
if(tmp)f = false;
return f;
}
int main()
{
IOS
cin >> m >> d;
memset(f, -1, sizeof f);
string l, r;
cin >> l >> r;
ll ans = 0;
for(int i = r.size() - 1; i >= 0; i --)
{
a[++ len] = r[i] - '0';
}
ans = dfs(len, 0, true);
len = 0;
for(int i = l.size() - 1; i >= 0; i --)
{
a[++ len] = l[i] - '0';
}
ans = (ans - dfs(len, 0, true)) % mod;
ans += check(l);
ans = (ans % mod + mod) % mod;
cout << ans;
return 0;
}
Salazar Slytherin's Locket
求[l, r]中在B进制中每个数字都出现偶数次的 数的个数
新得知识点:
多次B进制差的话不必每次清空,很有可能会超时,数组多开一层即可记录了
limit其实也可以开一个在数组后面多开一个[2],但无法复用且需每次清空所以不能这么写
0需考虑是否特判
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 70;
int a[N], B;
ll f[11][N][(1 << 10) + 1];
ll dfs(int pos, bool limit, bool lead0, int st)
{
if(!pos)
{
int res = !st && !lead0;
return res;
}
auto &v = f[B][pos][st];
if(!limit && !lead0 && v != -1)return v;
int up = limit ? a[pos] : B - 1;
ll res = 0;
for(int i = 0; i <= up; i ++)
{
if(lead0)
{
int tmp;
if(!i)tmp = 0;
else tmp = st ^ (1 << i);
res += dfs(pos - 1, limit && i == up, lead0 && i == 0, tmp);
}
else
{
res += dfs(pos - 1, limit && i == up, false, st ^ (1 << i));
}
}
if(!limit && !lead0)v = res;
return res;
}
ll solve(ll x)
{
int len = 0;
while(x)
{
a[++ len] = x % B;
x /= B;
}
return dfs(len, true, true, 0);
}
int main()
{
IOS
memset(f, -1, sizeof f);
int _;
cin >> _;
while(_ --)
{
ll l, r;
cin >> B >> l >> r;
cout << solve(r) - solve(l - 1) << endl;
}
return 0;
}
CCPC广州站M题
Dashboard - 2022 China Collegiate Programming Contest (CCPC) Guangzhou Onsite - Codeforces
首先需要拆位操作,其次需要想到数位dp
不同的是要对某一位上的全部数字同时操作,这时需要把limit改为s,意为受到限制的数的个数
因为是异或操作,所以用二进制来写是极好的
因为是二进制,在每一数位上能选的数除了0就是1,所以可以按照当前最高位是0还是1来考虑
是1的话就意味着受到限制的数们能选到1,是0的话就意味着受到限制的数们只能选0
因为m只有一个数且不是多组输入所以不需考虑需要复用的问题,因此s也可以加入记忆化中
题目中要算的话是每一对儿的和,能造成贡献的是0和1组对儿,因此0的数量*1的数量就是能造成贡献的数对儿的数量
k个数字,当前位,设x位选1,k-x位选0,造成的贡献就是x * (k - x) * ,而总共有C(k, x)种情况
还需要两个剪枝
1.now>n时就return 0
2.当前能取到的最大值都<n时就return0
每一位上能取到的最大贡献为,0~pos位的加起来就是
同时对k个数字进行数位dp,非常巧妙!
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 65, mod = 1e9 + 7;
ll n, m;
int k;
map<ll, int> f[N][N];
int C[N][N];
int a[N];
void init()
{
C[0][0] = 1;
for(int i = 1; i < N; i ++)
{
for(int j = 0; j <= i; j ++)
{
if(!j)C[i][j] = 1;
else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
}
int dfs(int pos, int s, ll now)//s个位置收到限制 现在的值为now
{
if(now > n)return 0;//比n大了就剪枝
int n1 = k / 2, n0 = k - n1;
ll t1 = ((1ll << pos + 1) - 1) * n1 * n0;
if(now + t1 < n)return 0;//剩下的最大加起来比n小也剪枝
if(pos == -1)return now == n;
if(f[pos][s].count(now))return f[pos][s][now];
ll res = 0;
if(a[pos] == 1)//被限制的数最多只能取到最高位,最高位为1时这些被限制的数才能操作
{
for(int i = 0; i <= s; i ++)//从被限制的里选i个1
{
for(int j = 0; j <= k - s; j ++)//从未被限制的里选j个1
{
int tmp = (ll)C[s][i] * C[k - s][j] % mod;//数量
ll t2 = (ll)(i + j) * (k - i - j) * (1ll << pos);
res = (res + (ll)tmp * dfs(pos - 1, i, now + t2) % mod) % mod; //注意方案数tmp要乘在这里
}
}
}
else
{
for(int i = 0; i <= k - s; i ++)//从未被限制的里选i个1
{
int tmp = C[k - s][i];
ll t2 = (ll)i * (k - i) * (1ll << pos);
res = (res + (ll)tmp * dfs(pos - 1, s, now + t2) % mod) % mod;
}
}
f[pos][s][now] = res;
return res;
}
int solve(ll x)
{
int len = 0;
while(x)
{
a[len ++] = x % 2;
x /= 2;
}
return dfs(len - 1, k, 0);
}
int main()
{
IOS
init();
cin >> n >> m >> k;
cout << solve(m);
return 0;
}
对我而言的最终boos - hard math
1214:square game (csgrandeur.cn)
虽然学数位dp是迟早的事,但学数位dp也有一部分原因是这道题------在曾经的比赛中挫败我的ta!
既然要记录不同数字的数量,那肯定要开一个state记录出现过哪些数字,但我固执地认为应该把state放进数组的一维里,就是开一个f[200010][1<<10]的数组,2e8的空间,就算空间不会爆,但最坏2e8次的dfs也得爆掉时间复杂度,于是虽然写法清晰但却不能写
直到想明白要的是“不同数字个数”,那要的是哪些数字重要吗?不重要!只要保证有这么多个数字不同就够了,所以state虽然重要,但完全不必要放到数组中,放到dfs过程中即可!
因为两个数字长度一样,想起来一些学习中不太愉快的细节,于是谨慎起见把最高位搞成了从1开始,这样也不用顾虑前导零了。
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 200010, mod = 1e9 + 7;
int n, m;
int f[N][11];
bool st[11];
int a[N];
bool check(string &s)
{
for(auto ch : s)
{
st[ch - '0'] = true;
}
int res = 0;
for(int i = 0; i <= 10; i ++)
{
if(st[i])
{
res ++;
}
}
return res == m;
}
int dfs(int pos, bool limit, int num, int state)
{
if(!pos)
{
if(!state)return m == 1;
return num == m;
}
auto &v = f[pos][num];
if(!limit && v != -1)return v;
ll res = 0;
int up = limit ? a[pos] : 9;
int down = (pos == n) ? 1 : 0;
for(int i = 0; i <= up; i ++)
{
int tmp1 = num + !(state >> i & 1);
res += dfs(pos - 1, limit && i == up, tmp1, state | (1 << i));
res %= mod;
}
if(!limit)v = res;
return res;
}
int solve(string &s)
{
int len = 0;
for(int i = s.size() - 1; i >= 0; i --)
{
a[++ len] = s[i] - '0';
}
return dfs(len, true, 0, 0);
}
int main()
{
IOS
memset(f, -1, sizeof f);
cin >> n;
string L, R;
cin >> L >> R >> m;
ll ans = solve(R) - solve(L) + check(L);
ans = (ans % mod + mod) % mod;
cout << ans;
return 0;
}