知识点:数学,构造。
题目要求构造一个长为 \(m\) 的序列 \(c\) ,\(m\) 自选,使得 \(c\) 的无限循环序列 \(b\) 中任意连续 \(a_i\) 个数中都存在数 \(i\) 。因此,容易想到从起点 \(pos_i\) 开始每隔 \(a_i\) 个数就填一次 \(i\) ,即在位置 \((pos_i + ka_i) \mod m , 0\leq k < \frac{m}{a_i}\) ,由此确定了构造的基本方法是填数。
但显然,直接挨个填数很可能会使得某个数的某个落点已经被其他数占据了。比如当 \(a = [5,4]\) 时,考虑构造 \(m=5\) 的序列:先填 \(1\) 得到 \([1,0,0,0,0]\) ,再填 \(2\) 得到 \([1,2,0,0,0]\) ,发现下一个位置是 \(c[0]\) ,已经有数字了。然而事实上,我们不容易在较小的时间复杂度内得到一个合适的起点,使得这个数的落点不会被其他数占据。因此,考虑调整 \(a_i\) ,使得我们可以容易避免落点重合的情况。
证明1
显然,我们只能缩小 \(a_i\) 使得,因为缩小后的 \(a_i\) 满足时原来的 \(a_i\) 一定满足,而放大不一定。
注意到,对于任意两个数 \(a_i\) 与 \(a_j\), 当 \(a_j = da_i,d\geq1\) ,若 \(\exist k,m\) ,使得 \(pos_i + ka_i = pos_j +ma_j\) ,则有 \(pos_j = pos_i + ka_i - ma_j\) ,于是对于 \(\forall m'\),都有 \(pos_j + m'a_j = pos_i + ka_i + (m'-m)a_j = pos_i + [k+(m'-m)d]a_i\) ,即处处重合。
因此,当 \(a_j = da_i\) ,只要一处不重合,则所有落点都不重合。为了方便,将 \(a_i\) 缩小成小于等于它的最大的 \(2\) 的幂 \(a'_i\)。
但仅仅是这样只能保证两两不重合,还无法保证全都不重合。比如当 \(a = [8,8,8,8,4]\) ,考虑 \(m = 8\) ,按顺序填,填到 \(5\) 时会发现 \([1,2,3,4,5,0,0,0]\) ,下一个位置是 \(c[0]\) ,还是被占据了。因此考虑改变填数顺序,使得后一个填的数不会被前面所有的数的位置影响。
证明2
从大到小的就是上述情况,不可行。
考虑从小到大,每次在第一个空位开始填。
由于每次都是从第一个空位填,所以不允许某个 \(a_i'\) 前面填了大于等于 \(a_i'\) 个位置,否则必然会循环回来落到开头被占据的位置,如上述情况。只要我们证明,对于任意一个 \(a_i’\),其前方已经填的位置数 \(x < a_i'\) ,即可。
由于 \(a_i' > \dfrac{a_i}{2}\) ,又条件告诉我们有 \(\sum \frac{1}{a_i} \leq \frac12\) ,那么现在有 $\sum \frac{1}{a_i'} < \sum \frac{2}{a_i} \leq 1 $ 。
当填到 \(a_i' = 2^k\) 时,我们尝试构造 \(a_{1\cdots i-1}' \leq 2^k\) ,使得从 \(0\) 开始连续占用最多的位置。注意到,如果 \(a_j' = 2^ma_i',m\geq 1\),则使用 \(a_i'\) 等效于使用了 \(2^m\) 个 \(a_j'\) ,且总和 \(2^m \frac{1}{a_j'} = \frac{1}{a_i'}\) 也是等价的 ,所以我们按这个方法,无论怎么取,最优结果都是一样的。因此在 \(\sum a_i' \leq 1 \Rightarrow \frac{1}{a_1'} + \cdots + \frac{1}{a_{i-1}'} \leq 1-\frac{1}{2^k}\) 下,为了方便,我们可以优先用小的 \(a_i\) 构造,快速缩小可行范围。
显然,我们只能构造 \(a = [2,4,8,\cdots,2^k]\) ,其倒数总和刚好是 \(1-\frac{1}{2^k}\) ,而连续占用位置是 \(2^k-1\) 个。
于是我们证明了这个结论。
到这里其实最难的两个证明已经结束了。接下来我们思考一下 \(m\) 到底取多少合适。
证明3
长度 \(m\) 时占位为 \(\sum \lceil \frac{m}{a_i'} \rceil\) ,因为每个 \(a_i'\) 对应的 \(i\) 至少出现一次,可得 \(m \geq max(a)\)。
令 \(m = max(a)\) ,带入得 \(\sum \lceil \frac{m}{a_i'} \rceil = \sum \frac{m}{a_i'} \leq m\) ,因此 \(m = max(a)\) 是最小长度。
由于可能多出空位,但是可以随便填,所以全填 \(1\) 。
到此这道构造题就完全结束了。
时间复杂度 \(O(n\log n + max(a))\)
空间复杂度 \(O(n+max(a))\)
#include <bits/stdc++.h>
using namespace std;
struct node {
int val;
int id;
}a[100007];
int c[1000007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
cin >> a[i].val;
a[i].id = i;
int tmp = 1 << 30;
while (tmp > a[i].val) tmp >>= 1;
a[i].val = tmp;
}///使用2的幂,证明1
sort(a + 1, a + n + 1, [&](node a, node b) {
return a.val < b.val;
});///从小到大,证明2
int m = a[n].val;///c的长度,证明3
for (int i = 1, pos = 0;i <= n;i++) {
for (int j = pos;j < m;j += a[i].val) c[j] = a[i].id;///方法是填数
while (c[pos]) pos++;
}
cout << m << '\n';
for (int i = 0;i < m;i++) cout << max(1, c[i]) << ' ';///可能多出来空位随便填
cout << '\n';
return 0;
}
知识点:枚举,前缀和,差分,树。
考虑树上前缀和与差分。维护当前递归的一条树链的深度到节点编号的映射数组 \(depL\) 即可差分,最后递归求前缀和。
时间复杂度 \(O(n)\)
空间复杂度 \(O(n)\)
#include <bits/stdc++.h>
using namespace std;
vector<int> g[2000007];
int d[2000007];
int dep[2000007], depL[2000007];///节点深度,一条链上深度对应节点
int sum[2000007];///树上差分
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
depL[dep[u]] = u;
sum[u]++;
sum[depL[max(0, dep[u] - d[u] - 1)]]--;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
}
}
void cal(int u, int fa) {
for (auto v : g[u]) {
if (v == fa) continue;
cal(v, u);
sum[u] += sum[v];
}
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i < n;i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1;i <= n;i++) cin >> d[i];
dfs(1, 0);
cal(1, 0);
for (int i = 1;i <= n;i++) cout << sum[i] << ' ';
cout << '\n';
return 0;
}
知识点:模拟。
签到题。直接打表也行,模拟也行。
时间复杂度 \(O(n^2)\)
空间复杂度 \(O(1)\)
#include <bits/stdc++.h>
using namespace std;
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i <= 13 * n + 19;i++) cout << '*';
cout << '\n';
for (int i = 1;i <= n;i++) {
cout << "*";
for (int j = 2;j <= 13 * n + 18;j++)
cout << ".";
cout << "*";
cout << '\n';
}
for (int i = 1;i <= 2 * n + 3;i++) {
cout << "*";
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 1 || i == 2 * n + 3) {
if (j == 1 || j == 2 * n + 3) cout << "@";
else cout << ".";
}
else {
if (j == 1 || j == 2 * n + 3 || j == i) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 1 || i == n + 2) cout << "@";
else {
if (j == 1) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 2 * n + 3) cout << "@";
else {
if (j == 1) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
for (int j = 1;j <= 2 * n + 3;j++) {
if (i == 1 || i == n + 2 || i == 2 * n + 3) cout << "@";
else if (1 < i && i < n + 2) {
if (j == 1) cout << "@";
else cout << ".";
}
else if (n + 2 < i && i < 2 * n + 3) {
if (j == 2 * n + 3) cout << "@";
else cout << ".";
}
}
for (int j = 1;j <= n + 1;j++) cout << ".";
cout << "*";
cout << '\n';
}
for (int i = 1;i <= n;i++) {
cout << "*";
for (int j = 2;j <= 13 * n + 18;j++)
cout << ".";
cout << "*";
cout << '\n';
}
for (int i = 1;i <= 13 * n + 19;i++) cout << '*';
cout << '\n';
return 0;
}
知识点:思维,数学。
A不能变,B只有A-B与B两个值,于是C的变换方法只有形如交替的A-B与B,而最终C的符号取决于偶数次变换还是奇数次。公式为:
\[\left\{
\begin{aligned}
k(2B-A) +C = x,变换偶次\\
B-k(2B-A)-C = x,变换奇次
\end{aligned}
\right.
\]
这里的 \(k\) 取负时的意义简单理解是交换了变换顺序,比如 \([A-B,B] \rightarrow [B,A]\) 。只要找到整数 \(k\) 满足等式即可,注意 \(2B-A = 0\) 时需要特判。
时间复杂度 \(O(1)\)
空间复杂度 \(O(1)\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
bool solve() {
int a, b, c, x;
cin >> a >> b >> c >> x;
if (2 * b - a == 0) {
if (x - c == 0 || x + c - b == 0) cout << "Yes" << '\n';
else return false;
}
else if ((x - c) % (2 * b - a) == 0 || (x + c - b) % (a - 2 * b) == 0) cout << "Yes" << '\n';
else return false;
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "No" << '\n';
}
return 0;
}
知识点:线性dp,博弈论。
通过转移局面情况到起点,然后判断起点是否到达目标状态。
其中由于走法是固定向下或者向右,所以可以通过横纵坐标之和的奇偶性来决定当前是谁的回合。
对于A而言,两种走法有一种能得到目标状态就转移;对于B而言,两种走法有一种得不到目标状态就转移。
具体看代码注释qwq。
时间复杂度 \(O(nm)\)
空间复杂度 \(O(nm)\)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
char dt[507][507];
int dp[507][507];
///三种局面是可以独立考虑的,分别看作能否一定走到A、B、(n,m)处状态
///用三位二进制分别表示(i, j)处A是否一定能走到A、B、(n, m)处状态
///1表示一定能走到,0表示不一定能走到(包括一定不)
///已知A处001,终点处为010,B处100
bool solve() {
int n, m;
cin >> n >> m;
for (int i = 1;i <= n;i++)
for (int j = 1;j <= m;j++)
cin >> dt[i][j];
for (int i = n;i >= 1;i--) {
for (int j = m;j >= 1;j--) {
if (dt[i][j] == 'A') { dp[i][j] = 1;continue; }
if (dt[i][j] == 'B') { dp[i][j] = 4;continue; }
if (i == n && j == m) { dp[i][j] = 2;continue; }
if (i == n) dp[i][j] = dp[i][j + 1];
else if (j == m) dp[i][j] = dp[i + 1][j];///无路可走的两种直接传递
else if ((i + j) & 1) dp[i][j] = dp[i + 1][j] & dp[i][j + 1];///可以通过坐标之和奇偶性得知是谁的回合,因为只能往下或者往右走,每轮都只会加一
else dp[i][j] = dp[i + 1][j] | dp[i][j + 1];///每种局面有0则B一定走0,有1则A一定走1
}
}
if (dp[1][1] & 1) cout << "yes" << ' ';
else cout << "no" << ' ';
if (dp[1][1] & 2) cout << "yes" << ' ';
else cout << "no" << ' ';
if (dp[1][1] & 4) cout << "yes" << ' ';
else cout << "no" << ' ';
cout << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章