1. 程式人生 > 其它 >“紅旗杯”第十五屆東北地區大學生程式設計競賽

“紅旗杯”第十五屆東北地區大學生程式設計競賽

​ 東北地區賽,也是\(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;
}