2024XJTUPC西安交通大學校賽VP題解

qzhfx發表於2024-05-25

每次vp都自閉,已成習慣,時間還是分配的不合理,debug時做太多無用功。
一鍵做題

A.交小西的禮物

輸出 a + b + 2c + 3d 即可

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e5 + 7;
void solve()
{
    ll a,b,c,d;
    cin >> a>> b >> c>> d;
    cout << a + b + c*2 + d *3 << '\n';
}

C.榕樹之心

直接按題目要求計算

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
const int mod = 998244353;

void solve()
{
    double x, y;
    cin >> x >> y;
    double x0 = 0.5 * x + 0.5 * y;
    double y0 = sqrt(3) * 0.5 * x - sqrt(3) * 0.5 * y;
    cout << fixed << setprecision(7) << x0 << " " << y0 << '\n';
}

B.轉呀轉

直接用半徑和圓心角求弦長即可,注意sin中引數是弧度制

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e5 + 7;
const double pi = acos(-1.0);
void solve()
{
    double x, y;
    cin >> x >> y;
    double r = (x * x) + y * y;
    r = sqrt(r);
    double t;
    cin >> t;
    double v;
    cin >> v;
    v = 1.0 / v;
    double p = t / v;
    p = p - (int)p;
    double q = 1.0 - p;
    if (p > q)
        swap(p, q);
    double ans = p * pi;
    ans = sin(ans) * 2.0 * r;
    cout << fixed << setprecision(8) << ans << '\n';
}

F.Everyone’s ALL IN

預處理計算即可

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e6 + 7;
const double pi = acos(-1.0);
vector<int>g[N];
void solve()
{
    int n,m;
    cin>>n>>m;
    map<ll,ll>mp;
    for(int i=1;i<=n;i++)
    {
        ll x;
        cin>>x;
        g[x].push_back(i);
        mp[x]+=i;
    }
    while(m--)
    {
        int x,y;
        cin>>x>>y;
        cout<<1ll*mp[x]*mp[y]<<endl;
    }
}

D.瑟莉姆的宴會

直接構造菊花圖即可,對於每個點的正負貢獻都計算一下,如果該點非負,即以該點為根構造菊花圖

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e6 + 7;
const double pi = acos(-1.0);

void solve()
{
    int n,m;
    cin >> n >> m;
    vector<int>a(n + 1);
    for(int i=0;i<m;i++){
        int u,v;
        cin >> u >> v;
        a[u]++,a[v]--;
    }
    int rt = 0;
    for(int i=1;i<=n;i++){
        if(a[i]>=0){
            rt = i;
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(i==rt){
            cout << 0 << ' ';
        }else {
            cout << rt << " " ;
        }
    }
}

O.篩法

打表猜答案為\(n^{2}\),證明不會(

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 1e6 + 7;
const double pi = acos(-1.0);

void solve()
{
    ll n;
    cin >> n;
    cout << n * n << endl;
}

M.生命遊戲

就直接暴力模擬即可,使用bfs模擬,若度為k進佇列執行後刪除,每個點最多進隊一次
最後再使用遍歷一遍圖得到答案

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 998244353;
const double eps = 1e-9;
vector<int> s[N];
void solve()
{
    int n, k;
    cin >> n >> k;
    vector<int> du(n + 1);
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        s[u].push_back(v);
        s[v].push_back(u);
        du[u]++, du[v]++;
    }
    queue<int> q;
    for (int i = 1; i <= n; i++)
    {
        if (du[i] == k)
        {
            q.push(i);
            du[i] = -1;
        }
    }
    vector<int> vis(n + 1);
    int ans = 0;
    while (q.size())
    {
        set<int> st;
        while (q.size())
        {
            int u = q.front();
            q.pop();
            vis[u] = 1;
            for (auto v : s[u])
            {
                if (du[v] == -1)
                    continue;
                du[v]--;
                if (du[v] == k)
                {
                    st.insert(v);
                }
                else
                {
                    if (st.count(v))
                        st.erase(v);
                }
            }
        }
        for (auto v : st)
        {
            q.push(v);
            du[v] = -1;
        }
    }
    auto bfs = [&](int u)
    {
        queue<int> qp;
        qp.push(u);
        vis[u] = 1;
        while (qp.size())
        {
            int now = qp.front();
            qp.pop();
            for (auto v : s[now])
            {
                if (!vis[v])
                {
                    qp.push(v);
                    vis[v] = 1;
                }
            }
        }
    };
    for (int i = 1; i <= n; i++)
    {
        if (!vis[i])
        {
            bfs(i);
            ans++;
        }
    }
    cout << ans << '\n';
}

K.崩壞:星穹鐵道

沒玩崩鐵導致的,讀假題演了半小時(
一看資料範圍加上求方案數自然想到矩陣快速冪
考慮如何構造矩陣
發現對於每一時刻,只需用當前所剩技能點進行轉移即可,矩陣大小6*6
透過dfs構造矩陣,列舉初始技能點i,搜尋經過四次(一輪)攻擊後所到達的狀態j有多少方案
得到轉移矩陣後進行n/4次轉移即可
最後要與初始狀態相乘,也就是有k個技能點
由於n不一定是4的倍數,所以要再用dfs轉移完餘數的貢獻

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 1e6 + 7;
const int mod = 998244353;
const double eps = 1e-9;
#define int long long
struct Mat
{
    int v[130][130] = {0};
    int x, y;
    Mat() {}
    Mat(int _x, int _y) { x = _x, y = _y; }
    // 清空
    void zero()
    {
        memset(v, 0, sizeof(v));
        x = y = 0;
    }
    // 單位矩陣化
    void ones()
    {
        memset(v, 0, sizeof(v));
        for (int i = 0; i <= x; i++)
        {
            v[i][i] = 1;
        }
    }
    // 乘法
    void Mul(Mat a, Mat b)
    {
        zero();
        x = a.x, y = b.y;
        int c = a.y;
        for (int i = 0; i <= x; ++i)
        {
            for (int j = 0; j <= y; ++j)
            {
                for (int k = 0; k <= c; ++k)
                {
                    v[i][j] += (long long)a.v[i][k] * b.v[k][j] % mod;
                    v[i][j] %= mod;
                }
            }
        }
        return;
    }
    // 列印
    void show()
    {
        for (int i = 0; i <= x; i++)
        {
            for (int j = 0; j <= y; j++)
            {
                cout << v[i][j] << " ";
            }
            cout << '\n';
        }
    }
};
Mat operator*(const Mat &x, const Mat &y)
{
    Mat res;
    res.Mul(x, y);
    return res;
}
Mat ksm(Mat a, long long b)
{
    Mat x = a;
    x.ones();
    while (b)
    {
        if (b & 1)
            x = x * a;
        b >>= 1;
        a = a * a;
    }
    return x;
}
int ps[5];
Mat a(5, 5);
void dfs(int u, int i, int r, int lim)
{
    if (i == lim)
    {
        a.v[r][u]++;
        return;
    }
    if (ps[i] == 1)
    {
        u++;
        if (u > 5)
            u = 5;
        dfs(u, i + 1, r, lim);
    }
    if (ps[i] == 2)
    {
        if (u == 0)
            u++;
        else
            u--;
        dfs(u, i + 1, r, lim);
    }
    if (ps[i] == 3)
    {
        int v = u;
        v++;
        if (v > 5)
            v = 5;
        dfs(v, i + 1, r, lim);
        if (u != 0)
        {
            u--;
            dfs(u, i + 1, r, lim);
        }
    }
}
void solve()
{
    ll n, k;
    cin >> n >> k;
    for (int os = 1; os <= 4; os++)
    {
        cin >> ps[os];
    }
    for (int i = 0; i <= 5; i++)
    {
        dfs(i, 1, i, 5);
    }
    Mat p(0, 5);
    ll nm = n % 4;

    p.v[0][k] = 1;

    Mat res = p * ksm(a, n / 4);
    ll ans = 0;
    a.zero();
    a = Mat(5, 5);
    for (int i = 0; i <= 5; i++)
    {
        dfs(i, 1, i, n % 4 + 1);
    }
    for (int i = 0; i <= 5; i++)
    {
        ll sb = 0;
        for (int j = 0; j <= 5; j++)
        {
            sb += res.v[0][i] * a.v[i][j] % mod;
            sb %= mod;
        }
        ans += sb;
        ans %= mod;
    }
    cout << ans << '\n';
}

E.雪中樓

沒想到題解期望難度這麼高
看完題的第一想法就是直接模擬,考慮如何快速實現,我這裡使用了一個雙端連結串列
l記錄左邊編號,r記錄右邊編號,直接插入即可

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const int mod = 998244353;
const int N = 2e5 + 7;
const double pi = acos(-1.0);
int l[N],r[N];
void solve()
{
    int n;
    cin >> n;
    for(int i=1;i<=n;i++){
        int x;
        cin >> x;
        if(x == 0){
            int p = r[0];
            l[r[0]] = i;
            r[0] = i;
            l[i] = 0;
            r[i] = p;
        }else {
            int p = r[x];
            l[r[x]] = i;
            r[x] = i;
            l[i] = x;
            r[i] = p;
        }
    }
    int x = r[0];
    while(x!=0){
        cout << x << ' ';
        x = r[x];
    }
}

I.命令列

從寫完E之後就一直在想這道題,感覺題目描述有些不清晰一直wa最後一個點,結束後看別人程式碼才發現坑點,如果串為空那麼補全是集合T為所有指令串(空串是所有串的字首?)
要求字首考慮使用字典樹,用cnt記錄每個字首有多少串經過,並記錄有每個串的終點out
我們執行操作的時候記錄當前指標p,使用p在字典樹上移動,如果該節點不存在,此時p = -1,同時用一個k記錄-1的個數,一個lst記錄最後在字典樹上的節點編號。當p=-1時,增加操作使k增加,退格操作同理,執行操作無解,補全操作也是沒有行動。
考慮不是-1的情況,即當前輸入串還在字典樹上,如果是加入操作就直接執行,如果是刪除操作,我們可以對於字典樹上每個節點p都記錄一個父親節點,那麼刪除操作就是在字典樹上跳父親。
對於執行操作,如果當前p是終點out,那麼輸出out[p],否則無解。
對於補全操作,一個最樸素的想法是在樹上一直跳,直到有分叉為止,但是會被一個很長的串卡掉
考慮最佳化,我們在字典樹預處理時再記錄一下每個節點有哪些串經過in(可能有多個,但是不影響答案),再記錄對於每個串的每個字母對應的節點mp,這樣我們可以在補全操作時對於當前串二分,如果二分到的位置的節點的cnt小於最初的cnt就不行,這樣就得到了補全後的節點p,此時即可透過此題

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 5e6 + 7;
const int mod = 998244353;
// const int N = 1000050;
int trie[N][26]; // 所構造的查詢樹,其對應的值是子節點的編號,【它的編號】【子節點的字母】
// 等於0表示沒有該字母;
int cnt[N]; // N是一個單詞終點的編號,表示有無該單詞(0表示沒有)
int id;     // 結點計數器,初始值為0
// 每次插入和查詢複雜度都是O(n)
int fa[N];
int in[N];
int out[N];
void solve()
{
    int n, m;
    cin >> n >> m;
    vector<string> str(n + 1);
    vector<vector<int>> mp(n + 1);
    // in[0]=1;
    for (int i = 1; i <= n; i++)
    {
        cin >> str[i];
        int p = 0;
        mp[i].resize(str[i].size());
        in[p] = i;
        cnt[p]++;//記錄0節點
        for (int j = 0; j < str[i].size(); j++)
        {
            int x = str[i][j] - 'a';
            if (trie[p][x] == 0)
                trie[p][x] = ++id;
            int suv = p;
            p = trie[p][x];
            fa[p] = suv;
            cnt[p]++;
            mp[i][j] = p;//每個字元對應節點
            in[p] = i;//每個節點被哪個經過
        }
        out[p] = i;//是否是一個串的終點
    }
    string TS;
    cin >> TS;
    int p = 0, lst = 0, k = 0;
    for (auto it : TS)
    {
        if (it == 'T')
        {
            if (p == -1)
            {
                continue;
            }
            int ls = cnt[p];//當前節點經過串個數
            int i = in[p];//經過當前節點的一個串的編號
            int l = 0, r = str[i].size() - 1;
            while (l < r)
            {
                int mid = (l + r + 1) >> 1;
                if (cnt[mp[i][mid]] >= ls)
                {
                    l = mid;
                }
                else
                {
                    r = mid - 1;
                }
            }
            if(cnt[mp[i][l]]<ls){//檢查最終結果是否合法(主要針對空串情況)
                p = 0;
            }else
                p = mp[i][l];//跳到補全後的位置
        }
        else if (it == 'B')
        {
            if (p == 0)
                continue;
            if (p == -1)
            {
                k--;
                if (k == 0)//沒有-1,當前輸入又回到樹上
                {
                    p = lst;
                }
            }
            else
            {
                p = fa[p];//跳父親
            }
        }
        else if (it == 'E')
        {
            if (p != -1 && out[p])
            {
                cout << out[p] << ' ';
            }
            else
            {
                cout << -1 << ' ';
            }
            p = 0;//清空輸入
            lst = 0;
            k = 0;
        }
        else
        {
            if (p == -1)
            {
                k++;
                continue;
            }
            int x = it - 'a';
            int suv = p;
            p = trie[p][x];
            if (!p)//開始沒有對應節點
            {
                lst = suv;
                k++;
                p = -1;
            }
        }
    }
}

N.聖誕樹

十分簡單的啟發式合併
一個貪心的想法是隻要當前子樹有k個不同,就對答案貢獻
令1為根
對每個節點開一個set存子樹中節點顏色,如果子樹已經貢獻答案則不進入set
合併時候從小到大合併,確保複雜度
整體複雜度\(O(nlog^{2}n)\)

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f3f
using namespace std;
using ll = long long;
const int N = 5e5 + 7;
const int mod = 998244353;
vector<int> s[N];
set<int> st[N];
void solve()
{
    int n, k;
    cin >> n >> k;
    vector<int> col(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> col[i];
    }
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        s[u].push_back(v);
        s[v].push_back(u);
    }
    int ans = 0;
    auto dfs = [&](auto &dfs, int u, int fa) -> void
    {
        st[u].insert(col[u]);
        for (auto v : s[u])
        {
            if (v == fa)
                continue;
            dfs(dfs, v, u);
            if(st[v].size()>st[u].size()){
                swap(st[u],st[v]);//啟發式合併
            }
            for(auto x:st[v]){
                st[u].insert(x);
            }
        }
        if (st[u].size() >= k)
        {
            ans++;
            st[u].clear();
        }
    };
    dfs(dfs,1,0);
    cout << ans << '\n';
}

相關文章