10.13 L5-f2021ljh模拟赛 题解

L5-f2021ljh模拟赛 题解

(自己组的题,补下题解)

真正的普及难度。。。

A. 运算(CF1542B

题意

\(s=1\),给定三个正整数 \(n,a,b\),不断令 \(s\gets s\times a\)\(s\gets s+b\),问能否使得 \(s=n\)

\(T \le 10^5\)\(n, a, b \le 10^9\)

思路

发现如果先加再乘即 \((s+b)\times a\),和先乘再加即 \(as+ab\) 是等效的。

所以我们不妨先乘再加。

那么所有可以生成的数字可以表示为:\(a^x+yb\)

于是问题变成了:判断是否存在 \(x,y\in\mathbb N\) 使得 \(n=a^x+yb\)

上式即 \(n\equiv a^x\pmod b\)

所以我们枚举 \(x\),判断 \(a^x\bmod b\) 是否等于 \(n\bmod b\) 即可。

由于 \(yb\ge0\),所以 \(a^x\le n\)

注意:特判 \(a=1\) 的情况,不然会死循环。

代码

#include <iostream>
#define int ll
using namespace std;
typedef long long ll;
bool flag;
int T, n, a, b;

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	
	cin >> T;
	while (T--) {
		flag = false;
		cin >> n >> a >> b;
		if (b == 1 || n == 1) {
			cout << "Yes\n";
			continue;
		}
		int t = n % b;
		if (a == 1) {
			if (t == 1) cout << "Yes\n";
			else cout << "No\n";
			continue;
		}
		for (int i = 1; i <= n; i *= a) {
			if (i % b == t) {
				flag = true;
				break;
			}
		}
		if (flag) cout << "Yes\n";
		else cout << "No\n";
	}
	
	return 0;
}

B. 移动(CF1015D

题意

有一排 \(n\) 个洞穴,从左到右编号为 \(1\)\(n\)。初始时你在洞穴 \(1\)

问能否使移动的总次数恰好为 \(k\),并且总距离恰好等于 \(s\) 个单位长度。

移动距离的计算方式:如果你从洞穴 \(x\) 移动到了洞穴 \(y\)\(1 \le x, y \le n\)),那么他移动的总距离(初始为 \(0\))增加了 \(|x - y|\) 个单位长度。

如果可行,输出移动方案。

\(n \le 10^9\)\(k \le 2 \times 10^5\)\(s \le 10^{18}\)

思路

首先,由于每一步最小距离为 \(1\),最大距离为 \(n-1\),所以总距离 \(s\) 应满足 \(k\le s\le k(n-1)\),如果不满足则无解。

现在,在满足上面条件的前提下,我们来尝试构造一种解。

  1. 取每一步的大小为 \(\lfloor s/k\rfloor\),剩余的 \(s\bmod k\) 个单位长度平均分配到前 \(s\bmod k\) 步上。

    即前 \(s\bmod k\) 步走 \(\lfloor s/k\rfloor+1\) 的距离,后 \(k-s\bmod k\) 步走 \(\lfloor s/k\rfloor\) 的距离。

    由于 \(k\le s\le k(n-1)\),这样走每一步的距离一定是小于 \(n\) 的。

    移动方案可以通过反复横跳来构造,跳的范围在 \([1,\lfloor s/k\rfloor+2]\) 之内。

    (最大的 \(s=k(n-1)\)\(\lfloor s/k\rfloor=n-1\)\(s\bmod k=0\),此时范围为 \([1,\lfloor s/k\rfloor+1]\)\([1,n]\),所以不会超过范围)

  2. 尽量走最大步,即每一步都走 \(n-1\) 个单位长度,直到不满足上述的有解条件,此时再尽量走最小步即 \(1\) 个单位长度,直到不满足上述的有解条件,此时剩余长度可以一步走完。

按上面两种思路暴力模拟即可。

代码

思路一
#include <iostream>
using namespace std;
long long n, k, s;
int main() {
	cin >> n >> k >> s;
	if (k * (n - 1) < s || k > s) {
		cout << "NO";
		return 0;
	}
	cout << "YES\n";
	long long t = 1;
	for (int i = 1; i <= s % k; i++) {
		if (i & 1) t = s / k + 2;
		else t = 1;
		cout << t << ' ';
	}
	for (int i = s % k + 1; i <= k; i++) {
		if (t <= (s / k)) t += s / k;
		else t -= s / k;
		cout << t << ' ';
	}
	return 0;
}
/*
https://www.luogu.com.cn/blog/1517460958dyc/solution-cf1015d
*/
思路二
#include <cstdio>
#include <iostream>
#define int ll
using namespace std;
typedef long long ll;
int n, k, s;

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> k >> s;
	if (k * (n - 1) < s || k > s) return cout << "NO\n", 0;
	else cout << "YES\n";
	int t = 1;
	while ((k - 1) * (n - 1) >= s - (n - 1) && k - 1 <= s - (n - 1) && k) {
		--k;
		s -= (n - 1);
		if (t == 1) t = n;
		else t = 1;
		cout << t << ' ';
	}
	if (!k) return cout << '\n', 0;
	while (k > 1) {
		--k;
		--s;
		if (t == 1 || t == n - 1) ++t;
		else if (t == 2 || t == n) --t;
		cout << t << ' ';
	}
	if (t == 1 || t == 2) cout << t + s << '\n';
	else cout << t - s << '\n';
	
	return 0;
}

C. 相等长度(CF1681D

题意

给定 \(x\)\(n\),在 \(x\) 的十进制表示中选一位数 \(y\)\(0\le y\le9\)),把 \(x\) 变为 \(x\cdot y\)

你的目标是将 \(x\) 的十进制表示(无前导零)的长度变为 \(n\) 位。

请求出所需的最少操作次数。

\(2\le n\le19\)\(1\le x\le10^{n-1}\)

思路

首先考虑一下暴力,即枚举所有数位上的数,可以用深搜实现。

但是这样的话肯定是会 T 的,考虑应该如何剪枝。

这道题的一个重要性质是:每一次操作最多增加一位数。

由此进行最优化剪枝:如果 当前操作次数 加上 至少操作的次数 仍大于等于当前答案,那么就没有进行下去的必要了。

而至少操作的次数为 最终位数 - 当前位数。

这样就可以通过了。

最大的 \(n\)\(19\),最大的 \(x\)\(999999999999999999\)(18 个 \(9\)),这样能出现的数最大为 \(8999999999999999991\)(17 个 \(9\)),所以只需要开 long long,不需要 unsigned long long

代码

#include <cstdio>
#include <iostream>
#define int ll
using namespace std;
typedef long long ll;
int n, x, ans = 999999;
bool flag;
int pw[20];

void dfs(int d, int sum, int cnt) {
	if (cnt == n) {
		ans = min(ans, d);
		flag = true;
		return;
	}
	if (d + (n - cnt) >= ans) return; //最优性剪枝
	int tmp = sum;
	bool vis[10] = {0};
	while (tmp) {
		vis[tmp % 10] = true;
		tmp /= 10;
	}
	for (int i = 2; i <= 9; ++i)
		if (vis[i])
			dfs(d + 1, sum * i, cnt + (sum * i >= pw[cnt + 1]));
}

signed main() {
	
	cin >> n >> x;
	pw[1] = 1;
	for (int i = 2; i <= 19; ++i) pw[i] = pw[i - 1] * 10;
	int cnt = 0, tmp = x;
	while (tmp) {
		++cnt;
		tmp /= 10;
	}
	dfs(0, x, cnt);
	if (!flag) cout << -1 << '\n';
	else cout << ans << '\n';
	
	return 0;
}

D. 数字游戏(CF1628D1

题意

dbxxx 和 ACodingHusky 正在玩一个游戏,游戏分为 \(T\) 轮,每一轮有 \(n\) 个回合。在每个回合中,dbxxx 和 ACodingHusky 要同时对一个数 \(x\) 进行操作,已知这个数在每一轮的初始值是 \(0\)

游戏规则如下:

  • 每一回合中,dbxxx 选择一个实数 \(t\),其中 \(t\in[0,k]\)
  • 每一回合中,ACodingHusky 可以选择让 \(x\) 变成 \(x+t\) 或者 \(x-t\),但是 ACodingHusky 在 \(n\) 个回合之内至少选择 \(m\) 次让 \(x\) 变成 \(x+t\)

dbxxx 想让最终的 \(x\) 最大,ACodingHusky 想让最终的 \(x\) 最小。

由于他们都聪明绝顶,所以他们在每个回合 一定均采用最优策略,给定 \(k\),求每一轮最终的 \(x\) 值(对 \(10^9+7\) 取模)。

\(T\le1000\)\(1\le m\le n\le 2000\)\(0\le k<10^9+7\)

思路

这类题一般有一个重要条件:A 和 B 两个人都是 绝顶聪明 的,即每一次决策都一定是当前最优的。

所以 这一次决策 由 下一次决策的结果 来决定。

\(f(i,j)\) 表示还剩 \(i\) 个回合、B 还需要\(j\) 次时的答案(也可以看做是 \(n=i,m=j\) 时的答案)。

初值:\(f(i,0)=0\)\(f(i,i)=i\times k\)

设 A 所选的数为 \(x\)\(x\in[0,k]\)),由于 B 一定选加和减中较小的,所以 \(f(i,j)=\min\{f(i-1,j)-x,f(i-1,j-1)+x\}\)

A 需要最大化 \(f(i,j)\),最大值点即为两个一次函数的交点,即 \(f(i,j)=\dfrac{f(i-1,j)+f(i-1,j-1)}2\)\(j\le i-1\))。

答案为 \(f(n,m)\)

题目要求答案对 \(10^9+7\) 取模,所以每次除以 \(2\) 即相当于乘以 \(2\)\(10^9+7\) 意义下的逆元,即 \(5\times10^8+4\)

单次查询时间复杂度 \(O(nm)\)

然而这道题有多组测试数据,这样肯定是过不去的。

发现状态转移与 \(n,m,k\) 无关,只有初值与 \(k\) 有关。

所以我们令 \(k=1\) 预处理出 dp 数组,每次询问输出 \(f(n,m)\times k\) 即可。

预处理复杂度 \(O(nm)\),询问复杂度 \(O(1)\),可以通过。

代码

#include <cstdio>
#include <iostream>
#define int ll
using namespace std;
typedef long long ll;
const int N = 2e3 + 10;
const int MOD = 1e9 + 7;
const int INV = 5e8 + 4;
int T, n, m, k;
int f[N][N]; //f[i][j]表示n=i, m=j时的答案

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
//	freopen("game1.in", "r", stdin);
//	freopen("game1.ans", "w", stdout);
	
	for (int i = 1; i <= 2000; ++i) {
		f[i][i] = i;
		for (int j = 1; j < i; ++j)
			f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) * INV % MOD;
	}
	cin >> T;
	while (T--) {
		cin >> n >> m >> k;
		cout << f[n][m] * k % MOD << '\n';
	}
	
	return 0;
}
posted @ 2022-11-06 21:10  f2021ljh  阅读(9)  评论(0)    收藏  举报