数位dp 笔记

小技巧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] = \sum_{k=j}^{9} 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 有关:

  1. 整数中某一位是 7;
  2. 整数的每一位加起来的和是 7 的整数倍;
  3. 这个整数是 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+...、a1^{2}+a2^{2}+...

(ja1)^{2} = (j*10^i +a1)^2 =j*j*10^i*10^i+2*j*10^i+a1*a1

所以(ja_1)^2+(ja_2)^2+...=(j*10^i)*t + 2*j*10^i+(a_1+a_2+...)+a_1^2+a_2^2+...

推出这个公式后就能做了

取模很容易出错,建议先写完原式后再添加取模,多注意些

#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

Problem - D - Codeforces

因为两个数的数字位数相同,所以比他们小的位数即使算错了也没事,会抵消掉

如果数位不同则需设为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

Problem - E - Codeforces

求[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) * 2^{pos},而总共有C(k, x)种情况

还需要两个剪枝

1.now>n时就return 0

2.当前能取到的最大值都<n时就return0

每一位上能取到的最大贡献为2^{pos}*\frac{k}{2}*(k-\frac{k}{2}),0~pos位的加起来就是(2^{pos+1}-1)*\frac{k}{2}*(k-\frac{k}{2})

同时对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;
}

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值