计数dp刷题题单

前置例题

[ZJOI2010] 数字计数 - 洛谷

题目:给定两个正整数 a 和 b,求在 [a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

思路:dp[pos][sum]表示后pos个位置为0...0~9...9,前面位置为数字now的个数有sum。

为什么要用多一维来记录sum?可以想到,只有前缀中数字共同出现sum次,前缀+后缀0...0~9...9组成的数的贡献才能归为一类,也就是前缀中的sum确定,后缀0...0~9...9贡献唯一。

比如1+0~9和2+0~9中2出现的次数明显不同。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e9 + 7, N = 1e5 + 10;
int num[15], dp[15][15];//dp[i][j]表示前sum个位置为数字now,后pos个位置为0...0~9...9,now总出现的个数
int now;//要统计的数字 
int dfs(int pos, int sum, int lead, int limit)//lead为1表示前len~pos-1位为0(即前导0),limit为1表示前len~pos-1位为最高位
{
	int ans = 0;
	if(pos == 0) return sum;
	if(!lead && !limit && dp[pos][sum] != -1) return dp[pos][sum];
	int up = (limit ? num[pos] : 9);
	for(int i = 0; i <= up; i ++)
	{
		if(i == 0 && lead) ans += dfs(pos - 1, sum, 1, limit && i == up);
		else if(i == now) ans += dfs(pos - 1, sum + 1, 0, limit && i == up);
		else ans += dfs(pos - 1, sum, 0, limit && i == up);
	}
	if(!lead && !limit) dp[pos][sum] = ans;
	return ans;
}
int solve(int x)
{
	int len = 0;
	while(x)
	{
		num[++ len] = x % 10;
		x /= 10;
	}
	memset(dp, -1, sizeof (dp));
	return dfs(len, 0, 1, 1);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int a, b; cin >> a >> b;
	for(int i = 0; i <= 9; i ++) now = i, cout << solve(b) - solve(a - 1) << ' ';
	cout << '\n';
}

题单

[SCOI2009] windy 数 - 洛谷

题目:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 a 和 b 之间,包括 a 和 b ,总共有多少个 windy 数?

思路:dp[pos][sum]表示后pos个位置为0...0~9...9,前面位置各位的和为sum的总数位和。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e9 + 7, N = 1e5 + 10;
int num[15], dp[15][15];
int dfs(int pos, int last, int lead, int limit)
{
	int ans = 0;
	if(pos == 0) return 1;
	if(!lead && !limit && dp[pos][last] != -1) return dp[pos][last];
	int up = (limit ? num[pos] : 9);
	for(int i = 0; i <= up; i ++)
	{
		if(abs(last - i) < 2) continue;
		if(i == 0 && lead) ans += dfs(pos - 1, -2, 1, limit && i == up);
		else ans += dfs(pos - 1, i, 0, limit && i == up);
	}
	if(!lead && !limit) dp[pos][last] = ans;
	return ans;
}
int solve(int x)
{
	int len = 0;
	while(x)
	{
		num[++ len] = x % 10;
		x /= 10;
	}
	memset(dp, -1, sizeof (dp));
	return dfs(len, -2, 1, 1);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int a, b; cin >> a >> b;
	cout << solve(b) - solve(a - 1) << ' ';
	cout << '\n';
}

 数页码 - 洛谷

题目:一本书的页码是从 1∼n 编号的连续整数:1,2,3,⋯,n。请你求出全部页码中所有单个数字的和,例如第 123页,它的和就是 1+2+3=6。

dp[pos][sum]表示数字长度为pos位,前一位是last的情况下(包括)前导0的无数位限制的Windy数。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e9 + 7, N = 1e5 + 10;
int num[15], dp[15][1005]; 
int dfs(int pos, int sum, int lead, int limit)
{
	int ans = 0;
	if(pos == 0) return sum;
	if(!lead && !limit && dp[pos][sum] != -1) return dp[pos][sum];
	int up = (limit ? num[pos] : 9);
	for(int i = 0; i <= up; i ++)
	{
		if(i == 0 && lead) ans += dfs(pos - 1, sum, 1, limit && i == up);
		else ans += dfs(pos - 1, sum + i, 0, limit && i == up);
	}
	if(!lead && !limit) dp[pos][sum] = ans;
	return ans;
}
int solve(int x)
{
	int len = 0;
	while(x)
	{
		num[++ len] = x % 10;
		x /= 10;
	}
	memset(dp, -1, sizeof (dp));
	return dfs(len, 0, 1, 1);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int x; cin >> x;
	cout << solve(x) << '\n';
}

 烦人的数学作业 - 洛谷

跟上面那题一样,加了个区间和取模。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e9 + 7, N = 1e5 + 10;
int num[25], dp[25][1005]; 
int dfs(int pos, int sum, int lead, int limit)
{
	int ans = 0;
	if(pos == 0) return sum % mod;
	if(!lead && !limit && dp[pos][sum] != -1) return dp[pos][sum] % mod;
	int up = (limit ? num[pos] : 9);
	for(int i = 0; i <= up; i ++)
	{
		if(i == 0 && lead) ans += dfs(pos - 1, sum, 1, limit && i == up);
		else ans += dfs(pos - 1, sum + i, 0, limit && i == up);
		ans %= mod;
	}
	if(!lead && !limit) dp[pos][sum] = ans % mod;
	return ans % mod;
}
int solve(int x)
{
	int len = 0;
	while(x)
	{
		num[++ len] = x % 10;
		x /= 10;
	}
	memset(dp, -1, sizeof (dp));
	return dfs(len, 0, 1, 1);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t = 1; cin >> t;
	while(t --)
	{
		int a, b; cin >> a >> b;
	    cout << (solve(b) - solve(a - 1) + mod) % mod<< '\n';
	}
	
}

 花神的数论题 - 洛谷

题目:设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你 ∏i=1~N​sum(i) ,也就是 sum(1)∼sum(N) 的乘积。

思路:统计1~n二进制下1的个数分别为1,2...len的数的数量,然后用快速幂累乘。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e7 + 7, N = 1e5 + 10;
int num[50], dp[50][200];
int now;//统计1的个数为now 
int dfs(int pos, int sum, int lead, int limit)
{
	int ans = 0;
	if(pos == 0) return sum == now;
	if(!lead && !limit && dp[pos][sum] != -1) return dp[pos][sum];
	int up = (limit ? num[pos] : 1ll);
	for(int i = 0; i <= up; i ++)
	{
		if(i == 0 && lead) ans += dfs(pos - 1, sum, 1, limit && i == up);
		else ans += dfs(pos - 1, sum + i, 0, limit && i == up);
	}
	if(!lead && !limit) dp[pos][sum] = ans;
	return ans;
}
int qmi(int a, int b)
{
	a %= mod;
	int res = 1;
	while(b)
	{
		if(b & 1) res = res * a % mod;
		b >>= 1ll;
		a = a * a % mod;
	}
	return res;
}
int solve(int n)
{
	int len = 0;
	int x = n;
	while(x)
	{
		num[++ len] = x % 2;
		x /= 2;
	}
	int ans = 1;
	for(int i = 1; i <= len; i ++)
	{
	    memset(dp, -1, sizeof (dp));
		now = i;
		ans = ans * qmi(i, dfs(len, 0, 1, 1)) % mod;
	}
	return ans % mod;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int n; cin >> n;
	cout << solve(n) << '\n';
}

 [CQOI2016] 手机号码 - 洛谷

题目:工具需要检测的号码特征有两个:号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8 和 4。号码必须同时包含两个特征才满足条件。手机号码一定是 11 位数,前不含前导的 0。工具接收两个数 L 和 R,自动统计出 [L,R] 区间内所有满足条件的号码数量。L 和 R 也是 11 位的手机号码。

思路:dp[pos][u][v][state][n8][n4]表示后pos位为0...0~9...9,往前第二个数是u,往前第一个数是v,sate=1标记有三个数相同的状态,n8表示存在8,n4表示存在4。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e7 + 7, N = 1e5 + 10;
int num[15], dp[15][10][10][2][2][2];
int now;//统计1的个数为now 
int dfs(int pos, int u, int v, int state, int n8, int n4, int limit)
{
	int ans = 0;
	if(n8 && n4) return 0;
	if(pos == 0) return state;
	if(!limit && dp[pos][u][v][state][n8][n4] != -1) return dp[pos][u][v][state][n8][n4];
	int up = (limit ? num[pos] : 9ll);
	for(int i = 0; i <= up; i ++)
	{
		ans += dfs(pos - 1, v, i, state || (u == v && v == i), n8 || (i == 8), n4 || (i == 4), limit && i == up);
	}
	if(!limit) dp[pos][u][v][state][n8][n4] = ans;
	return ans;
}
int solve(int n)
{
   
	int len = 0;
	int x = n;
	while(x)
	{
		num[++ len] = x % 10;
		x /= 10;
	}
	if(len != 11) return 0;
	int ans = 0;
	for(int i = 1; i <= num[len]; i ++)
	{ 
	    memset(dp, -1, sizeof (dp));
		ans += dfs(len - 1, -1, i, 0, i == 8, i == 4, i == num[len]);
	}
	return ans;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int a, b; cin >> a >> b;
	cout << solve(b) - solve(a - 1) << '\n';
}

[USACO06NOV] Round Numbers S - 洛谷

题目:如果一个正整数的二进制表示中,0 的数目不小于 1 的数目,那么它就被称为「圆数」。

例如,99 的二进制表示为 1001,其中有 2 个 0 与 2 个 1。因此,9 是一个「圆数」。

请你计算,区间 [l,r] 中有多少个「圆数」。

老套路,注意前导0不能算进去

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e7 + 7, N = 1e5 + 10;
int num[35], dp[35][35][35], len;
int dfs(int pos, int cnt0, int cnt1, int lead, int limit)
{
	int ans = 0;
	if(!pos) return (cnt0 >= cnt1);
	if(!lead && !limit && dp[pos][cnt0][cnt1] != -1) return dp[pos][cnt0][cnt1];
	int up = (limit ? num[pos] : 1ll);
	for(int i = 0; i <= up; i ++)
	{
		if(lead && i == 0) ans += dfs(pos - 1, cnt0, cnt1 + (i == 1), 1, limit && (i == up));
		else ans += dfs(pos - 1, cnt0 + (i == 0), cnt1 + (i == 1), 0, limit && (i == up));
	}
	if(!lead && !limit) dp[pos][cnt0][cnt1] = ans;
	return ans;
}
int solve(int n)
{
	len = 0;
	int x = n;
	while(x)
	{
		num[++ len] = x % 2;
		x /= 2;
	}
	memset(dp, -1, sizeof dp);
	int ans = 0;
	dfs(len, 0, 0, 1, 1);
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int a, b; cin >> a >> b;
	cout << solve(b) - solve(a - 1) << '\n';
	//int x; cin >> x; cout << solve(x) << '\n';
}

 Problem - C - Codeforces

定义一个数字是“好数”,当且仅当它的十进制表示下有不超过 3 个 1∼9 之间的数字。

给定 [l,r],问有多少个 x 使得 l≤x≤r,且 x 是“好数”

直接上套路

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int, int> PII;
const int mod = 1e7 + 7, N = 1e5 + 10;
int num[35], dp[35][35], len;
int dfs(int pos, int cnt, int lead, int limit)
{
	int ans = 0;
	if(!pos) return (cnt <= 3);
	if(!lead && !limit && dp[pos][cnt] != -1) return dp[pos][cnt];
	int up = (limit ? num[pos] : 9ll);
	for(int i = 0; i <= up; i ++)
	{
		ans += dfs(pos - 1, cnt + (i != 0), lead && i == 0, limit && (i == up));
	}
	if(!lead && !limit) dp[pos][cnt] = ans;
	return ans;
}
int solve(int n)
{
	len = 0;
	int x = n;
	while(x)
	{
		num[++ len] = x % 10;
		x /= 10;
	}
	memset(dp, -1, sizeof dp);
	int ans = 0;
	ans = dfs(len, 0, 1, 1);
	return ans;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	int t; cin >> t;
	while(t --)
	{
	    int a, b; cin >> a >> b;
	    cout << solve(b) - solve(a - 1) << '\n';		
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值