2021/01/19训练总结

前言

昨天进行了一场数位dp专题,写下题解。如果要学习数位dp,这里给一个我学习的链接
关于数位dp的一些题:
P4999 烦人的数学作业
P6218 [USACO06NOV] Round Numbers S
P2657 [SCOI2009] windy 数
P4317 花神的数论题
P2602 [ZJOI2010]数字计数
P4127 [AHOI2009]同类分布
P4124 [CQOI2016]手机号码

HDU 2089 不要62

题目链接:不要62
题目大意:给定 [ L , R ] [L,R] [L,R]区间,问区间内不含62和4的数字的个数。
数据范围: 0 < n ≤ m < 1000000 0<n \le m <1000000 0<nm<1000000
题解:这个数据量不是很大,所以可以直接暴力 L → R L\to R LR扫一遍,看是否有62 o r or or 4。不过还是说一下我数位 d p dp dp的做法,其实简单数位 d p dp dp完全是板子的做法。即先将 [ L , R ] [L,R] [L,R]变成 [ 1 , R ] − [ 1 − L − 1 ] [1,R]-[1-L-1] [1,R][1L1]。然后用 f i n d ( x ) find(x) find(x)函数来找 [ 1 , x ] [1,x] [1,x]内满足要求的个数。我们需要在dfs中维护的有:还剩余的位数,是否顶着上界,搜索的前一个数,是否已经包含62,是否包含了4。然后我们对应转移就行了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[15], dp[10][2][10][2][2];
int dfs(int cur, int limit, int pre, bool _4, bool _62)
{
	if (!cur)
	{
		if (_4 || _62)return 0;
		return 1;
	}
	if (~dp[cur][limit][pre][_4][_62])return dp[cur][limit][pre][_4][_62];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, i, _4 || i == 4, _62 || (i == 2 && pre == 6));
	}
	return dp[cur][limit][pre][_4][_62] = ans;
}
int find(int x)
{
	memset(dp, -1, sizeof(dp));
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 0, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	while (1)
	{
		int l, r; read(l), read(r);
		if (!l && !r)break;
		printf("%lld\n", find(r) - find(l - 1));
	}

	return 0;
}

HDU - 3555 Bomb

题目链接:Bomb
题目大意:求 [ L , R ] [L,R] [L,R]区间内包含 49 49 49的有多少。
数据范围: 1 ≤ T ≤ 1 0 5 , 1 ≤ N ≤ 2 63 − 1 1 \le T \le 10^5,1\le N \le 2^{63}-1 1T105,1N2631
题解:我们可以发现,不同的数位dp基本上改的就是dfs函数。这题我们就要维护前面的搜索结果里面是否已经包含了49,我这里用 i s o k isok isok表示。然后要维护 i s o k isok isok,我们需要得到上一次的搜索数位是什么,我们用 p r e pre pre来记录。剩下的就是对应转移了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[30], dp[30][2][10][2],t;
int dfs(int cur, bool limit, int pre, bool isok)
{
	if (!cur)
	{
		return isok;
	}
	if (~dp[cur][limit][pre][isok])return dp[cur][limit][pre][isok];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp),i, isok||(i == 9 && pre==4));
	}
	return dp[cur][limit][pre][isok] = ans;
}
int find(int x)
{
	memset(dp, -1, sizeof(dp));
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 0, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t);
	while (t--)
	{
		int x; read(x);
		printf("%lld\n", find(x));
	}

	return 0;
}

CodeForces - 55D Beautiful numbers

题目链接:Beautiful numbers
题目大意:求 [ L , R ] [L,R] [L,R]区间内包含可以被自身所有数位整除的有多少。
数据范围: 1 ≤ t ≤ 10 , 1 ≤ l ≤ r ≤ 9 ∗ 1 0 18 1\le t \le 10,1\le l\le r\le 9*10^{18} 1t10,1lr91018
题解:非常好的一道题,数位dp进阶必做。首先我们依靠前两题来写一下我们的模板dfs函数

int dfs(int cur, bool limit, int premul, int prenum)
{
	if (!cur)
	{
		return prenum % premul == 0;
	}
	if (~dp[cur][limit][premul][prenum])return dp[cur][limit][premul][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i?premul:premul*i, (prenum * 10 + i));
	}
	return  dp[cur][limit][premul][prenum] = ans;
}

和我们之前写的完全一样! p r e n u m prenum prenum表示之前搜索的数, p r e m u l premul premul表示已经搜索的数位的积。这样我们就完成了这一题。不过有个问题就是dp数组的大小。我们 p r e m u l premul premul可以达到 9 ∗ 1 0 18 9*10^{18} 91018的大小!空间已经炸穿了。我们这里可以发现一个空间小优化就是我们可以预处理出 9 ! 9! 9!,边模边运算。dfs如下

int dfs(int cur, bool limit, int premul, int prenum)
{
	if (!cur)
	{
		return prenum % premul == 0;
	}
	if (~dp[cur][limit][premul][prenum] && !limit)return dp[cur][limit][premul][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i ? premul : premul * i, (prenum * 10 + i) % (9!));
	}
	return  dp[cur][limit][premul][prenum] = ans;
}

这样空间要求变成了 19 ∗ 2 ∗ 9 ! ∗ 9 ! 19*2*9!*9! 1929!9!掐指一算,我们还是炸空间了,然后可以可以注意到一个优化的点,就是我们可以将 p r e m u l premul premul变成 p r e l c m prelcm prelcm也就是将之前搜索到的数位积变成数位lcm。这样我们就可以将9!优化下到 l c m ( 1 , 2 , . . 9 ) = 2520 lcm(1,2,..9)=2520 lcm(1,2,..9)=2520。dfs如下

int dfs(int cur, bool limit, int prelcm, int prenum)
{
	if (!cur)
	{
		return prenum % prelcm == 0;
	}
	if (~dp[cur][limit][prelcm][prenum] && !limit)return dp[cur][limit][prelcm][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), !i ? prelcm : lcm(prelcm ,i), (prenum * 10 + i) % (2520));
	}
	return  dp[cur][limit][prelcm][prenum] = ans;
}

空间成功变成 19 ∗ 2 ∗ 2520 ∗ 2520 19*2*2520*2520 19225202520。还是超了一点。。。不过我们可以发现 p r e l c m prelcm prelcm无法取到 1 → 2520 1\to 2520 12520之间的所有数,只能取到2520的因子数。所以最后一个优化就是将 p r e l c m prelcm prelcm离散化。离散化后可以发现 p r e l c m prelcm prelcm只能取48个,所以我们空间变成了 19 ∗ 2 ∗ 50 ∗ 2520 19*2*50*2520 192502520,完全ok了。

AC代码:


#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
int w[30], dp[20][50][2600], t, totmod;
int a[N], toa[N];
int gcd(int x, int y)
{
	return !y ? x : gcd(y, x % y);
}
int lcm(int x, int y)
{
	if (!x || !y)return x + y;
	return x * y / gcd(x, y);
}
void init()
{
	memset(dp, -1, sizeof(dp));
	totmod = 1;
	for (int i = 2; i <= 9; i++)totmod = lcm(totmod, i);
	for (int i = 1; i <= totmod; i++)
	{
		if (totmod % i == 0)a[++a[0]] = i, toa[i] = a[0];
	}
}
int dfs(int cur, bool limit, int prelcm, int prenum)
{
	if (!cur)
	{
		return prenum % prelcm == 0;
	}
	if (~dp[cur][toa[prelcm]][prenum]&&!limit)return dp[cur][toa[prelcm]][prenum];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && (i == tp), lcm(prelcm, i), (prenum * 10 + i) % totmod);
	}
	if (!limit)dp[cur][toa[prelcm]][prenum] = ans;
	return  ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	init();
	read(t);
	while (t--)
	{
		int l, r; read(l), read(r);
		printf("%lld\n", find(r) - find(l - 1));
	}

	return 0;
}

LightOJ - 1336 Sigma Function

题目链接:Sigma Function
题目大意:给定n,求[1-n]中因子个数为偶数的个数。
数据范围: 1 ≤ t ≤ 100 , 1 ≤ n ≤ 1 0 12 1\le t \le 100,1\le n\le10^{12} 1t100,1n1012
题解:我是直接打表写的。程序如下

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int calc(int x)
{
	int ans = 0;
	for (int i = 1; i * i <= x; i++)
	{
		if (x % i == 0)
		{
			ans += i;
			if (x != i * i)ans += x / i;
		}
	}
	return ans;
}
ll t, n;
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	for (int i = 1; i <= 1000; i++)
	{
		if(calc(i)%2)
			printf("%d ", i);
	}

	return 0;
}

结果如下:
在这里插入图片描述
我发现因子个数为奇数的i都满足 i = 2 p ∗ x ∗ x i=2^p*x*x i=2pxx
进一步分解,假设p是偶数则有 y = x ∗ 2 p 2 , i = y ∗ y y=x*2^{\frac{p}{2}},i=y*y y=x22p,i=yy
p是奇数则有 y = x ∗ 2 p 2 , i = 2 ∗ y ∗ y y=x*2^{\frac{p}{2}},i=2*y*y y=x22p,i=2yy
即如果i满足是完全平方数 o r or or完全平方数的两倍则i的因子个数数是奇数。
又完全平方数的个数= n \sqrt{n} n ,完全平方数的两倍个数= n 2 \sqrt{\frac{n}{2}} 2n 。所以 a n s = n − n − n 2 ans=n-\sqrt{n}-\sqrt{\frac{n}{2}} ans=nn 2n
这里有一个证明的题解:链接
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
ll t, n;
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE

	ll t, n; int qiu = 0;
	read(t);
	while (t--)
	{
		read(n);
		printf("Case %d: %lld\n", ++qiu, n - (ll)sqrt(n) - (ll)sqrt(n / 2));
	}
	return 0;
}

HDU - 4352 XHXJ’s LIS

题目链接:C - XHXJ’s LIS
题目大意:给定[L,R],求各位数字组成的严格上升子序列的长度为K的个数。
数据范围: 1 ≤ L ≤ R ≤ 2 63 − 1 , 1 ≤ K ≤ 10 1\le L\le R\le 2^{63}-1,1\le K \le 10 1LR2631,1K10
题解:我们用一个10位二进制状态来表示最长上升子序列,状态的修改参考导弹拦截题目里面给出的 n l o g n nlogn nlogn修改的方法。其他的就是数位dp了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, l, r, K;
int dp[20][2][1 << 11][11], w[21];
int getnum(int x)
{
	int ans = 0;
	while (x)
	{
		ans++;
		x -= x & -x;
	}
	return ans;
}
int updatestate(int x, int st)
{
	for (int i = x; i <= 9; i++)
	{
		if (st & (1 << i))return (st ^ (1 << i)) | (1 << x);
	}
	return st | (1 << x);
}
int dfs(int cur, int limit, int _0, int state)
{
	if (!cur)return getnum(state) == K;
	if (!limit && ~dp[cur][_0][state][K])return dp[cur][_0][state][K];
	int ans = 0;
	int tp = limit ? w[cur] : 9;
	for (int i = 0; i <= tp; i++)
	{
		ans += dfs(cur - 1, limit && i == tp, _0 && i == 0, (_0 && i == 0) ? 0 : updatestate(i, state));
	}
	if (!limit)dp[cur][_0][state][K] = ans;
	return ans;
}
int find(int x)
{
	w[0] = 0;
	while (x)w[++w[0]] = x % 10, x /= 10;
	return dfs(w[0], 1, 1, 0);
}
signed main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	memset(dp, -1, sizeof(dp));
	read(t); int qiu = 0;
	while (t--)
	{
		read(l), read(r), read(K);
		printf("Case #%lld: %lld\n", ++qiu, find(r) - find(l - 1));
	}

	return 0;
}

LightOJ - 1282 Leading and Trailing

题目链接:Leading and Trailing
题目大意:求 n k n^k nk的前3位和后3位
数据范围: 2 ≤ n ≤ 2 31 , 1 ≤ k ≤ 1 0 7 2\le n \le 2^{31},1\le k \le 10^7 2n231,1k107
题解:参考链接
后三位我们可以直接快速幂模1000求出,对于前三位
1 0 p = n k 10^p=n^k 10p=nk其中 p = l o g 10 ( n k ) = k ∗ l o g 10 ( n ) = m + x p=log10(n^k)=k*log10(n)=m+x p=log10(nk)=klog10(n)=m+x, m m m p p p的整数部分, x x x p p p的小数部分。写成科学计数法就是 1 0 x ∗ 1 0 m = n k 10^x*10^m=n^k 10x10m=nk其中 1 0 x 10^x 10x大于1小于10, m m m是决定值的位数。也就是我们的前3位只和 1 0 x 10^x 10x有关, 1 0 x ∗ 100 10^x*100 10x100再取整就是答案了。
AC代码:

#include<bits/stdc++.h>

#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
	T res = 0, f = 1; char c = getchar();
	while (!isdigit(c)) {
		if (c == '-')f = -1; c = getchar();
	}
	while (isdigit(c)) {
		res = (res << 3) + (res << 1) + c - '0'; c = getchar();
	}
	x = res * f;
}
const ll N = 200000 + 10;
const int mod =1000;
ll t, n, k;
int fpow(ll x, ll y)
{
	x %= mod;
	int ans = 1;
	while (y)
	{
		if (y & 1)ans = 1ll * x * ans % mod;
		x = 1ll * x * x % mod; y >>= 1;
	}
	return ans;
}
int main()
{
	//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
	freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
	read(t); int qiu = 0;
	while (t--)
	{
		read(n), read(k);
		double p = k * log10(n);
		double x = p - int(p);
		double tx = pow(10, x);
		printf("Case %d: %03d %03d\n", ++qiu, (int)(tx * 100), fpow(n, k));
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值