“红旗杯”第十五届东北地区大学生程序设计竞赛
东北地区赛,也是\(2021CCPC\)网络预选赛压力赛的题,感觉再不写就永远的咕咕咕了......
https://codeforces.com/gym/103145 传送门
A
题目大意
给一个\(n*n\)的矩阵填充\([1,n^2]\)的数,定义\(a_i\)为第i行最小值,\(S=\{a_1,a_2...a_n\}\cap\{1,2...n\}\)。求\(\sum\vert S\vert(mod998244353)\)。
题目分析
考虑如何选择,假如在某行选择数字\(i\),那么\(i\)可从\(n\)行中的任意一行的\(n\)个任意列选择,假定\(i\)是对集合\(S\)有贡献的数字,那么对于选定的行就要保证其余\(n-1\)个元素大于\(i\),那么选择方式就为\(C_{n^2-i}^{n-1}\),此外这\(n-1\)个元素可以随便排列,那么方式为(n-1)!,考虑此行后,剩余的\(n^2-n\)个元素可以自行排列。又已知对于集合\(S\)有贡献的只有数字\(1~n\),那么对于上述式子求和即可,答案为\(\sum_{i=1} n*n!*(n^2-n)!*C_{n^2-i}^{n-1}\)。
顺带一提,这玩意的常数大的离谱......\(hdu\)上正常模拟并不能过去,打表或者分段打表可以加速这一过程,关于分段打表,可以看 https://oi-wiki.org/math/dictionary/。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <string.h>
#include <cmath>
#include <deque>
#include <vector>
#include <map>
#define Lint long long
#define pi acos(-1.0)
using namespace std;
const Lint mod = 998244353;
int T, n;
Lint f[5005 * 5005], inv[5005 * 5005];
Lint pw(Lint x, Lint y)
{
Lint ans = 1;
while (y)
{
if (y & 1)
{
ans = ans * x;
ans %= mod;
}
y >>= 1;
x = (x * x) % mod;
}
return ans;
}
Lint C(Lint x, Lint y)
{
if (!x)
return 1;
if (!y)
return 1;
return f[x] * inv[y] % mod * inv[x - y] % mod;
}
int main()
{
f[0] = 1;
for (int i = 1; i <= 5000 * 5000; ++i)
f[i] = f[i - 1] * (i % mod), f[i] %= mod;
inv[5000 * 5000] = pw(f[5000 * 5000], mod - 2);
for (int i = 5000 * 5000 - 1; i >= 0; --i)
{
inv[i] = inv[i + 1] * (i + 1) % mod;
inv[i] %= mod;
}
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
Lint ans = 0;
for (int j = 1; j <= n; ++j)
{
ans += (C(n * n - j, n - 1) % mod);
ans %= mod;
}
ans = ans * n % mod * f[n] % mod * f[n * n - n] % mod;
printf("%lld\n", ans);
}
return 0;
}
C
题目大意
给定一颗树,可删除任意点,要求删除后的树不存在单独节点的情况,求方案数。
题目分析
树形\(dp\),考虑\(dp[x][0]\)为删除此点的方案数,\(dp[x][1]\)为保留此点并保留以此点为根节点至少有一个叶子节点的方案数,\(dp[x][2]\)为保留此点并删除所有叶子节点的方案数。
对于删除\(x\):\(x\)的儿子\(v\)可以选择删除或保留并保留至少一个儿子,不可以保留并删除所有儿子,因为这样\(v\)就会独立。
对于保留\(x\)并删除所有儿子:显然只有删除儿子\(v\)一种选择
对于保留\(x\)并保留至少一个儿子:儿子的所有形态可进行一个累乘,但要减去保留\(x\)删除所有儿子的方法。
for (auto u : v[x])
{
if (u == fa)
continue;
dp[x][0] = dp[x][0] % mod * (dp[u][0] + dp[u][1]) % mod;
dp[x][2] = dp[x][2] % mod * dp[u][0] % mod;
dp[x][1] = dp[x][1] % mod * (dp[u][0] + dp[u][1] + dp[u][2]) % mod;
}
dp[x][1] = (dp[x][1] - dp[x][2] + mod) % mod;
取模操作时似乎使用自加/自乘运算会有奇怪的锅......所以还是展开写叭
对于边界问题,对于整个树的叶子节点,删去本身和保留此点删除所有叶子节点是合法的,而保留此点与至少一个叶子节点是非法的(不会有额外的叶子节点)。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <string.h>
#include <cmath>
#include <deque>
#include <vector>
#include <map>
#define Lint long long
#define pi acos(-1.0)
using namespace std;
int n;
const int N = 1e5 + 10;
const Lint mod = 998244353;
vector<int> v[N];
Lint dp[N][3];
void dfs(int x, int fa)
{
int flag = 0;
for (auto u : v[x])
{
if (u == fa)
continue;
flag++;
dfs(u, x);
}
if (flag == 0)
{
dp[x][0] = 1;
dp[x][1] = 0;
dp[x][2] = 1;
return;
}
dp[x][0] = 1;
dp[x][1] = 1;
dp[x][2] = 1;
for (auto u : v[x])
{
if (u == fa)
continue;
dp[x][0] = dp[x][0] % mod * (dp[u][0] + dp[u][1]) % mod;
dp[x][2] = dp[x][2] % mod * dp[u][0] % mod;
dp[x][1] = dp[x][1] % mod * (dp[u][0] + dp[u][1] + dp[u][2]) % mod;
}
dp[x][1] = (dp[x][1] - dp[x][2] + mod) % mod;
}
int main()
{
int x, y, T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
v[i].clear();
}
for (int i = 1; i < n; ++i)
{
scanf("%d%d", &x, &y);
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1, -1);
printf("%lld\n", (dp[1][0] + dp[1][1]) % mod);
}
return 0;
}
D
题目大意
给定一个初始序列,有两个操作:
1.区间\([L,R]\)每个元素\(a_i\)增加\(lowbit(a_i)\)
2.区间\([L,R]\)元素和(取模\(998244353\))
题目分析
乍一看对于元素增加\(lowbit\)是很难拓展的一个操作,但我们先看\(lowbit\)操作的本质:取出一个数二进制下最小位的\(1\)及其后续。那么对于\(2\)的幂指数\(x\),\(lowbit(x)=x\)。对于任意数\(n\)显然\(log_2n\)次的\(lowbit\)操作可以将其变成\(2\)的幂指数。
假若某一区间所有数都为\(2\)的幂指数,那么对于此区间的操作就为区间元素乘\(2\),否则我们递归到叶子节点进行暴力修改。
需要额外注意的是,由于我们的操作要进行取模,且题目并未保证元素在\(lowbit\)操作后变为\(2\)的幂指数时小于模数,所以对于叶子节点,我们应额外开变量记载其原本值,以免在取模后\(lowbit\)操作出锅导致无法变成\(2\)的幂指数。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <string.h>
#include <cmath>
#include <deque>
#include <vector>
#include <map>
#define Lint long long
#define pi acos(-1.0)
using namespace std;
const int N = 1e5 + 10;
const long long mod = 998244353;
Lint f[N], b[N];
int n;
Lint lowbit(Lint x) { return x & -x; }
struct Tree
{
struct node
{
int l, r, qwq, lazy;
Lint sum, val, times;
};
node a[N * 4 + 10];
void pushup(int p)
{
a[p].sum = (a[p << 1].sum + a[p << 1 | 1].sum) % mod;
a[p].qwq = a[p << 1].qwq & a[p << 1 | 1].qwq;
}
void pushdown(int p)
{
if (a[p].lazy)
{
a[p << 1].lazy += a[p].lazy;
a[p << 1 | 1].lazy += a[p].lazy;
a[p << 1].sum *= (f[a[p].lazy]);
a[p << 1].sum %= mod;
a[p << 1 | 1].sum *= (f[a[p].lazy]);
a[p << 1 | 1].sum %= mod;
a[p].lazy = 0;
}
}
void change(int p)
{
if (a[p].qwq)
{
a[p].sum *= 2, a[p].sum %= mod;
a[p].lazy++;
return;
}
if (a[p].l == a[p].r)
{
if (!a[p].val)
{
a[p].sum += lowbit(a[p].sum);
if (a[p].sum == lowbit(a[p].sum))
{
a[p].qwq = 1, a[p].val = a[p].sum;
}
}
else{
a[p].times++;
a[p].sum=a[p].val%mod*f[a[p].times];
a[p].sum %= mod;
}
return;
}
int mid = (a[p].l + a[p].r) >> 1;
pushdown(p);
change(p << 1), change(p << 1 | 1);
pushup(p);
}
void build(int p, int l, int r)
{
a[p].l = l, a[p].r = r, a[p].qwq = 0, a[p].lazy = 0, a[p].times = 0, a[p].val = 0;
if (l == r)
{
a[p].sum = b[l];
if (a[p].sum == lowbit(a[p].sum))
{
a[p].val = a[p].sum;
a[p].qwq = 1;
}
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
pushup(p);
}
void add(int p, int L, int R)
{
int l = a[p].l, r = a[p].r;
if (l >= L && r <= R)
{
change(p);
return;
}
pushdown(p);
int mid = (l + r) >> 1;
if (mid >= L)
{
add(p << 1, L, R);
}
if (mid + 1 <= R)
{
add(p << 1 | 1, L, R);
}
pushup(p);
}
Lint qurry(int p, int L, int R)
{
int l = a[p].l, r = a[p].r;
if (l >= L && r <= R)
{
return a[p].sum % mod;
}
pushdown(p);
Lint ans = 0;
int mid = (l + r) >> 1;
if (mid >= L)
ans += qurry(p << 1, L, R);
if (mid + 1 <= R)
ans += qurry(p << 1 | 1, L, R);
return ans % mod;
}
} Tree;
int main()
{
f[0] = 1;
for (int i = 1; i <= 100000; ++i)
{
f[i] = f[i - 1] * 2;
f[i] %= mod;
}
int T, x, y, op, m;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%lld", &b[i]);
}
Tree.build(1, 1, n);
scanf("%d", &m);
while (m--)
{
scanf("%d%d%d", &op, &x, &y);
if (op == 1)
{
Tree.add(1, x, y);
}
else
printf("%lld\n", Tree.qurry(1, x, y));
}
}
return 0;
}
E
题目大意
给定一个数\(k\),构造出数\(x\),使得\(k\)是\(x\)的因子且\(x\)所有因子的子集元素和等于\(x\)。
题目分析
显然\(6p=p+2p+3p\)。看准数据范围构造即可。
I
签到题......
K
题目大意
给定图,每条边上有权值,多组询问,每次询问给定一个\(x\),判断有多少组节点相连(相连的前提为边权大于等于\(x\))。
题目分析
只考虑边权大于等于x的边实际上是一个删边的操作,但我们知道删除边在并查集中是很棘手的,相反,添加边是很容易的。那么就像星球大战一样,我们从大到小考虑每次询问,每次都添加一些边,若添加边为\((u,v)\),那么增加的组数为\(Size_u*Size_v\)。
此外,如果最小的数个询问没有增加新的边,那么答案需要继承较其更大的值。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <string.h>
#include <cmath>
#include <deque>
#include <vector>
#include <map>
#define Lint long long
#define pi acos(-1.0)
using namespace std;
typedef pair<int, int> P;
const int N = 1E5 + 10;
const int M = 2E5 + 10;
int T, n, m, q;
P b[M];
struct node
{
int f[N];
Lint Size[N];
void build()
{
for (int i = 1; i <= n; ++i)
{
f[i] = i;
Size[i] = 1;
}
}
int Find(int x)
{
if (f[x] == x) return x;
return f[x] = Find(f[x]);
}
void marge(int x, int y)
{
int le = Find(x), re = Find(y);
f[le] = re;
Size[re] += Size[le];
}
} fa;
struct edge
{
int x, y, z;
} e[M];
bool cmp(edge x, edge y)
{
return x.z < y.z;
}
Lint c[M];
int main()
{
scanf("%d", &T);
while (T--)
{
scanf("%d%d%d", &n, &m, &q);
fa.build();
Lint ans = 0;
for (int i = 1; i <= m; ++i)
{
scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].z);
}
sort(e + 1, e + 1 + m, cmp);
for (int i = 1; i <= q; ++i)
{
scanf("%d", &b[i].first);
b[i].second = i;
}
sort(b + 1, b + 1 + q);
int now = q; //最大
for (int i = m; i >= 1; --i)
{
while (e[i].z < b[now].first)
{
c[b[now].second] = ans;
now--;
}
if (fa.Find(e[i].x) != fa.Find(e[i].y))
{
ans += (Lint)fa.Size[fa.Find(e[i].x)] * fa.Size[fa.Find(e[i].y)];
fa.marge(e[i].x, e[i].y);
}
}
while(now){
c[b[now].second]=ans;
now--;
}
for (int i = 1; i <= q; ++i)
{
printf("%lld\n", c[i]);
}
}
return 0;
}