做题记录 3
Underground Lab
来源:CF780E, 2200
题目是保证有解的,所以对于任意的一棵树来说也一定有解,那就不妨思考树的情况.
这道题最苛刻的情况就是 $\mathrm{K=1}$, 即意味着需要遍历完整颗树且中间不能断开.
很自然想到构建欧拉序,不过长度是 $\mathrm{O(3n)}$ 的,那就魔改一下:每次便利完儿子后不再将 $\mathrm{x}$ 加入,这样就好了.
#include <bits/stdc++.h>
#define N 400009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct UFS {
int p[N];
void init() {
for(int i = 0; i < N ; ++ i) p[i] = i;
}
int find(int x) {
return p[x] == x ? x : p[x] = find(p[x]);
}
int merge(int x, int y) {
x = find(x), y = find(y);
if(x == y) return 0;
p[x] = y;
return 1;
}
}T;
int n, m, K, bu[N], scc;
vector<int>G[N];
void dfs(int x, int ff) {
bu[++ scc] = x;
for(int i = 0; i < G[x].size() ; ++ i) {
int v = G[x][i];
if(v == ff) continue;
dfs(v, x);
bu[++ scc] = x;
}
}
int main() {
/// setIO("input");
scanf("%d%d%d", &n, &m, &K);
T.init();
for(int i = 1; i <= m ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
if(T.merge(x, y)) {
G[x].pb(y);
G[y].pb(x);
}
}
dfs(1, 0);
int num = (2 * n + K - 1) / K;
int cur = 1;
for(int i = 1; i <= K ; ++ i) {
if(cur > scc) {
printf("%d %d\n", 1, 1);
}
else {
int l = cur, r = min(scc, cur + num - 1);
printf("%d ", r - l + 1);
for(int j = l; j <= r; ++ j) {
printf("%d ", bu[j]);
}
printf("\n");
cur = r + 1;
}
}
return 0;
}
Send Boxes to Alice (Hard Version)
来源:CF1254B2, 2100
$K$ 一定是综合的因数,所以不妨考虑枚举这个 $\mathrm{K}$.
然后显然 $\mathrm{K}$ 是质因数的情况是最好的,因为会更稠密,更容易满足.
对于一个 $\mathrm{K}$, 令所有数字对 $\mathrm{K}$ 取模,表示需要补齐的部分.
假设前 $\mathrm{i-1}$ 个数字都补好,就差 $\mathrm{i}$, 那么 $\mathrm{i}$ 差的就是前 $\mathrm{i}$ 个数的和模 $\mathrm{K}$.
这个用 $\mathrm{i+1}$ 来贡献的话就是 $\mathrm{i}$ 移向 $\mathrm{i+1}$ 或 $\mathrm{i+1}$ 移向 $i$, 但 $\mathrm{i+1}$ 不会变.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 1000009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll b[N], a[N], sum, ans;
int n ;
vector<ll>g;
void calc(ll K) {
ll det = 0, fin = 0;
for(int i = 1; i <= n ; ++ i) {
b[i] = a[i] % K;
(det += b[i]) %= K;
// printf("%lld %lld\n", b[i], det);
fin += min(det, K - det);
}
ans = min(ans, fin);
}
int main() {
// setIO("input");
scanf("%d",&n);
for(int i = 1; i <= n ; ++ i) {
scanf("%lld", &a[i]);
sum += a[i];
}
ans = 1005193768297041258ll;
for(ll i = 2; i * i <= sum; ++ i) {
if(sum % i == 0) {
g.pb(i);
while(sum % i == 0) sum /= i;
}
}
if(sum > 1) g.pb(sum);
for(int i = 0; i < g.size() ; ++ i) {
calc(g[i]);
}
printf("%lld", ans == 1005193768297041258ll ? -1 : ans);
return 0;
}
Bitwise Queries (Hard Version)
来源:CF1451E2, 2300
交互题.
显然可以用 $\mathrm{n-1}$ 次操作来求出每个数与第一个数字的异或.
然后这道题有一个性质非常有用,就是数字范围在 $\mathrm{[0, n-1]}$ 之间.
利用这个性质,发现要么所有数字都连续,要么会出现相等的情况.
然后相等的情况就把等的找出来然后判断异或对应的 0 和 1 对应真实的值是什么.
如果出现连续的情况就找到连续的 $\mathrm{x,x+1}$ 然后可以判断除了 0 位以外的所有.
判断 0 位就让其中 0 位为 0 的和 1 去与一下,看看是什么.
第一种操作为 $\mathrm{n}$, 第二种为 $\mathrm{n+1}$, 故符合答案要求.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 500009
#define pb push_back
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>g[N], h[N];
int val[N];
int n , a[N], Lg[N], mx, vis[N], pos[N];
int AND(int x, int y) {
printf("AND %d %d\n", x, y);
printf("\n");
fflush(stdout);
int p;
scanf("%d", &p);
return p;
}
int XOR(int x, int y) {
printf("XOR %d %d\n", x, y);
printf("\n");
fflush(stdout);
int p;
scanf("%d", &p);
return p;
}
void init() {
// Lg[1] = 0;
for(int i = 0; i <= 17; ++ i) {
Lg[1 << i] = i;
}
}
// val[j]: 第 j 位为 1 时对应的结果.
void s1() {
// 这个是存在相同的情况.
for(int i = 0; i < N ; ++ i) {
if(vis[i] > 1) {
// 个数要大于 1.
int x = g[i][0], y = g[i][1];
int c = AND(x, y);
for(int j = 0; j <= mx; ++ j) {
int v = 0;
if(c & (1 << j)) v = 1;
val[j] = ((a[x] >> j) & 1) ? v : (1 ^ v);
}
break;
}
}
}
void s2() {
for(int i = 1; i <= n ; ++ i) {
int p = (a[i] & 1) ? a[i] - 1: a[i];
h[p].pb(i);
}
for(int i = 0; i < N ; ++ i) {
if(h[i].size() > 1) {
// 有两个相邻的.
int x = h[i][0], y = h[i][1];
if(x == 1 || y == 1) continue;
int c = AND(x, y);
for(int j = 1; j <= mx ; ++ j) {
// 2 ^ i 位的情况.
int v = 0;
if(c & (1 << j)) v = 1;
val[j] = ((a[x] >> j) & 1) ? v : (1 ^ v);
}
// 看看 val[0] 行不行.
if(a[h[i][0]] & 1) {
// a[h[i][1]] 为 0.
int det = (AND(1, h[i][1]) & 1);
val[0] = (det ^ 1);
}
else {
int det = (AND(1, h[i][0]) & 1);
val[0] = (det ^ 1);
}
break;
}
}
}
int main() {
// setIO("input");
scanf("%d",&n);
init();
++ vis[0];
g[0].pb(1);
for(int i = 2; i <= n ; ++ i) {
a[i] = XOR(1, i);
vis[a[i]] ++ ;
g[a[i]].pb(i);
}
int fl = 0;
for(int i = 0; i < N ; ++ i) {
if(vis[i] > 1) {
fl = 1;
break;
}
}
mx = Lg[n] - 1;
if(fl) {
s1();
}
else {
s2();
}
printf("! ");
for(int i = 1; i <= n ; ++ i) {
int o = 0;
for(int j = 0; j <= mx ; ++ j) {
int cur = 0;
if(a[i] & (1 << j)) {
cur = 1;
}
if(cur) o += val[j] << j;
else o += (val[j] ^ 1) << j;
}
printf("%d ", o);
}
fflush(stdout);
return 0;
}
The LCMs Must be Large
来源:CF1166E, 2100
看到题面后就知道这道题肯定有什么结论了.
然后这道题的结论就是若所给的 $\mathrm{m}$ 个集合存在两个集合无交集就不合法.
这个是显而易见的,然后若不出现上面的情况就一定合法,证明过程还没研究.
#include <cstdio>
#include <bitset>
#include <cstring>
#include <algorithm>
#define N 10009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int m, n;
bitset<N>bi[51];
int main() {
// setIO("input");
scanf("%d%d", &m, &n);
for(int i = 1; i <= m ; ++ i) {
int s, x;
scanf("%d", &s);
for(int j = 1; j <= s; ++ j) {
scanf("%d", &x);
bi[i].set(x);
}
for(int j = 1; j < i ; ++ j) {
if(!(bi[i] & bi[j]).count()) {
printf("impossible");
return 0;
}
}
}
printf("possible");
return 0;
}
Tree Constructing
来源:CF1003E, 2100
这道题的构造思路就是把最长链拿出来,然后其它点往上挂.
可以预处理每个点能挂的最大深度,能挂的最大点数,然后 $\mathrm{dfs}$ 输出即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 400009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, d, K, cnt;
ll f[N];
void dfs(int cur, int ff, int de, int mx) {
if(cur <= n) printf("%d %d\n", cur, ff);
if(cnt == n) return ;
if(de == mx) return ;
for(int i = 1; i < K ; ++ i) {
if(cnt >= n) exit(0);
dfs(++ cnt, cur, de + 1, mx);
}
}
int main() {
// setIO("input");
scanf("%d%d%d", &n, &d, &K);
f[1] = 1, ++ d;
if(d > n) {
printf("NO");
return 0;
}
if(K == 1) {
if(n == 1) {
if(d == 0) printf("YES\n");
else printf("NO\n");
}
else if(n == 2) {
if(d == 2) { printf("YES\n"); printf("%d %d\n", 1, 2); }
else printf("NO\n");
}
else {
printf("NO\n");
}
return 0;
}
ll prev = 1;
for(int i = 2; i < N ; ++ i) {
f[i] = f[i - 1] + 1ll * prev * (K - 1);
if(f[i] > 1ll * n) {
for(int j = i; j < N ; ++ j) f[j] = n + 1;
break;
}
prev = 1ll * prev * (K - 1);
}
int rem = n - d, lst = 0;
for(int i = 2; i < d; ++ i) {
int dep = min(i - 1, d - i);
if(1ll * f[dep] * (K - 2) >= 1ll * rem) {
lst = i, rem = 0;
break;
}
else {
rem -= f[dep] * (K - 2);
}
}
if(rem) {
printf("NO");
}
else {
printf("YES\n");
for(int i = 2; i <= d; ++ i) {
printf("%d %d\n", i - 1, i);
}
cnt = d;
for(int i = 2; i < lst; ++ i) {
int dep = min(i - 1, d - i);
// 这里都是满的.
for(int j = 1; j <= K - 2; ++ j) {
dfs(++ cnt, i, 1, dep);
}
}
rem = n - cnt;
// 最后构造 lst 即可.
for(int j = 1; j <= K - 2; ++ j) {
int dep = min(lst - 1, d - lst);
if(rem == 0) break;
if(f[dep] <= rem) {
dfs(++ cnt, lst, 1, dep);
rem -= f[dep];
}
else {
// f[dep] > rem.
dfs(++ cnt, lst, 1, dep);
}
}
}
return 0;
}
Minimax
来源:CF1530E, 2100
构造题.
对于要求答案最小化的构造题,先考虑答案可能的取值情况.
1. 所有字符相等,直接输出
2. 存在一个字符使得该字符出现一次,答案为 0.
3. 其余情况答案为 1.
前 2 种情况好处理,主要看第三种.
第三种情况的话枚举一下开头,即 aa 或 ab.
#include <cstdio>
#include <vector>
#include <cstring>
#include <set>
#include <map>
#include <algorithm>
#define N 100009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
char str[N];
int n, c[30], num[N];
vector<int>g;
void print() {
for(int i = 0; i < 26; ++ i) {
for(int j = 1; j <= c[i]; ++ j) printf("%c", i + 'a');
}
printf("\n");
}
int check(int x, int y) {
// 前两个字符是一样的.
if(c[g[x]] < 2) {
return 0;
}
int rem = c[g[x]] - 2;
if(n - c[g[x]] >= rem) return 1;
else return 0;
}
void sol(int x, int y) {
if(x == y) {
// 全为第一个.
int cnt = 0;
for(int i = 1; i < g.size() ; ++ i) {
for(int j = 1; j <= c[g[i]]; ++ j) num[++ cnt] = g[i];
}
printf("%c%c", g[0] + 'a', g[0] + 'a');
c[g[0]] -= 2;
int d = 1;
for(int i = 3; i <= n ; ++ i) {
if((i & 1)) {
printf("%c", 'a' + num[d]), ++ d;
}
else {
if(c[g[0]]) printf("%c", 'a' + g[0]), -- c[g[0]];
else printf("%c", 'a' + num[d]) , ++ d;
}
}
printf("\n");
}
else {
// x != y, abbbbbbbbbbbbbaaaaa
// abaaaaaaaaaaaaaaaaaaaaacbbbbbbbbbbb
if(g.size() == 2) {
printf("%c%c", g[0] + 'a', g[1] + 'a');
c[g[0]] -= 1, c[g[1]] -= 1;
for(int i = 1; i <= c[g[1]]; ++ i) printf("%c", 'a' + g[1]);
for(int i = 1; i <= c[g[0]]; ++ i) printf("%c", 'a' + g[0]);
}
else {
printf("%c%c", g[0] + 'a', g[1] + 'a');
c[g[0]] -= 1, c[g[1]] -= 1;
for(int i = 1; i <= c[g[0]]; ++ i) printf("%c", 'a' + g[0]);
printf("%c", g[2]+ 'a'), --c[g[2]];
for(int i = 1; i < g.size() ; ++ i) {
for(int j = 1; j <= c[g[i]]; ++ j) printf("%c", 'a' + g[i]);
}
}
printf("\n");
}
}
void cal2() {
// in this case, length of border = 1.
if(check(0, 0)) sol(0, 0);
else sol(0, 1);
}
// border = 1 的情况.
void pt() {
int mn = 0;
for(int i = 0; i < 26; ++ i) if(c[i] == 1) { mn = i; break; }
printf("%c", mn + 'a');
for(int i = 0; i < 26; ++ i) if(i != mn) {
for(int j = 1; j <= c[i] ; ++ j) printf("%c", i + 'a');
}
printf("\n");
}
void solve() {
scanf("%s", str + 1);
n = strlen(str + 1);
for(int i = 1; i <= n; ++ i) {
int cur = str[i] - 'a';
if(!c[cur]) g.pb(cur);
++c[cur];
}
sort(g.begin(), g.end());
if(g.size() == 1 || g.size() == n)
print();
else {
// 还有 border = 0 的情况:
int mn = -1;
for(int i = 0; i < 26; ++ i) if(c[i] == 1) {mn = i; break; }
if(mn != -1)
pt();
else cal2();
}
memset(c, 0, sizeof(c)), g.clear();
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T--) solve();
return 0;
}
Permutation Shift
来源:CF1553E, 2100
给定一个排列,可以随意交换两个数,问最少多少次变成 1 到 n.
这个问题可以将数字和位置连边,然后判断有几个环,答案就是 n - 环数.
这道题直观上肯定先去进行枚举位移,然后判断是否合法.
然后题中交换次数给了一个奇怪的限定,退一下式子发现答案要满足正确位置大于等于 n/3.
而又有所有正确位置之和为 n, 故最多只有 3 种 k 值.
最后暴力枚举,然后连边跑 dfs 判断即可.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 300009
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
vector<int>G[N], an;
int n, m, p[N], vis[N], cnt[N], a[N];
void dfs(int x) {
vis[x] = 1;
for(int i = 0; i < G[x].size() ; ++ i) {
if(!vis[G[x][i]]) dfs(G[x][i]);
}
}
void solve() {
scanf("%d%d", &n, &m);
for(int i = 0; i <= n ; ++ i) cnt[i] = 0;
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &p[i]);
if(p[i] >= i) cnt[p[i] - i] ++ ;
else {
cnt[n - i + p[i]] ++ ;
}
}
an.clear();
for(int i = 0; i < n ; ++ i) {
if(cnt[i] < n - 2 * m) continue;
// printf("%d %d\n", i, cnt[i]);
for(int j = 1; j <= n ; ++ j) {
int nex = ((j - 1) + i) % n + 1;
a[nex] = p[j];
}
for(int j = 1; j <= n ; ++ j) {
if(a[j] != j) G[j].pb(a[j]);
}
int c = 0;
for(int j = 1; j <= n ; ++ j) {
if(!vis[j]) ++ c, dfs(j);
}
// printf("%d\n", c);
for(int j = 1; j <= n ; ++ j) G[j].clear(), vis[j] = 0;
if(n - c <= m) an.pb((n - i) % n);
}
printf("%d ", an.size());
sort(an.begin(), an.end());
for(int i = 0; i < an.size() ; ++ i) printf("%d ", an[i]);
printf("\n");
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T-- ) solve();
return 0;
}
Errich-Tac-Toe (Hard Version)
来源:CF1450C2, 2300
构造题.
在简单版本中,可以直接按 3 的余数进行棋盘染色,然后每次只改变一类点.
在本题中,可以强制令两类点都改成 $X$ 与 $O$.
利用抽屉原理,有 3 种染色方案且和为 $\mathrm{K}$, 所以最小的方案肯定小于 K/3.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 305
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n ;
char str[N][N];
void print(int x, int y, int z) {
for(int i = 1; i <= n ; ++ i) {
for(int j = 1; j <= n ; ++ j) {
if(str[i][j] == '.') {
printf("%c", str[i][j]);
continue;
}
int d = (i + j) % 3;
if(d == 0) {
if(x == -1) printf("%c", str[i][j]);
else if(x == 0) printf("O");
else printf("X");
}
if(d == 1) {
if(y == -1) printf("%c", str[i][j]);
else if(y == 0) printf("O");
else printf("X");
}
if(d == 2) {
if(z == -1) printf("%c", str[i][j]);
else if(z == 0) printf("O");
else printf("X");
}
}
printf("\n");
}
}
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) {
scanf("%s", str[i] + 1);
}
int c[4][2], tot = 0;
memset(c, 0, sizeof(c));
for(int i = 1; i <= n ; ++ i) {
for(int j = 1; j <= n ; ++ j) {
int d = (i + j) % 3;
if(str[i][j] == '.') continue;
++ tot;
if(str[i][j] == 'X') c[d][0] ++ ;
else c[d][1] ++ ;
}
}
if(c[0][0] + c[1][1] <= tot / 3) print(0, 1, -1);
else if(c[1][0] + c[2][1] <= tot / 3) print(-1, 0, 1);
else print(1, -1, 0);
}
int main() {
// setIO("input");
int T;
scanf("%d", &T);
while(T--) solve();
return 0;
}
Trains and Statistic
来源:CF675E, 2300
贪心题.
令 $\mathrm{sum[i]}$ 表示以 $\mathrm{i}$ 为起点的答案和.
那么显然只需跳一步的就是 $\mathrm{[i, a[i]]}$ 内的.
然后如果是在 $\mathrm{a[i]}$ 之外,肯定要找区间内跳的最远的.
然后直接转移一下即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int a[N], n, Lg[N], mi[N][20];
ll sum[N];
void init() {
Lg[1] = 0;
for(int i = 2; i < N ; ++ i) Lg[i] = Lg[i >> 1] + 1;
for(int i = 1; i <= n ; ++ i) mi[i][0] = i;
for(int i = 1; i < 19 ; ++ i) {
for(int j = 1; j + (1 << i) - 1 <= n ; ++ j) {
if(a[mi[j][i - 1]] > a[mi[j + (1 << (i - 1))][i - 1]])
mi[j][i] = mi[j][i - 1];
else mi[j][i] = mi[j + (1 << (i - 1))][i - 1];
}
}
}
int query(int l, int r) {
int p = Lg[r - l + 1];
if(a[mi[l][p]] > a[mi[r - (1 << p) + 1][p]])
return mi[l][p];
else return mi[r - (1 << p) + 1][p];
}
int main() {
// setIO("input");
scanf("%d",&n);
for(int i = 1; i < n ; ++ i) {
scanf("%d", &a[i]);
}
init();
sum[n] = 0;
for(int i = n - 1; i >= 1; -- i) {
int l = i + 1, r = a[i];
if(a[i] == n) {
sum[i] = n - i;
}
else {
int id = query(l, r);
sum[i] = sum[id] + id + n - i - a[i];
}
// printf("%d %lld\n", i, sum[i]);
}
ll ans = 0;
for(int i = 1; i <= n ; ++ i) ans += sum[i];
printf("%lld", ans);
return 0;
}
Magic Stones
来源:CF1110E, 2200
需要手玩性质.
显然,合法的必要条件是首末两个数字必须对应相等.
然后我们需要寻求变换过程中的不变量,看看两个序列是否有给定量可以判定相等.
然后发现变换之后差分数组等于说是左右交换,所以只需判断一下两个序列差分数组是否排完序后相等即可.
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N 1000009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, c[N], t[N];
void solve() {
scanf("%d", &n);
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &c[i]);
}
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &t[i]);
}
if(c[1] != t[1] || c[n] != t[n]) {
printf("No\n");
}
else {
for(int i = n; i >= 2; -- i) {
c[i] -= c[i - 1];
t[i] -= t[i - 1];
}
sort(c + 2, c + 1 + n);
sort(t + 2, t + 1 + n);
for(int i = 2; i <= n ; ++ i) {
if(c[i] != t[i]) {
printf("No\n");
return ;
}
}
printf("Yes\n");
}
}
int main() {
// setIO("input");
int T = 1;
// scanf("%d", &T);
while(T -- ) solve();
return 0;
}
Placing Rooks
来源:CF1342E, 2300
可以将问题转化为:在区间 $\mathrm{[1, n-k]}$ 中选择 $\mathrm{n}$ 个数且 $1$ 至 $\mathrm{n-k}$ 都被选到.
然后利用容斥原理,枚举交集为 $\mathrm{i}$ 个数空着的情况,最后加和一下即可.
注意特判无解和 $\mathrm{k=0}$ 的情况.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200009
#define ll long long
#define mod 998244353
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, k;
ll K;
int fac[N], inv[N];
int qpow(int x, int y) {
int tmp = 1;
for(; y; y >>= 1, x = (ll)x * x % mod)
if(y & 1) tmp = (ll)tmp * x % mod;
return tmp;
}
int get_inv(int x) {
return qpow(x, mod - 2);
}
void init() {
fac[0] = inv[0] = 1;
for(int i = 1; i < N ; ++ i) {
fac[i] = (ll)fac[i - 1] * i % mod;
}
inv[N - 1] = get_inv(fac[N - 1]);
for(int i = N - 2; i >= 1; -- i) {
inv[i] = (ll)inv[i + 1] * (i + 1) % mod;
}
}
int C(int x, int y) {
return (ll)fac[x] * inv[y] % mod * inv[x - y] % mod;
}
// h 个空位.
int F(int h) {
return (ll)C(n - k, h) * qpow(n - k - h, n) % mod;
}
void solve() {
k = K;
int ans = 0;
for(int i = 0; i <= n - k; ++ i) {
int d = (i & 1) ? (mod - 1) : 1;
(ans += (ll)C(n, k) * d % mod * F(i) % mod) %= mod;
}
// printf("%d\n", ans);
printf("%d", (ll)((ll)ans * 2 % mod) % mod);
}
int main() {
// setIO("input");
init();
scanf("%d%lld", &n, &K);
if(K == 0) {
printf("%d", fac[n]);
}
else if(K > n) printf("0");
else {
solve();
}
return 0;
}
MEX Sequences
来源:CF1073E
构造一下,发现合法的情况有 3 种:
1. 全为 1
2. 一个逐次 +1 的序列.
3. 一个前半部分逐次加一,后半部分相差为 2 的波动序列.
针对 3 种情况 $\mathrm{DP}$ 弄一下即可.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 500009
#define ll long long
#define mod 998244353
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int qpow(int x, int y) {
int tmp = 1;
for(; y; y >>= 1, x = (ll)x * x % mod) {
if(y & 1) tmp = (ll)tmp * x % mod;
}
return tmp;
}
int a[N], pw[N], f[N], cnt[N], g[N][2], suf[N][2], n ;
void init() {
pw[0] = 1;
for(int i = 1; i < N ; ++ i) {
pw[i] = (ll)pw[i - 1] * 2 % mod;
}
}
void solve() {
scanf("%d", &n);
int c1 = 0;
for(int i = 1; i <= n ; ++ i) {
scanf("%d", &a[i]);
if(a[i] == 1) ++ c1 ;
}
// g[i][0]: i and i - 2
// g[i][1]: i and i + 2
for(int i = n ; i >= 1; -- i) {
// calc g[a[i]][0];
int c0 = (ll)(suf[a[i]][0] + 1) % mod;
if(a[i] - 2 >= 0) (c0 += suf[a[i] - 2][1]) %= mod;
int c1 = (ll)(suf[a[i]][1] + 1) % mod;
if(a[i] + 2 <= n) (c1 += suf[a[i] + 2][0]) %= mod;
g[i][0] = c0, g[i][1] = c1;
(suf[a[i]][0] += c0) %= mod;
(suf[a[i]][1] += c1) %= mod;
}
int ans = 0;
for(int i = 1; i <= n ; ++ i) {
if(a[i] == 0) {
++ cnt[0];
f[0] = (ll)(pw[cnt[0]] - 1 + mod) % mod;
}
else {
++ cnt[a[i]];
f[a[i]] = (ll)f[a[i]] * 2 % mod;
(f[a[i]] += f[a[i] - 1]) %= mod;
}
if(a[i] - 2 >= 0) {
(ans += (ll)f[a[i] - 2] * g[i][0] % mod) %= mod;
}
}
(ans += (ll)(pw[c1] - 1 + mod) % mod) %= mod;
for(int i = 0; i <= n ; ++ i) {
(ans += f[i]) %= mod;
}
for(int i = 0; i <= n ; ++ i) {
f[i] = g[i][0] = g[i][1] = cnt[i] = suf[i][0] = suf[i][1] = 0;
}
printf("%d\n", ans);
}
int main() {
// setIO("input");
int T;
init();
scanf("%d", &T);
while(T--) solve();
return 0;
}
Segment Sum
来源:CF1073E, 2300
数位 DP.
可以二进制将每种数字选不选压缩起来,然后跑两遍数位 DP.
第一遍记录方案数,第二遍统计答案.
这里要注意要把第一遍所维护的 $\mathrm{f}$ 数组所有情况都要枚举到,因为第二种情况要用.
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20
#define ll long long
#define mod 998244353
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int lowbit(int x) {
return x & (-x);
}
int cnt[1 << 12], a[N], K, len, f[N][1 << 12][2][2], g[N][1 << 12][2][2], pw[N];
// DP 状态: f[cur][sta][is][fl];
void init() {
cnt[0] = 0;
for(int i = 1; i < (1 << 12) ; ++ i) cnt[i] = cnt[i - lowbit(i)] + 1;
pw[0] = 1;
for(int i = 1; i <= 18 ; ++ i) pw[i] = (ll)pw[i - 1] * 10 % mod;
}
int dfs(int cur, int sta, int is, int fl) {
if(cur > len) {
return f[cur][sta][is][fl] = (!is);
}
if(f[cur][sta][is][fl] != -1) return f[cur][sta][is][fl];
int d = fl ? a[cur] : 9;
int an = 0;
// printf("%d %d\n", cur, d);
for(int i = 0; i <= d; ++ i) {
if(is) {
// 前面全是 0.
if(i == 0) {
an += dfs(cur + 1, sta, is, fl && (i == d));
if(an >= mod) an -= mod;
}
else {
if(cnt[sta | (1 << i)] > K)
continue;
an += dfs(cur + 1, sta | (1 << i), 0, fl && (i == d));
if(an >= mod) an -= mod;
}
}
else {
if(cnt[sta | (1 << i)] > K) continue;
an += dfs(cur + 1, sta | (1 << i), 0, fl && (i == d));
if(an >= mod) an -= mod;
}
}
return f[cur][sta][is][fl] = an;
}
int cal(int cur, int sta, int is, int fl) {
//printf("%d\n", sta);
if(cur > len) {
return 0;
}
if(g[cur][sta][is][fl] != -1)
return g[cur][sta][is][fl];
int d = fl ? a[cur] : 9;
int an = 0;
g[cur][sta][is][fl] = 0;
for(int i = 0; i <= d; ++ i) {
if(is) {
// 前面全是 0.
if(i == 0) {
an += cal(cur + 1, sta, is, fl && (i == d));
if(an >= mod) an -= mod;
}
else {
if(cnt[sta | (1 << i)] > K)
continue;
an += (ll)(cal(cur + 1, sta | (1 << i), 0, fl && (i == d)) + (ll)i * pw[len - cur] % mod * f[cur + 1][sta | (1 << i)][0][fl && (i == d)] % mod) % mod;
an %= mod;
}
}
else {
if(cnt[sta | (1 << i)] > K) continue;
an += (ll)((ll)pw[len - cur] * i % mod * f[cur + 1][sta | (1 << i)][0][fl && (i == d)] % mod + cal(cur + 1, sta | (1 << i), 0, fl && (i == d))) % mod;
an %= mod;
}
}
return g[cur][sta][is][fl] = an;
}
int solve(ll X) {
memset(f, -1, sizeof(f));
memset(g, -1, sizeof(g));
len = 0;
while(X) {
a[ ++ len ] = X % 10;
X /= 10;
}
for(int i = 1; i <= len / 2; ++ i) swap(a[i], a[len - i + 1]);
dfs(1, 0, 1, 1);
return cal(1, 0, 1, 1);
}
int main() {
// setIO("input");
init();
ll L, R;
scanf("%lld%lld%d", &L, &R, &K);
printf("%d\n", (ll)(solve(R) - solve(L - 1) + mod) % mod);
return 0;
}
Mike and Frog
来源:CF547A, 2200
这道题没有思维难度,但是有很多情况需要特殊讨论一下.
一个数字的步数表述成 $\mathrm{y=kx+b}$ 的形式.
$\mathrm{b}$ 是从初始位置走到该数字的步数,$\mathrm{k}$ 是模意义下的循环节.
这里要分几种情况讨论:
1. 两个数都具有 $\mathrm{k,b}$.
2. 两个数都没有 $\mathrm{k}$.
3. 其中一个数字有 $\mathrm{k}$.
第一种情况用 $\mathrm{exgcd}$ 求解,后面算一下即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define pb push_back
#define ll long long
#define N 1000009
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
struct Math {
int exgcd(ll a, ll b, ll &x, ll &y) {
if(!b) {
x = 1, y = 0;
return a;
}
int gc = exgcd(b, a % b, x, y);
ll t = x;
x = y, y = t - (a / b) * y;
return gc;
}
ll solve(int a, int b, int c) {
ll x, y;
int c0 = exgcd(a, b, x, y);
if(c % c0) return -1;
ll det = abs(b / c0);
ll an = (x * (c / c0) % det + det) % det;
ll y0 = (c - an * a) / b;
if(y0 >= 0) return an;
else {
// y0 < 0
ll d = abs(a / c0);
ll t = (abs(y0) + d - 1) / d;
return an + 1ll * t * det;
}
}
}T;
int m;
int h[2], x[2], y[2], a[2], vis[N];
struct node {
int k, b;
node(int k = 0, int b = 0):k(k), b(b){}
}A[2];
node solve(int id) {
memset(vis, -1, sizeof(vis));
int cur = h[id];
vis[cur] = 0;
while(1) {
int nex = (ll)(1ll * x[id] * cur % m + y[id]) % m;
if(vis[nex] != -1) break;
vis[nex] = vis[cur] + 1, cur = nex;
}
if(vis[a[id]] == -1) {
return node(-1, -1);
}
else {
int b = vis[a[id]], k = 0;
// b 为步数.
cur = a[id];
memset(vis, -1, sizeof(vis));
while(1) {
++ k;
int nex = (ll)((ll)cur * x[id] % m + y[id]) % m;
if(nex == a[id]) {
return node(k, b);
}
if(vis[nex] != -1) break;
vis[nex] = 1, cur = nex;
}
return node(-233, b);
}
}
int main() {
// setIO("input");
scanf("%d",&m);
for(int i = 0; i < 2; ++ i) {
scanf("%d%d%d%d",&h[i],&a[i],&x[i],&y[i]);
A[i] = solve(i);
if(A[i].k == -1) {
printf("-1");
return 0;
}
}
if(A[0].k == -233 || A[1].k == -233) {
if(A[0].k == -233 && A[1].k == -233) {
printf("%d", A[0].b == A[1].b ? A[0].b: -1);
return 0;
}
else if(A[0].k == -233) {
// x * A[1].k + A[1].b == A[0].b;
if(A[0].b - A[1].b >= 0 && (A[0].b - A[1].b) % A[1].k == 0) {
printf("%d", A[0].b);
}
else printf("-1");
return 0;
}
else {
if(A[1].b - A[0].b >= 0 && (A[1].b - A[0].b) % A[0].k == 0) {
printf("%d", A[1].b);
}
else printf("-1");
return 0;
}
}
// A[0] and A[1]
ll fin = T.solve(A[0].k, -A[1].k, A[1].b - A[0].b);
if(fin == -1) printf("-1");
else {
printf("%lld", fin * A[0].k + A[0].b);
}
return 0;
}
Quantifier Question
来源:CF1344C, 2600
可以将大小关系建立一张图,显然这张图不可以有环.
考虑一个点可以被设为任意的条件:
这个点的前驱和后继不能有小于这个点的编号.
然后每种这样的点都能被取到,于是跑一个拓扑排序记录一下入队序列更新即可.
#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 200009
#define pb push_back
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int n, m, deg[N], A[N], m1[N], m2[N], cn, cnt;
queue<int>q;
vector<int>G[N], e[N];
int main() {
// setIO("input");
scanf("%d%d", &n, &m);
for(int i = 1; i <= m ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].pb(y);
++ deg[y];
e[y].pb(x);
}
for(int i = 1; i <= n ; ++ i) {
if(!deg[i]) {
A[++ cnt] = i, q.push(i);
}
}
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = 0; i < G[u].size() ; ++ i) {
int v = G[u][i];
-- deg[v];
if(!deg[v]) {
A[++ cnt] = v, q.push(v);
}
}
}
for(int i = 1; i <= n ; ++ i) {
m1[i] = m2[i] = i;
}
if(cnt < n) {
printf("-1");
}
else {
for(int i = 1; i <= cnt ; ++ i) {
int u = A[i];
// 前驱,即 y -> u
for(int j = 0; j < e[u].size() ; ++ j) {
int y = e[u][j];
m1[u] = min(m1[u], m1[y]);
}
}
for(int i = cnt; i >= 1; -- i) {
int u = A[i];
for(int j = 0; j < G[u].size() ; ++ j) {
int y = G[u][j];
m2[u] = min(m2[u], m2[y]);
}
}
int sc = 0;
for(int i = 1; i <= n ; ++ i) {
if(min(m1[i], m2[i]) >= i) {
++ sc;
}
}
printf("%d\n", sc);
for(int i = 1; i <= n ; ++ i) {
if(min(m1[i], m2[i]) >= i) {
printf("A");
}
else {
printf("E");
}
}
}
return 0;
}
Phoenix and Earthquake
来源:CF1515F, 2600
显然问题有解的必要条件是所有点权和大于等于 $\mathrm{(n-1)x}$.
然后有一个结论就是这还是一个充分条件.
证明:随便拿出 $\mathrm{n}$ 个点的生成树,并随便拿出一个叶子.
若叶子的权值大于等于 $\mathrm{x}$, 将叶子和父亲合并后对剩余 $\mathrm{n-1}$ 个块更优.
若叶子的权值小于 $\mathrm{x}$, 则先将父亲所在的 $\mathrm{n-1}$ 个点的块合并,最后合并叶子.
用数学归纳法就证完了.
然后求解的时候就按照上述方法来做,开一个队列和栈.
权值大的边加入队列,权值小的边加入栈,然后边加边更新权值即可.
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <stack>
#include <algorithm>
#define N 300009
#define ll long long
#define pb push_back
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int fa[N];
int hd[N], to[ N << 1 ], nex[N << 1];
int n, m, val,from[N], vis[N], edges;
ll a[N];
queue<int>q;
stack<int>S;
void add(int u, int v) {
nex[++ edges] = hd[u];
hd[u] = edges, to[edges] = v;
}
vector<int>G[N];
void init() {
for(int i = 0; i < N ; ++ i) fa[i] = i;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
x = find(x);
y = find(y);
if(x != y) fa[x] = y;
}
void dfs(int x, int ff, int id) {
vis[x] = 1;
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(vis[v]) {
continue;
}
// printf("%d\n", v);
dfs(v, x, i);
}
if(x == 1) {
return ;
}
else {
if(a[x] >= val) {
q.push(id), a[ff] += a[x] - val;
}
else {
S.push(id);
}
}
}
int main() {
// setIO("input");
scanf("%d%d%d", &n, &m, &val);
ll sum = 0ll;
for(int i = 1; i <= n ; ++ i) {
scanf("%lld", &a[i]), sum += a[i];
}
edges = 1;
for(int i = 1; i <= m ; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y), add(y, x);
merge(x, y);
}
int cn = 0;
for(int i = 1; i <= n ; ++ i) {
if(find(i) == i) ++ cn;
}
if(cn > 1 || sum < 1ll * (n - 1) * val) {
printf("NO");
}
else {
printf("YES\n");
dfs(1, 0, 0);
while(!q.empty()) {
printf("%d\n", q.front() >> 1);
q.pop();
}
while(!S.empty()) {
printf("%d\n", S.top() >> 1);
S.pop();
}
}
return 0;
}
Digit Tree
来源:CF715C, 2800
路径问题考虑用点分治进行处理.
考虑当前的分治中心 $\mathrm{x}$, 对于两条路径 $\mathrm{d1,d2}$ 如何合并.
令 $\mathrm{d1,d2}$ 分别表示从下到上,从上到下的路径.
然后$\mathrm{d1}$ 和 $\mathrm{d2}$ 能合并的条件是 $\mathrm{d1=-d2 \times 10^{-dep2}}$.
有这个式子后开两个 $\mathrm{map}$ 分别进行统计即可.
这里注意这个 $\mathrm{m}$ 不一定是质数,所以需要用扩展欧几里得算法求逆元.
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
#define N 100009
#define ll long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
ll ans;
int n, mod, inv, hd[N], to[N << 1], nex[N << 1], val[N << 1], edges, sn, root;
int f[N], size[N], vis[N], dep[N], bu[N], base[N];
map<int, int>c1, c2;
ll exgcd(ll a, ll b, ll &x, ll &y) {
if(!b) {
x = 1, y = 0;
return a;
}
ll gc = exgcd(b, a % b, x, y);
ll t = x;
x = y, y = t - (a / b) * y;
return gc;
}
void init() {
ll x0, y0;
exgcd(10, mod, x0, y0);
x0 = (x0 % mod + mod) % mod;
inv = (int)x0;
}
void add(int u, int v, int c) {
nex[++edges] = hd[u];
hd[u] = edges, to[edges] = v, val[edges] = c;
}
void getroot(int x, int ff) {
size[x] = 1, f[x] = 0;
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(v == ff || vis[v]) continue;
getroot(v, x), size[x] += size[v];
f[x] = max(f[x], size[v]);
}
f[x] = max(f[x], sn - f[x]);
if(f[x] < f[root]) root = x;
}
// 第一波.
void calc(int x, int ff, int d1, int d2) {
dep[x] = dep[ff] + 1;
// 考虑 (d1, d2) 对前面的贡献.
ans += c2[d1];
ans += c1[(ll)(mod - d2) * bu[dep[x]] % mod];
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(v == ff || vis[v]) {
continue;
}
calc(v, x, (ll)(d1 + (ll)base[dep[x]] * val[i] % mod) % mod, (ll)((ll)d2 * 10 % mod + val[i]) % mod);
}
}
// 第二波.
void getdis(int x, int ff, int d1, int d2) {
dep[x] = dep[ff] + 1;
if(!d1) ++ ans;
if(!d2) ++ ans;
c1[d1] ++ ;
c2[(ll)(mod - d2) * bu[dep[x]] % mod] ++ ;
// c1 = d1
// c2 = -d2 * 10 ^ (-dep)
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(v == ff || vis[v]) {
continue;
}
getdis(v, x, (ll)(d1 + (ll)base[dep[x]] * val[i] % mod) % mod, (ll)((ll)d2 * 10 % mod + val[i]) % mod);
}
}
void dfs(int x) {
vis[x] = 1;
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(vis[v]) continue;
dep[x] = 0;
calc(v, x, val[i] % mod, val[i] % mod);
getdis(v, x, val[i] % mod, val[i] % mod);
}
// 当前计算完毕.
c1.clear();
c2.clear();
for(int i = hd[x]; i ; i = nex[i]) {
int v = to[i];
if(vis[v]) continue;
root = 0, sn = size[v], getroot(v, 0);
dfs(root);
}
}
int main() {
// setIO("input");
scanf("%d%d", &n, &mod);
init();
for(int i = 1; i < n ; ++ i) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
++ x, ++ y;
add(x, y, z), add(y, x, z);
}
bu[0] = 1, base[0] = 1;
for(int i = 1; i < N ; ++ i) {
bu[i] = (ll)bu[i - 1] * inv % mod;
base[i] = (ll)base[i - 1] * 10 % mod;
}
f[root = 0] = N, sn = n, getroot(1, 0);
dfs(root);
printf("%lld", ans);
return 0;
}

浙公网安备 33010602011771号