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)\),如果不满足则无解。
现在,在满足上面条件的前提下,我们来尝试构造一种解。
-
取每一步的大小为 \(\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]\),所以不会超过范围)
-
尽量走最大步,即每一步都走 \(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;
}

浙公网安备 33010602011771号