周作业 42
A - Pull Your Luck
题意
给定 \(n,x,p\),要求选择 \(f \in [1,p]\),使得 \(x\) 在依次增加 \(f,f-1,f-2,\cdots,1\) 之后为 \(n\) 的倍数,求是否求解。
时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define il inline
#define N 100005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
int n, x, p;
signed Main(){
n = rd(), x = rd(), p = rd();
for (int s = 1, i = 1; i <= min(p, n + n); i++, s = (s + i % n) % n) if (s == (n - x) % n) return puts("Yes");
puts("No");
return 0;
}
signed main(){
for (int T = rd(); T--;) Main();
return 0;
}
分析
注意到,当 \(f = 2n\) 时,\(x\) 会加回到 \(x\),因为 \(\frac{2n(2n+1)}{2} = n(2n+1)\) 为 \(n\) 的倍数。所以直接枚举 \(f\) 即可。
B - Shocking Arrangement
题意
有一个序列 \(a\),保证 \(\sum a_i = 0\)。现在你要重排 \(a\),使得满足下面式子。
求方案或报告无解。
时间复杂度 \(O(n\log n)\) 或 \(O(n)\)。
分析
首先注意到,我们应该让前缀和的最大最小值尽量接近 \(0\),此时是最优的。而无解当且仅当 \(a_i\) 全都是 \(0\)。
所以我们可以排序,然后选择取最小值或最大值,如果为负数去最大值,为正数取最小值。
当然,这题做法很多,只要意思接近,做法类似即可。当然也不用排序,只要分成正负数即可。
至于我的做法比较抽象,一样的是取最大最小值,如果放最大值后不合法就放最小值,放最小值不合法就放最大值,都不合法就是无解,都合法就随意。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define il inline
#define N 300005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
const int INF = 1e9;
int n, a[N], mx, mn, s, mxs, mns;
il bool check(int s){return abs(s - mxs) < mx - mn && abs(s - mns) < mx - mn;}
signed Main(){
n = rd(), mx = -INF, mn = INF;
multiset <int> S;
for (int i = 1, x; i <= n; i++) x = rd(), mx = max(mx, x), mn = min(mn, x), S.insert(x);
if (n == 1) return puts("No"), 0;
s = 0, mxs = 0, mns = 0;
for (int i = 1; i <= n; i++){
auto itl = S.begin(), itr = --S.end();
int fl = check(s + *itl), fr = check(s + *itr);
if (!fl && !fr) return puts("No"), 0;
if (fl) a[i] = *itl, S.erase(itl);
else a[i] = *itr, S.erase(itr);
s += a[i];
mxs = max(mxs, s), mns = min(mns, s);
}
puts("Yes");
for (int i = 1; i <= n; i++) printf ("%lld ", a[i]);
puts("");
return 0;
}
signed main(){
for (int T = rd(); T--;) Main();
return 0;
}
C - Climbing the Tree
题意
有一口井,深度为 \(h\)。一开始你并不知道 \(h\) 为多少。一直蜗牛有两个属性 \(a,b\),表示这只蜗牛会在白天向上爬 \(a\),晚上向下掉 \(b\),保证 \(a>b\)。
现在有 \(q\) 次操作,有两种。
1 a b n,表示有一只属性为 \(a,b\) 的蜗牛从井底出发,在第 \(n\) 天出井。你需要判断是否可能,若可能,则视为该信息正确,返回 \(1\)。若不可能,返回 \(0\)。2 a b,表示询问一直属性为 \(a,b\) 的蜗牛从井底出发要多少天才能出井,若答案不唯一,返回 \(-1\)。
时间复杂度 \(O(q)\)。
分析
不难发现一只蜗牛在第 \(n\) 天出井,当且仅当 \(h \in [(a-b)(n-2)+a+1,(a-b)(n-1)+a]\)。前者表示 \(n-1\) 天的最高高度 \(+1\),后者表示 \(n\) 天的最高高度。
我们直接记 \([L,R]\) 表示当且可以确定的 \(h\) 的范围。当给定新的信息时,将新的区间和 \([L,R]\) 比对,若没有交,说明信息是假的,否则就是真的。如果是真的信息,就更新 \([L,R]\) 为其交集。
查询的时候,我们分别计算 \(h = L\) 和 \(h = R\) 时的天数,若不同,就是 \(-1\),否则就是对应天数。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define il inline
#define N 100005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
const int INF = 1e18;
signed Main(){
int L = 1, R = INF;
for (int Q = rd(), op, a, b, n; Q--;){
op = rd(), a = rd(), b = rd();
if (op == 1){
n = rd();
int l = (n == 1 ? 1 : (a - b) * (n - 2) + a + 1), r = (a - b) * (n - 1) + a;
if (r < L || l > R) printf ("0 ");
else printf ("1 "), L = max(L, l), R = min(r, R);
}
else{
int hl = max((L - b - 1) / (a - b) + 1, 1ll), hr = max((R - b - 1) / (a - b) + 1, 1ll);
if (hl == hr) printf ("%lld ", hl);
else printf ("-1 ");
}
}
puts("");
return 0;
}
signed main(){
for (int T = rd(); T--;) Main();
return 0;
}
D - A Wide, Wide Graph
题意
给定一棵树,定义一张无向图 \(G_k\),生成方式为找到所有在树上距离不小于 \(k\) 的点对,并在 \(G_k\) 中连接。问对于 \(k \in [1,n]\),\(G_k\) 的连通块数量。
时间复杂度 \(O(n)\)。
分析
首先会发现,设直径长度为 \(d\),当 \(k > d\) 的时候 \(G_k\) 所有点都是孤立点。
当 \(k \le d\) 时,显然直径两个端点在同一连通块内,而对于一个点 \(i\),如果它到直径端点最远距离比 \(k\) 小,那么显然这个点就是孤立点,否则这个点与直径相连。
于是我们直接找出直径,然后差分一下即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define N 100005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
int n, d[2][N], s, t, ans[N];
vector <int> e[N];
void dfs(int u, int *d, int fa = 0){for (int v : e[u]) if (v != fa) d[v] = d[u] + 1, dfs(v, d, u);}
signed main(){
n = rd();
for (int i = 1, u, v; i < n; i++) u = rd(), v = rd(), e[u].push_back(v), e[v].push_back(u);
d[1][1] = 0, dfs(1, d[1]);
for (int i = 1; i <= n; i++) if (d[1][s] < d[1][i]) s = i;
d[0][s] = 0, dfs(s, d[0]);
for (int i = 1; i <= n; i++) if (d[0][t] < d[0][i]) t = i;
d[1][t] = 0, dfs(t, d[1]);
for (int i = 1; i <= n; i++) if (i != s) ans[max(d[0][i], d[1][i]) + 1]++;
ans[1] = 1;
for (int i = 2; i <= n; i++) ans[i] += ans[i - 1];
for (int i = 1; i <= n; i++) printf ("%d ", ans[i]);
return 0;
}
E - No Cost Too Great (Easy Version)
Easy Version 和 Hard Version 是字面意思,E 题真的简单,F 真的有点难。
题意
给定一个序列 \(a\),一次操作可以令一个 \(a_i\) 加 \(1\),问至少几次操作后 \(a\) 中至少有两个数不互质。
时间复杂度 \(O(n\log{V})\)。
分析
可以注意到,答案不超过 \(2\),最坏情况就是将两个奇数加 \(1\)。考虑计算答案为 \(0\) 和 \(1\) 的。
对于答案为 \(0\),就是计算是否有两个数不互质,可以预处理所有数拥有的质因数,然后用桶存一下即可。
对于答案为 \(1\),就是计算是否有一个数加 \(1\) 之后和别的数不互质,一样用桶即可。
但是我手比脑子快,没有筛质因数,直接筛的质数,时间复杂度会劣一些,但是也能过。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define N 200005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
int n, a[N], cnt[N];
vector <int> p[N];
signed Main(){
n = rd();
int flg = 0;
for (int i = 1; i <= n; i++){
a[i] = rd();
for (int j : p[a[i]]){
if (j != 1 && cnt[j]) flg = 1;
cnt[j]++;
}
}
for (int i = 1; i <= n; i++) rd();
if (flg){
for (int i = 1; i <= n; i++) for (int j : p[a[i]]) cnt[j]--;
return puts("0"), 0;
}
int flg1 = 0;
for (int i = 1; i <= n; i++) for (int j : p[a[i] + 1]) if (j != 1 && cnt[j]) flg1 = 1;
for (int i = 1; i <= n; i++) for (int j : p[a[i]]) cnt[j]--;
return puts(flg1 ? "1" : "2"), 0;
}
signed main(){
for (int i = 1; i <= 200001; i++){
for (int j = 1; j * j <= i; j++) if (i % j == 0){
p[i].push_back(j);
if (j * j != i) p[i].push_back(i / j);
}
sort (p[i].begin(), p[i].end());
}
for (int T = rd(); T--;) Main();
return 0;
}
F - No Cost Too Great (Hard Version)
题意
给定一个序列 \(a\),一次操作可以令一个 \(a_i\) 加 \(1\),要花费 \(b_i\) 的代价,问至少花费多少代价使得 \(a\) 中至少有两个数不互质。
时间复杂度 \(O(n\log{V})\)。
分析
同上的考虑,首先排除答案为 \(0\) 的,然后会发现,答案不超过 \(b_i\) 的最小值加次小值,这种情况是操作两个数。
但是这一次只操作一个数的话,不一定只加一次,因为会有下面的数据。
2
4 9
1 1000000000
这种情况显然令 \(a_1\) 加 \(2\) 最优。但是我们一样可以注意到一个点,就是操作不只 \(1\) 次的位置,\(b_i\) 一定是最小的。所以我们可以枚举质因数,然后计算要花费的代价。而只操作 \(1\) 次的话,可以直接计算。
那会不会有多个最小值呢?注意到如果不只一个最小值,那么最小值一定等于次小值,那么操作多次答案一定不优于操作两个数。于是我们就做完了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define il inline
#define N 400005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
int n, a[N], b[N], cnt[N], pr[N], tot, isp[N], mxp[N];
vector <int> p[N];
signed Main(){
n = rd();
int flg = 0;
for (int i = 1; i <= n; i++){
a[i] = rd();
for (int j : p[a[i]]){
if (cnt[j]) flg = 1;
cnt[j]++;
}
}
for (int i = 1; i <= n; i++) b[i] = rd();
if (flg){
for (int i = 1; i <= n; i++) for (int j : p[a[i]]) cnt[j]--;
return puts("0"), 0;
}
int ans = 2e9, mn = 2e9, cmn = 2e9;
for (int i = 1; i <= n; i++) if (b[i] <= mn) cmn = mn, mn = b[i]; else if (b[i] < cmn) cmn = b[i];
ans = mn + cmn;
for (int i = 1; i <= n; i++) for (int j : p[a[i] + 1]) if (cnt[j]) ans = min(ans, b[i]);
if (mn < cmn){
int pos = 0;
for (int i = 1; i <= n; i++) if (b[i] == mn) pos = i;
for (int i = 1; i <= n; i++) if (i != pos) for (int j : p[a[i]])
ans = min(ans, (j - a[pos] % j) % j * mn);
}
for (int i = 1; i <= n; i++) for (int j : p[a[i]]) cnt[j]--;
printf ("%lld\n", ans);
return 0;
}
signed main(){
for (int i = 2; i <= 400000; i++) if (!isp[i]){
pr[++tot] = i;
for (int j = i + i; j <= 400000; j += i) isp[j] = 1;
}
for (int i = 1; i <= 400000; i++){
int x = i;
for (int j = 1; j <= tot && pr[j] * pr[j] <= x; j++) if (x % pr[j] == 0){
p[i].push_back(pr[j]);
while (x % pr[j] == 0) x /= pr[j];
}
if (x > 1) p[i].push_back(x), mxp[i] = x;
}
for (int T = rd(); T--;) Main();
return 0;
}
G - Accommodation
题意
给定一个 \(n \times m\) 的 \(01\) 网格,你要在每行放置 \(\frac{m}{2}\) 个 \(1 \times 1\) 的骨牌和 \(\frac{m}{4}\) 个 \(1 \times 2\) 的横向骨牌,保证 \(m\) 是 \(4\) 的倍数。定义一个放置方案的权值为有覆盖到 \(1\) 的骨牌数量,问最小和最大权值分别是多少。
时间复杂度 \(O(nm)\)。
分析
显然每行独立,分别考虑。如果全是 \(1 \times 1\) 的或者 \(1 \times 2\) 的至多覆盖一个 \(1\),那么权值就是 \(\sum a_{i,j}\),即 \(1\) 的数量。但是有一些会覆盖 \(2\) 个 \(1\) 的骨牌,我们要减去,求的就是最多和最少有几个 \(1 \times 2\) 的骨牌覆盖了两个 \(1\)。
如果要覆盖两个最大的话,直接找出极长连续的 \(1\) 段,然后把它长度除以 \(2\) 的值加起来。
找最少的话,也可以贪心,但是我不会,选择直接 dp,设 \(f_{i,0/1}\) 表示前 \(i\) 位,第 \(i\) 位是否选的最小值,转移就是简单的了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define N 500005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
const int INF = 1e9;
int n, m, a[N], sum, s1, s2, f[N][2];
char ch[N];
signed main(){
m = rd(), n = rd();
for (int c = 1; c <= m; c++){
scanf ("%s", ch + 1);
for (int i = 1; i <= n; i++) a[i] = ch[i] - '0', sum += a[i];
int ct = 0, len = 0;
for (int i = 1; i <= n; i++){
if (a[i]) len++;
else ct += len / 2, len = 0;
}
s1 += min(n / 4, ct + len / 2);
f[0][0] = -INF, f[0][1] = 0;
for (int i = 1; i <= n; i++) f[i][0] = max(f[i - 1][0], f[i - 1][1]), f[i][1] = f[i - 1][0] + !(a[i] & a[i - 1]);
s2 += max(n / 4 - max(f[n][0], f[n][1]), 0);
}
printf ("%d %d", sum - s1, sum - s2);
return 0;
}
H - Monsters
题意
有一张 \(n\) 个点 \(m\) 条边的无向图。你有一个战斗力 \(x\),初始为 \(0\),每个点都有一只怪兽,战力为 \(a_i\)。初始你可以选择一个 \(a_i = 0\) 的点,并击败该怪兽。每击败一只怪兽,\(x\) 加 \(1\),你每次只能击败当前以击败的怪兽相邻的怪兽,且 \(a_i \le x\)。问最后是否可以击败所有怪兽。
时间复杂度 \(O(m\log m)\)。
分析
首先不难想到要先建重构树,当你能走到重构树的某一个点时,你就可以击败该点子树内所有点。于是建重构树,然后判断是否存在一条从 \(a_i=0\) 到根节点的路径,满足路径上所有所有父子对 \((u,v)\) 满足 \(a_u \le sz_v\),直接 dfs 即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define N 400005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
int n, m, a[N], fa[N], sz[N], tag[N], tot;
vector <int> e[N];
il int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
struct Edge{int u, v, w;}ed[N];
bool dfs(int u){
if (e[u].empty()) return !a[u];
int flg = 0;
for (int v : e[u]) if (sz[v] >= a[u]) flg |= dfs(v);
return flg;
}
signed Main(){
n = rd(), m = rd(), tot = n;
for (int i = 1; i <= n + n; i++) fa[i] = i, sz[i] = (i <= n), tag[i] = 0, e[i].clear();
for (int i = 1; i <= n; i++) a[i] = rd(), tag[i] = (!a[i]);
for (int i = 1, u, v; i <= m; i++) u = rd(), v = rd(), ed[i] = Edge{u, v, max(a[u], a[v])};
sort (ed + 1, ed + m + 1, [](Edge x, Edge y){return x.w < y.w;});
for (int i = 1; i <= m; i++){
int u = find(ed[i].u), v = find(ed[i].v), w = ed[i].w, mx = 0;
if (u == v) continue;
sz[++tot] = sz[u] + sz[v], a[tot] = w, fa[u] = fa[v] = tot, e[tot].push_back(u), e[tot].push_back(v);
}
if (sz[tot] < n) return puts("No"), 0;
puts(dfs(tot) ? "Yes" : "No");
return 0;
}
signed main(){
for (int T = rd(); T--;) Main();
return 0;
}
I - Skate
出到我做过的题了。
题意
有一个 \(n \times m\) 的网格,从 \((i,j)\) 出发时选择一个方向,然后一直向该方向移动直到碰到墙或 #,问要使得从任意一个格子出发可以到达任意格子,至少添加几个 #。
分析
注意到从每个格子出发,一定可以到达 \((1,1)\) 以及其他三个角落。所以我们只要判断是否可以从 \((1,1)\) 出发走到每个格子即可。
我们会发现,这个问题的答案不会超过 \(\min(n,m)-2\)。因为最劣情况就是样例一,只要在一行或一列的一边除了角落摆满即可。这启示我们从行列的角度考虑。
我们注意到,只要我们可以走到一行,我们就可以通过这一行上的所有地面走到其他列上去,列也同理,可以走到行上去。所以我们可以访问的格子是类似于行列覆盖的。所以我们考虑一行或一列是否可以走到。
如果把每一行和每一列看作一个点,那么位于 \((r,c)\) 的地面相当于串联起了第 \(r\) 行和第 \(c\) 列,是他们两个只要有一个可达,另一个就一定可达。这十分像合并操作。所以我们考虑将所有互相可达的行和列连起来,用并查集去维护。
我们可以先将四条边连起来,然后枚举每一个地板,连同第 \(r\) 行和第 \(c\) 列。
最后的答案考虑只要所有行或所有列都互相可达即可。所以答案就是行的连通块数量和列的连通块数量的较小值。
时间复杂度 \(O(nm \times \alpha(nm))\)。
#include <bits/stdc++.h>
using namespace std;
#define ll int
#define il inline
#define N 1005
#define mg(u, v) fu = find(u), fv = find(v), sz[fu] > sz[fv] ? (sz[fu] += sz[fv], fa[fv] = fu) : (sz[fv] += sz[fu], fa[fu] = fv)
il ll rd(){
ll s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
ll n = rd(), m = rd(), fu, fv, sz[N << 1], fa[N << 1], t1[N << 1], t2[N << 1], c1, c2;
il ll find(ll x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
char s[N][N];
int main(){
for (int i = 1; i <= n; i++) scanf ("%s", s[i] + 1);
for (int i = 1; i <= n + m; i++) fa[i] = i, sz[i] = 1;
mg(1, n), mg(1, n + 1), mg(1, n + m);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (s[i][j] == '#') mg(i, n + j);
for (int i = 1; i <= n; i++) if (!t1[find(i)]) c1++, t1[find(i)] = 1;
for (int i = n + 1; i <= n + m; i++) if (!t2[find(i)]) c2++, t2[find(i)] = 1;
printf ("%d\n", min(c1, c2) - 1);
return 0;
}
J - Multitest Generator
题意
定义一个长度为 \(m\) 的序列 \(a\) 为正确的,当且仅当 \(a_1 = m - 1\)。
定义一个长度为 \(n\) 的序列 \(a\) 为合法的,当且仅当存在一种将 \([2,n]\) 划分成 \(a_1\) 个区间的方法,使得每一段都是正确的。
问对于所有的 \(i \in [1,n-1]\),至少修改多少个位置,使得 \([i,n]\) 的区间是合法的。修改后的值可以为 \(0\),但不能为负数。
时间复杂度 \(O(n)\)。
分析
注意到最坏情况下可以 \(2\) 次操作完,分别修改第 \(1\) 和 \(2\) 个数。而对于不操作,也是好判断的,主要在于怎么判断一个位置能否一次操作完。
我们可以把序列看作一颗内向树,\(i\) 指向 \(i+a_i+1\),而不需要操作就是 \(a_i = dep_{i+1}\),操作一次有两种情况。
- 修改 \(a_i\),使得 \(a_i = dep_{i+1}\),这种情况当且仅当 \(i+1\) 的根节点是 \(n + 1\)。
- 修改 \(i+1\) 到根节点路径上一个节点,即将这个节点连接到编号比它大的节点中的一个,并使得连接后 \(a_i = dep_{i+1}\)。
第一种情况直接判,第二种情况会注意到可以选择的深度是一个左端点为 \(1\) 的区间,所以只要 dp 预处理最大值即可。剩下情况就是答案为 \(2\)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define N 300005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
const int INF = 1e9;
int n, a[N], cnt, dep[N], mx[N], f[N];
signed Main(){
n = rd(), dep[n + 1] = mx[n + 1] = f[n + 1] = 0;
for (int i = 1; i <= n; i++) a[i] = rd();
for (int i = n; i >= 1; i--) if (i + a[i] <= n) dep[i] = dep[i + a[i] + 1] + 1; else dep[i] = -INF;
for (int i = n; i >= 1; i--) mx[i] = max(mx[i + 1], dep[i]);
for (int i = n; i >= 1; i--){
f[i] = mx[i] - dep[i];
if (i + a[i] + 1 <= n) f[i] = max(f[i], f[i + a[i] + 1]);
}
for (int i = 1; i < n; i++){
int ans = 2;
if (dep[i + 1] == a[i]) ans = 0;
else if (dep[i + 1] > 0 || a[i] - dep[i + 1] - 1 <= f[i + 1]) ans = 1;
printf ("%d ", ans);
}
puts("");
return 0;
}
signed main(){
for (int T = rd(); T--;) Main();
return 0;
}
K - DSU Master
出题人并查集大师,但是语文不好。
题意
给定一个长度为 \(n - 1\) 的 \(01\) 序列 \(a\),定义一个长度为 \(k\) 的排列 \(p\) 的权值如下。
- 以此取出图上 \(p_i\) 和 \(p_{i+1}\) 所在并查集的根节点 \(u,v\)。
- 若 \(a_{p_i} = 0\),将 \(u\) 合并到 \(v\) 上;否则,将 \(v\) 合并到 \(u\) 上。
- 权值为最后并查集的儿子个数。
求对于所有的 \(k \in [1,n-1]\),所有长度为 \(k\) 的排列的权值和。
时间复杂度 \(O(n)\)。
分析
很好的计数题,但是脑子坏掉了,没有想出来。
首先不难想到的是拆贡献,考虑 \(i\) 对 \(1\) 产生贡献的方案数。\(i\) 能对 \(1\) 产生贡献,即 \(i\) 是 \(1\) 的儿子,有三个条件。
- \(a_{i-1}=0\)。
- \([1,i-1]\) 均在 \(i\) 之前出现。
- \(1\) 是 \([1,i-1]\) 节点的根节点。
条件 \(1,2\) 好处理,条件 \(3\) 先搁着,看看答案怎么求。
设 \(k\) 个点的答案是 \(g_k\),将 \([1,i]\) 排列后 \(1\) 为根的方案数为 \(f_i\),那么我们有下面式子。
这个式子表示枚举 \(i\),然后将这 \(i\) 个数放入排列中,剩下的数显然随意排,这 \(i\) 个数有 \(f_i\) 种排法。
考虑拆一下组合数的式子,得到下面的式子。
显然只要我们知道了 \(f\),就可以 \(O(n)\) 求解。考虑怎么求 \(f\)。
本质和上面相同,考虑到这样一个过程应该是先把 \([1,j]\) 并成以 \(1\) 为根,然后 \([j+1,i]\) 乱排,最后用 \(j\) 连起来。
同上可化简为下面式子。
初始状态 \(f_1 = 1\)。\(O(n)\) 求即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define N 500005
il int rd(){
int s = 0, w = 1;
char ch = getchar();
for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
return s * w;
}
const int P = 998244353;
il int ksm(int x, int r){
int ans = 1;
for (; r; x = 1ll * x * x % P, r >>= 1) if (r & 1) ans = 1ll * ans * x % P;
return ans;
}
int n, s, a[N], f[N], g[N], fact[N], inft[N];
il int C(int n, int m){return n < m || m < 0 ? 0 : 1ll * fact[n] * inft[m] % P * inft[n - m] % P;}
signed Main(){
n = rd();
for (int i = 1; i < n; i++) a[i] = rd();
f[1] = 1, s = (a[1] == 0);
for (int i = 2; i <= n; i++){
f[i] = 1ll * fact[i - 2] * s % P;
if (a[i] == 0) s = (s + 1ll * f[i] * inft[i - 1] % P) % P;
}
s = 0;
for (int i = 1; i <= n; i++){
g[i] = 1ll * fact[i - 1] * s % P;
if (a[i] == 0) s = (s + 1ll * f[i] * inft[i] % P) % P;
}
for (int i = 2; i <= n; i++) printf ("%d ", g[i]);
puts("");
return 0;
}
signed main(){
fact[0] = 1;
for (int i = 1; i <= 500000; i++) fact[i] = 1ll * fact[i - 1] * i % P;
inft[500000] = ksm(fact[500000], P - 2);
for (int i = 500000; i >= 1; i--) inft[i - 1] = 1ll * inft[i] * i % P;
for (int T = rd(); T--;) Main();
return 0;
}

浙公网安备 33010602011771号