【CodeForces】Hello 2019 (Div. 1 + Div. 2) 题解

【比赛链接】

【题解链接】

【思路要点】

• 按照题意模拟。
• 时间复杂度 $O(1)$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
string x, y;
int main() {
cin >> x;
bool ans = false;
for (int i = 1; i <= 5; i++) {
cin >> y;
ans |= y[0] == x[0] || y[1] == x[1];
}
if (ans) puts("YES");
else puts("NO");
return 0;
}


**【B】**Petr and a Combination Lock

【思路要点】

• 枚举每一次旋转的方向。
• 时间复杂度 $O(2^N)$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, a[MAXN];
bool ans;
void work(int pos, int sum) {
if (pos == n + 1) ans |= sum % 360 == 0;
else {
work(pos + 1, sum + a[pos]);
work(pos + 1, sum - a[pos]);
}
}
int main() {
for (int i = 1; i <= n; i++)
work(1, 0);
if (ans) puts("YES");
else puts("NO");
return 0;
}


**【C】**Yuhao and a Parenthesis

【思路要点】

• 将一个括号序列的 $($ 看做 $+1$$)$ 看做 $-1$ ，我们可以用二元组 $(sum,Min)$ 来描述一个括号序列，其中 $sum$ 表示序列和， $Min$ 表示前缀最小值。
• $sum≤0$ ，要求 $Min=sum$ ，否则，要求 $Min=0$
• 满足上述条件的 $sum$ 之和为 $0$ 的括号序列可以配对，贪心即可。
• 时间复杂度 $O(\sum |s_i|)$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int cnt[MAXN], cmt[MAXN];
int main() {
for (int i = 1; i <= n; i++) {
string x; cin >> x;
int sum = 0, Min = 0;
for (auto y : x)
if (y == '(') sum++;
else sum--, chkmin(Min, sum);
if (sum <= 0) {
if (Min == sum) cnt[-sum]++;
} else {
if (Min == 0) cmt[sum]++;
}
}
int ans = cnt[0] / 2;
for (int i = 1; i <= 5e5; i++)
ans += min(cnt[i], cmt[i]);
writeln(ans);
return 0;
}


**【D】**Makoto and a Blackboard

【思路要点】

• 可以发现，最后各个质因子 $p$ 剩余指数为某一值 $x$ 的概率在不同质因子之间是相互独立的。
• $N$ 质因数分解，对各个质因数分别 $dp$ ，然后计算 $N$ 各因数出现的概率即可。
• 时间复杂度 $O(KLog^2N+\sqrt{N})$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
ll n; int k, num[MAXN];
int m, ans, val[MAXN], inv[MAXN];
int dp[MAXN][100], p[MAXN][100];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void getans(int pos, int sum, int pro) {
if (pos == m + 1) {
update(ans, 1ll * sum * pro % P);
return;
}
for (int i = 0; i <= num[pos]; i++) {
getans(pos + 1, sum, 1ll * pro * p[pos][i] % P);
sum = 1ll * sum * val[pos] % P;
}
}
int main() {
for (ll i = 2; i * i <= n; i++)
if (n % i == 0) {
int cnt = 0;
while (n % i == 0) n /= i, cnt++;
num[++m] = cnt;
val[m] = i;
}
if (n != 1) {
num[++m] = 1;
val[m] = n % P;
}
for (int i = 1; i <= 64; i++)
inv[i] = power(i, P - 2);
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= k; j++)
for (int l = 0; l <= num[i]; l++)
dp[j][l] = 0;
dp[0][num[i]] = 1;
for (int j = 1; j <= k; j++)
for (int l = 0; l <= num[i]; l++) {
for (int o = 0; o <= l; o++)
update(dp[j][o], 1ll * dp[j - 1][l] * inv[l + 1] % P);
}
for (int j = 0; j <= num[i]; j++)
p[i][j] = dp[k][j];
}
getans(1, 1, 1);
writeln(ans);
return 0;
}


**【E】**Egor and an RPG game

【思路要点】

• 考虑一类输入 $\{1,3,2,6,5,4,10,9,8,7,...\}$ ，此类输入在长度 $N$$\frac{k(k+1)}{2}$ 时可以构造出一组答案为 $k$ 的输入，于是，一个猜想是 $f(x)=max\{k\ |\ k\in \Z,\frac{k(k+1)}{2}≤x\}$
• 我们提出一个构造算法来证明上述猜想。
• 形式化地来说，我们需要证明对于 $N<\frac{k(k+1)}{2}$ ，存在一组 $k-1$ 的解。
• 求解输入序列的最长上升子序列，令其长度为 $x$
• $x≥k$ ，将该序列删除，递归解决该问题。
• 否则，根据 $Dilworth$ 定理，该序列可以被拆分为 $x<k$ 个下降子序列，并且我们可以在求解最长上升子序列时顺带解决将其拆分为 $x$ 个下降子序列的问题。
• 时间复杂度 $O(N\sqrt{N}LogN)$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, a[MAXN];
bool vis[MAXN];
vector <vector <int> > ans;
void solve(int k) {
static vector <int> low[MAXN];
int Max = 0; low[0].resize(1);
static int dp[MAXN], from[MAXN];
for (int i = 1; i <= n; i++) {
if (vis[a[i]]) continue;
int l = 0, r = Max + 1;
while (l < r) {
int mid = (l + r) / 2;
if (a[i] > low[mid].back()) l = mid + 1;
else r = mid;
}
dp[a[i]] = l, from[a[i]] = low[l - 1].back();
if (l > Max) {
Max = l;
low[Max].clear();
low[Max].push_back(a[i]);
} else low[l].push_back(a[i]);
}
if (Max == 0) return;
if (Max >= k) {
vector <int> res; res.clear();
int pos = 0;
for (int i = 1; i <= n; i++)
if (!vis[a[i]] && dp[a[i]] == Max) {
pos = a[i];
break;
}
while (pos) {
res.push_back(pos);
vis[pos] = true;
pos = from[pos];
}
reverse(res.begin(), res.end());
ans.push_back(res);
solve(k - 1);
} else {
for (int i = 1; i <= Max; i++)
ans.push_back(low[i]);
}
}
int main() {
while (T--) {
for (int i = 1; i <= n; i++) {
vis[i] = false;
}
ans.clear();
int k = 1;
while (k * (k + 1) / 2 <= n) k++;
solve(k);
writeln(ans.size());
for (auto x : ans) {
write(x.size());
for (auto y : x)
printf(" %d", y);
putchar('\n');
}
}
return 0;
}


**【F】**Alex and a TV Show

【思路要点】

• 如果我们用 $bitset$ 维护集合中的元素，那么操作 $1,2,4$ 均可以轻松地实现。
• 考虑操作 $3$ ，通过莫比乌斯反演，结果集合中存在数字 $x$ 当且仅当 $\sum_{i=1}^{\lfloor\frac{v}{x}\rfloor}\mu(i)*sum_A(ix)*sum_B(ix)=1\ (mod\ 2)$ ，其中 $sum_A(x),sum_B(x)$ 分别表示 $A,B$$x$ 的倍数的个数。
• 那么，如果我们用 $bitset$ 维护集合中的 $sum$ ，那么操作 $2,3$ 分别都可以用异或、按位与来实现，操作 $4$ 的询问可以通过预处理莫比乌斯函数对应的 $bitset$ 来实现。
• 时间复杂度 $O(\frac{qv}{w})$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXM = 7e3 + 5;
typedef long long ll;
typedef bitset <MAXM> info;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
info a[MAXN], b[MAXM], getans[MAXM];
int tot, prime[MAXM], f[MAXM], miu[MAXM];
void init(int n) {
miu[1] = 1;
for (int i = 2; i <= n; i++) {
if (f[i] == 0) {
prime[++tot] = f[i] = i;
miu[i] = -1;
}
for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
int tmp = prime[j] * i;
if (tmp > n) break;
f[tmp] = prime[j];
if (prime[j] == f[i]) miu[tmp] = 0;
else miu[tmp] = -miu[i];
}
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j += i) {
b[j][i] = 1;
if (miu[j / i]) getans[i][j] = 1;
}
}
}
int main() {
init(7000);
int n, m;
for (int i = 1; i <= m; i++) {
int opt, x, y, z;
if (opt == 1) a[x] = b[y];
if (opt == 2) {
a[x] = a[y] ^ a[z];
}
if (opt == 3) {
a[x] = a[y] & a[z];
}
if (opt == 4) putchar('0' + (a[x] & getans[y]).count() % 2);
}
return 0;
}


【思路要点】

• $x^k$ 写成 $\sum_{i=0}^{k}S(k,i)*i!*\binom{x}{i}$ 的形式。
• 因此，我们需要计算对于每一个点集对应的生成树，从中选出 $i\ (i=0,1,2,...,k)$ 条边标记的总方案数。
• 考虑在每一棵生成树深度最低的点处计算其贡献，用树形背包解决即可。
• 时间复杂度 $O(Nk)$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
const int MAXK = 205;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, k, fac[MAXK], s[MAXK][MAXK];
int dp[MAXN][MAXK], res[MAXK], size[MAXN];
vector <int> a[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void times(int *a, int &la, int *b, int lb) {
static int tmp[MAXK];
memset(tmp, 0, sizeof(tmp));
for (int i = 0; i <= la && i <= k; i++)
for (int j = 0; j <= lb && i + j <= k; j++)
update(tmp[i + j], 1ll * a[i] * b[j] % P);
memcpy(a, tmp, sizeof(tmp));
la += lb;
}
void work(int pos, int fa) {
size[pos] = 1, dp[pos][0] = 2;
for (auto x : a[pos])
if (x != fa) {
work(x, pos);
times(dp[pos], size[pos], dp[x], size[x]);
for (int i = 0; i <= k; i++)
update(res[i], P - dp[x][i]);
}
for (int i = 0; i <= k; i++)
update(res[i], dp[pos][i]);
for (int i = k; i >= 1; i--)
update(dp[pos][i], dp[pos][i - 1]);
update(dp[pos][1], P - 1);
}
void init(int n, int k) {
s[0][0] = fac[0] = 1;
for (int i = 1; i <= k; i++) {
fac[i] = 1ll * fac[i - 1] * i % P;
for (int j = 1; j <= i; j++)
s[i][j] = (s[i - 1][j - 1] + 1ll * s[i - 1][j] * j) % P;
}
}
int main() {
for (int i = 1; i <= n - 1; i++) {
a[x].push_back(y);
a[y].push_back(x);
}
init(n, k);
work(1, 0);
int ans = 0;
for (int i = 0; i <= k; i++)
update(ans, 1ll * s[k][i] * fac[i] % P * res[i] % P);
writeln(ans);
return 0;
}


**【H】**Mateusz and an Infinite Sequence

【思路要点】

• 考虑如何描述一个区间里的元素。
• 我们需要记录其长度 $(len)$ ，包含可行子串的个数 $(ans)$ ，其长度为 $i$ 的后缀是否能作为可行子串长度为 $i$ 的前缀（没有出现的元素当做 $0$$(suf_i\ (i=1,2,3,...,N-1))$ ，长度为 $N-i$ 的前缀是否能作为可行子串长度为 $N-i$ 的后缀（没有出现的元素当做 $0$$(pre_i\ (i=1,2,3,...,N-1))$
• 上述信息是可以合并的，并可以利用 $bitset$ 做到 $O(\frac{N}{w})$ 合并。
• 接下来的部分就是一个简单数位 $dp$ ，记 $dp_{i,j}$ 表示序列的前 $d^i$ 位加上 $j$ 后的信息，可以简单计算 $dp$ 值，并求解答案。
• 时间复杂度 $O(\frac{NdMLogM}{w})$

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e4 + 5;
const int MAXD = 25;
const int MAXM = 65;
const int MAXLOG = 64;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct info {
ll ans, len;
bitset <MAXN> pre, suf;
};
bitset <MAXN> all;
int n, d, m, gen[MAXD], b[MAXN];
int tot; ll len[MAXLOG]; info dp[MAXLOG][MAXM];
info operator + (const info &a, const info &b) {
if (a.len == 0) return b;
info res;
res.len = a.len + b.len;
res.ans = a.ans + b.ans;
res.pre = a.pre;
res.suf = b.suf;
if (a.len <= n - 2) res.pre &= (b.pre >> a.len) | (all << (n - 1 - a.len));
if (b.len <= n - 2) res.suf &= (a.suf << b.len) | (all >> (n - 1 - b.len));
if (a.len + b.len >= n) {
bitset <MAXN> tmp = a.suf & b.pre;
if (a.len <= n - 2) tmp &= all >> (n - 1 - a.len);
if (b.len <= n - 2) tmp &= all << (n - 1 - b.len);
res.ans += tmp.count();
}
return res;
}
ll work(ll x) {
info res;
res.ans = 0;
res.len = 0;
res.pre.reset();
res.suf.reset();
for (int i = tot; i >= 0; i--) {
for (int j = 1; j <= d; j++)
if (x >= len[i]) {
x -= len[i];
res = res + dp[i][(add + gen[j]) % m];
} else {
break;
}
}
return res.ans;
}
int main() {
for (int i = 1; i <= d; i++)
for (int i = 1; i <= n; i++)
for (int i = 1; i <= n - 1; i++)
all.set(i);
tot = 0, len[0] = 1;
for (int i = 0; i <= m - 1; i++) {
dp[0][i].len = 1;
if (n == 1) dp[0][i].ans = i <= b[1];
else {
for (int j = 1; j <= n - 1; j++) {
dp[0][i].pre[j] = i <= b[j + 1];
dp[0][i].suf[j] = i <= b[j];
}
}
}
while (len[tot] <= qr / d) {
tot++, len[tot] = len[tot - 1] * d;
for (int i = 0; i <= m - 1; i++)
for (int j = 1; j <= d; j++)
dp[tot][i] = dp[tot][i] + dp[tot - 1][(i + gen[j]) % m];
}
writeln(work(qr) - work(ql + n - 2));
return 0;
}


• 广告
• 抄袭
• 版权
• 政治
• 色情
• 无意义
• 其他

120