2024“釘耙程式設計”中國大學生演算法設計超級聯賽(2)

空気力学の詩發表於2024-07-22

Preface

最唐氏的一集,前中期被 A 卡得數次破防紅溫,後期經典不知道在幹嘛擺著擺著就結束了

可惜的是徐神最後 1h 寫的 B 因為兩個陣列搞反了一直沒過,賽後看了眼就過了,這下狠狠地掉 Rating 了


雞爪

丁真構造題,但有人連 WA 三發怎麼回事呢

首先不難想到最大化和 \(1\) 連邊的數量,首先可以以 \(1\) 為中心搞一個雞爪,剩下的雞爪都可以蹭一條和 \(1\) 的邊

手玩一下會發現此時邊數最多為 \(2+\lfloor\frac{n}{3}\rfloor+n\bmod 3\) 條,而剩下的邊數總是偶數

此時要在剩下的點間合理地選出若干個雞爪,可以考慮這樣兩條兩條的加邊:

(2,3),(2,4)
(2,5).(3,4)
(2,6),(3,5)

不難發現這樣顯然保證了字典序最小

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
int t,n;
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        RI i; scanf("%d",&n); int e1; vector <pi> ans;
        if (n<=2) e1=n; else e1=2+n/3+n%3;
        for (i=2;i<=e1+1;++i) ans.push_back(pi(1,i));
        int rem=n-e1; if (rem>0)
        {
            ans.push_back(pi(2,3));
            ans.push_back(pi(2,4));
            rem-=2;
        }
        for (i=5;i<=e1+1&&rem>0;++i)
        {
            ans.push_back(pi(2,i));
            ans.push_back(pi(3,i-1));
            rem-=2;
        }
        sort(ans.begin(),ans.end());
        for (auto [x,y]:ans) printf("%d %d\n",x,y);
    }
    return 0;
}

夢中的地牢戰鬥

很裸的大力狀壓題,不難發現用三元組 \((x,y,mask)\) 表示當前角色位於 \((x,y)\),剩下沒打死的怪物狀壓狀態為 \(mask\) 的最大收益

預處理轉移後可以得到一個有向圖,但由於可能會成環因此需要用 SPFA 求最長路,細節比較多很容易寫掛

#include <bits/stdc++.h>

std::tuple<int, int, int> decomp(int status) {
    return { status & 31, status >> 5 & 31, status >> 10 }; 
}

int comp(int x, int y, int e) {
    return (x) | (y << 5) | (e << 10);
}

int dis[1 << 20], vis[1 << 20], dmg[1 << 20];
int ex[10], ey[10], ea[10], eb[10], ec[10];
int U[30][30][10], D[30][30][10], L[30][30][10], R[30][30][10], eas[1 << 10];
int n, m, k, d;

inline int L1(int x1, int y1, int x2, int y2) {
    return std::abs(x1 - x2) + std::abs(y1 - y2);
}

int work() {
    std::cin >> n >> m >> k >> d;

    int emap[30][30]; memset(emap, 0, sizeof(emap));
    
    for(int i = 0; i < k; ++i)
        std::cin >> ex[i] >> ey[i],
        emap[--ex[i]][--ey[i]] |= 1 << i,
        std::cin >> ea[i] >> eb[i] >> ec[i];
    
    for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j) for(int k = 0; k <= d; ++k)
        U[i][j][k] = D[i][j][k] = L[i][j][k] = R[i][j][k] = emap[i][j];
        // U[i][j][1] = D[i][j][1] = L[i][j][1] = R[i][j][1] =*/ 0;
    
    for(int z = 1; z <= d; ++z) for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j) {
        if(i > 0)     U[i][j][z] = U[i - 1][j][z - 1] | emap[i][j];
        if(i < n - 1) D[i][j][z] = D[i + 1][j][z - 1] | emap[i][j];
        if(j > 0)     L[i][j][z] = L[i][j - 1][z - 1] | emap[i][j];
        if(j < m - 1) R[i][j][z] = R[i][j + 1][z - 1] | emap[i][j];
    }

    memset(eas, 0, sizeof(eas));
    for(int i = 0; i < (1 << k); ++i) {
        eas[i] = 0;
        for(int j = 0; j < k; ++j) if(i >> j & 1) eas[i] += ea[j];
    }

    memset(vis, 0, sizeof(vis));
    memset(dis, 0x80, sizeof(dis));

    for(int x = 0; x < n; ++x) for(int y = 0; y < m; ++y) for(int e = 0; e < (1 << k); ++e) {
        int c = comp(x, y, e); dmg[c] = 0;
        for(int z = 0; z < k; ++z) if((e >> z & 1) && L1(x, y, ex[z], ey[z]) <= ec[z]) dmg[c] += eb[z];
        // std::cerr << "dmg[" << x << "][" << y << "][" << e << "] = " << dmg[c] << std::endl;
    }

    int sx, sy; std::cin >> sx >> sy; sx--, sy--;
    int ans = 0;
    int start = comp(sx, sy, (1 << k) - 1);
    dis[start] = 0; vis[start] = 1;
    std::queue<int> q; q.push(start);

    while(!q.empty()) {
        int id = q.front(); q.pop();
        if(dis[id] > ans) ans = dis[id];
        vis[id] = false;
        auto [x, y, e] = decomp(id);
        assert(!(e & emap[x][y]));
        // std::cerr << "dis[" << x << "][" << y << "][" << e << "] = " << dis[id] << char(10);

        auto update = [&](int nid, int r) {
            if(dis[id] + r > dis[nid]) {
                dis[nid] = dis[id] + r;
                if(!vis[nid]) {
                    vis[nid] = true;
                    q.push(nid);
                }
            }
        };

        for(int i = 1; i <= d; ++i) {
            int nx, ny, ne, nid, r;
            nx = x - i, ny = y, ne = e ^ (e & U[x][y][i]);
            if(nx >= 0 && !(emap[nx][ny] & e)) nid = comp(nx, ny, ne), r = eas[e & U[x][y][i]] - dmg[nid], update(nid, r);
            nx = x + i, ny = y, ne = e ^ (e & D[x][y][i]);
            if(nx <  n && !(emap[nx][ny] & e)) nid = comp(nx, ny, ne), r = eas[e & D[x][y][i]] - dmg[nid], update(nid, r);
            ny = y - i, nx = x, ne = e ^ (e & L[x][y][i]);
            if(ny >= 0 && !(emap[nx][ny] & e)) nid = comp(nx, ny, ne), r = eas[e & L[x][y][i]] - dmg[nid], update(nid, r);
            ny = y + i, nx = x, ne = e ^ (e & R[x][y][i]);
            if(ny <  m && !(emap[nx][ny] & e)) nid = comp(nx, ny, ne), r = eas[e & R[x][y][i]] - dmg[nid], update(nid, r);
        }
    }

    return ans;
}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T; while(T--) {
        std::cout << work() << std::endl;
    }
    return 0;
}

絕對不模擬的簡單魔方

魔方大師祁神發現這題根本不用 Care 什麼旋轉,角塊的排布就是符合一定拓撲性質的,然後秒了此題

#include<bits/stdc++.h>
using namespace std;

const int corner[8][3][2] = {
    {{3, 4}, {4, 3}, {4, 4}},
    {{3, 6}, {4, 6}, {4, 7}},
    {{1, 6}, {4, 9}, {4, 10}},
    {{1, 4}, {4, 12}, {4, 1}},
    {{7, 4}, {6, 4}, {6, 3}},
    {{7, 6}, {6, 7}, {6, 6}},
    {{9, 6}, {6, 10}, {6, 9}},
    {{9, 4}, {6, 1}, {6, 12}},
};

int t;
string cube[10];

int gp(int c, int p){
    auto [x, y] = corner[c][p];
    return cube[x][y]-'0';
}

int getnum(int c){
    return gp(c, 0)*100 + gp(c, 1)*10 + gp(c, 2);
}

void rorate(int &x){
    int tmp = x%10;
    x /= 10;
    x += tmp*100;
}

void solve(){
    set<int> st({123, 134, 145, 152, 632, 643, 654, 625});

    for (int i=1; i<=9; ++i){
        cin >> cube[i];
        cube[i] = '$' + cube[i];
    }

    bool ok=true;
    int ans=0;
    for (int c=0; c<8; ++c){
        int x = getnum(c);
        while ((x/100 != 1) && (x/100 != 6)) rorate(x);
        if (!st.count(x)){ok=false; ans=x; break;}
    }

    if (ok) cout << "No problem\n";
    else{
        vector<int> vec({ans/100, (ans/10)%10, ans%10});
        sort(vec.begin(), vec.end());
        cout << vec[0] << ' ' << vec[1] << ' ' << vec[2] << '\n';
    }

}

signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> t; while (t--) solve();
    return 0;
}

a*b problem

不可做題,棄療


小塔的養成遊戲之夢

賽時沒幾個隊過,棄療


傳奇勇士小凱

根據題意就是選一條從根到葉子的路徑,然後簡單推一下期望的式子會發現每個點的貢獻就是 \(\frac{15}{p_i}\)

手寫一個分數類,不難發現只有加法的情況分母不會超過 \(\operatorname{LCM}(1,2,\dots,15)\),可以直接求解無需高精度

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
struct frac
{
    int x,y;
    inline frac(CI X=0,CI Y=1)
    {
        int g=__gcd(X,Y); x=X/g; y=Y/g;
    }
    friend inline bool operator < (const frac& A,const frac& B)
    {
        return A.x*B.y<A.y*B.x;
    }
    friend inline frac operator + (const frac& A,const frac& B)
    {
        int X=A.x*B.y+A.y*B.x,Y=A.y*B.y,g=__gcd(X,Y);
        return frac(X/g,Y/g);
    }
}f[N]; int t,n,x,y,a[N]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
    f[now]=frac(15,a[now]); frac tmp;
    for (auto to:v[now]) if (to!=fa)
    {
        DFS(to,now);
        if (tmp<f[to]) tmp=f[to];
    }
    f[now]=f[now]+tmp;
}
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        RI i; for (scanf("%lld",&n),i=1;i<=n;++i) v[i].clear();
        for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
        for (i=1;i<=n;++i) scanf("%lld",&a[i]);
        DFS(); printf("%lld/%lld\n",f[1].x,f[1].y);
    }
    return 0;
}

URL劃分

簽到,我題意都不知道

#include <bits/stdc++.h>

int main() {
    int T; std::cin >> T; while(T--) {
        std::string s; std::cin >> s;
        int i = 0;
        while(s[i] != ':') i++;
        std::cout << s.substr(0, i) << char(10);
        i += 3;
        int pre = i;
        while(s[i] != '/') i++;
        std::cout << s.substr(pre, i - pre) << char(10);

        for(;;) {
            pre = i + 1;
            if(pre >= s.size()) break;
            bool flag = false;
            i = pre;
            while(s[i] != '/') {
                if(s[i] == '=') flag = true;
                i += 1;
            }
            if(flag) std::cout << s.substr(pre, i - pre) << char(10);
        }
    }
    return 0;
}

成長,生命,幸福

很有趣的一個題

首先要觀察到一個重要的性質,即一棵樹成長一次後每個點的度數一定是 \(1/2/3\)

度數為 \(1\) 的一定是葉子不用管,度數為 \(2\) 的每次會分裂出兩個度數為 \(2\) 的,度數為 \(3\) 的每次會分裂出一個度數為 \(3\) 的和兩個度數為 \(2\)

考慮用一個二元組 \((x,y)\) 表示一條有 \(x\) 個度數為 \(2\) 的點,\(y\) 個度數為 \(3\) 的點的路徑,則進行 \(m-1\) 次成長後其貢獻為 \(2^{m-1}\times x+(2^m-1)\times y\)

不難發現在模意義下不能直接比較兩個二元組的大小,但我們發現這兩個係數在 \(m\) 增大時會無限趨近於 \(\frac{1}{2}\)

因此當 \(m\le 20\) 時,可以直接用對應的係數帶進去比較;否則直接把係數定為 \(\frac{1}{2}\) 算出最大的 \((x,y)\) 再計算答案即可

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=100005,mod=1e9+7;
int t,n,m,x,y,A,B; vector <int> v[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
struct ifo
{
    int x,y;
    inline ifo(CI X=0,CI Y=0)
    {
        x=X; y=Y;
    }
    inline LL val(void)
    {
        return 1LL*A*x+1LL*B*y;
    }
    friend inline ifo operator + (const ifo& A,const ifo& B)
    {
        return ifo(A.x+B.x,A.y+B.y);
    }
}ans,f[N],mx[N],smx[N];
inline void DFS(CI now=1,CI fa=0)
{
    if (v[now].size()>=2) f[now]=ifo(2,(int)v[now].size()-2); else f[now]=ifo();
    mx[now]=smx[now]=ifo();
    for (auto to:v[now]) if (to!=fa)
    {
        DFS(to,now);
        if (f[to].val()>mx[now].val()) smx[now]=mx[now],mx[now]=f[to];
        else if (f[to].val()>smx[now].val()) smx[now]=f[to];
    }
    if (f[now].val()+mx[now].val()+smx[now].val()>ans.val())
    ans=f[now]+mx[now]+smx[now]; f[now]=f[now]+mx[now];
}
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) v[i].clear();
        for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
        if (m<=20) A=1<<m-1,B=(1<<m)-1; else A=1,B=2;
        ans=ifo(); DFS(); A=quick_pow(2,m-1); B=(quick_pow(2,m)-1+mod)%mod;
        printf("%d\n",(ans.val()%mod+2)%mod);
    }
    return 0;
}

強攻計策

不可做題,棄療


女神的睿智

純純的簽到

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int t; char s[10];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%s",s+1);
        if (s[1]==s[5]) printf("%c\n",s[1]); else
        {
            int c[2]={0,0};
            for (RI i=1;i<=8;++i)
            {
                if (s[i]==s[1]) ++c[0];
                if (s[i]==s[5]) ++c[1];
            }
            if (c[0]==c[1]) puts("N");
            else printf("%c\n",c[0]>c[1]?s[1]:s[5]);
        }
    }
    return 0;
}

在 A 裡面找有 C 的 B

題意都不知道的字串,被徐神一眼秒了,好像是個 AC 自動機

#include <bits/stdc++.h>

struct node_t {
    int go[26];
    int fail, tag;

    node_t() { memset(this, 0, sizeof(node_t)); }
};

auto build_acam(
    const std::vector<std::string> &s
) -> std::tuple<
    std::vector<node_t>,
    std::vector<int>,
    std::vector<int>
> {
    std::vector<node_t> res = { node_t() };
    std::vector<int>    pos = {};
    for(auto s: s) {
        int cur = 0;
        for(auto c: s) {
            int u = c - 'a';
            if(!res[cur].go[u]) {
                res[cur].go[u] = res.size();
                res.push_back(node_t());
            }
            cur = res[cur].go[u];
        }
        pos.push_back(cur);
    }

    std::vector<int> q; int ql = 0;
    for(int i = 0; i < 26; ++i) if(res[0].go[i])
        q.push_back(res[0].go[i]);

    while(ql < q.size()) {
        int hd = q[ql++];

        for(int i = 0; i < 26; ++i)
            if(res[hd].go[i])
                res[res[hd].go[i]].fail = res[res[hd].fail].go[i],
                q.push_back(res[hd].go[i]);
            else
                    res[hd].go[i]       = res[res[hd].fail].go[i];
    }
    return { res, pos, q };
}

std::vector<int> work() {
    int n; std::cin >> n;
    std::string a, c;
    std::vector<std::string> b1(n);
    std::vector<std::string> b2(n);

    std::cin >> a >> c;
    for(int i = 0; i < n; ++i)
        std::cin >> b1[i] >> b2[i];
    
    std::vector<bool> is_ans(n, true);

    {
        auto [acam, pos, q] = build_acam(b1);

        std::reverse(q.begin(), q.end());

        for(int i = 0, cur = 0; i < a.size(); ++i) {
            int u = a[i] - 'a';
            cur = acam[cur].go[u];
            acam[cur].tag = 1;
        }

        for(auto i: q) acam[acam[i].fail].tag |= acam[i].tag;
        for(int i = 0; i < n; ++i) is_ans[i] = is_ans[i] && acam[pos[i]].tag;
    }

    {
        auto [acam, pos, q] = build_acam({c});

        for(auto pos: pos) acam[pos].tag = 1;
        for(auto q: q) acam[q].tag |= acam[acam[q].fail].tag;

        for(int i = 0; i < n; ++i) {
            int cur = 0;
            bool flag = false;
            for(auto c: b2[i]) {
                cur = acam[cur].go[c - 'a'];
                flag = flag || acam[cur].tag;
            }

            is_ans[i] = is_ans[i] && flag;
        }
    }

    std::vector<int> ans;
    for(int i = 0; i < n; ++i) if(is_ans[i]) ans.push_back(i);
    return ans;
}

int main() {
    std::ios::sync_with_stdio(false);

    int T; std::cin >> T; while(T--) {
        std::vector<int> ans = work();
        if(ans.empty()) std::cout << char(10);
        for(int i = 0; i < ans.size(); ++i)
            std::cout << ans[i] + 1 << char(i == ans.size() - 1 ? 10 : 32);
    }

    return 0;
}

圖計算

唉經典原題大戰,但這次是沒做過的原題

考慮如何判斷兩個點 \(x,y\)\(d+1\) 張圖內都連通,我們可以給每個點求出一個長為 \(d+1\) 的向量,其中第 \(i\) 個元素代表該點在第 \(i\) 個圖內的祖先,此時 \(x,y\) 連通當且僅當它們對應的向量相同

用 Hash 維護每個點的向量,每次修改時在對應的圖中啟發式合併一下,將更新父親節點的點對應的 Hash 值一併修改即可

總複雜度 \(O(k\log^2 n+m\times d)\)

#include<cstdio>
#include<iostream>
#include<random>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
typedef unsigned long long u64;
const int N=50005;
mt19937 rng;
uniform_int_distribution <u64> dist(0,1ull<<63);
int t,n,m,d,k,x[N<<1],y[N<<1]; u64 hsh[N];
long long ans; map <u64,int> rst;
inline void upt(const u64& x,CI y)
{
    ans-=1LL*rst[x]*(rst[x]-1)/2LL;
    rst[x]+=y;
    ans+=1LL*rst[x]*(rst[x]-1)/2LL;
}
struct DSU
{
    int fa[N]; vector <int> v[N]; u64 seed;
    inline void init(CI n)
    {
        seed=dist(rng);
        for (RI i=1;i<=n;++i) fa[i]=i,v[i]={i},hsh[i]+=u64(i)*seed;
    }
    inline void merge(int x,int y)
    {
        x=fa[x]; y=fa[y];
        if (x==y) return;
        if (v[x].size()<v[y].size()) swap(x,y);
        for (auto u:v[y])
        {
            fa[u]=x; upt(hsh[u],-1);
            hsh[u]-=u64(y)*seed;
            hsh[u]+=u64(x)*seed;
            upt(hsh[u],1); v[x].push_back(u);
        }
        v[y].clear();
    }
}G[105];
int main()
{
    //freopen("1012.in","r",stdin); freopen("my.out","w",stdout);
    for (scanf("%d",&t);t;--t)
    {
        RI i,j; scanf("%d%d%d%d",&n,&m,&d,&k);
        for (i=1;i<=n;++i) hsh[i]=0;
        for (i=1;i<=m;++i) scanf("%d%d",&x[i],&y[i]);
        for (i=1;i<=d+1;++i) G[i].init(n);
        for (ans=0,rst.clear(),i=1;i<=n;++i) upt(hsh[i],1);
        for (i=1;i<=d+1;++i)
        for (j=1;j<=m;++j) G[i].merge(x[j],y[j]);
        for (i=1;i<=k;++i)
        {
            int u,v,w; scanf("%d%d%d",&u,&v,&w);
            G[w].merge(u,v);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

Postscript

感覺現在一到後期就開始白蘭摸魚,一點作用都沒有,怎麼回事呢

相關文章