“紅旗杯”第十五屆東北地區大學生程式設計競賽
東北地區賽,也是\(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\)
順帶一提,這玩意的常數大的離譜......\(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;
}