2024ICPC網路賽第一場題解(部分)

TJUHuangTao發表於2024-09-15

這一場基本純掛件,給隊友翻譯翻譯題面,幫隊友打打板子了,可惜最後40sL題衝了一個 \(O(\frac{n^3}{w})\) 的bitset最後wa了,所以下面的題解我也只能看著隊友程式碼說說大概,主要參考一下程式碼吧。

A

題意

給出32個隊伍的能力值,和比賽的規則,其中中國隊是第一個隊伍,問所有分組的情況下,中國隊的最好可能成績是什麼。
簽到,首先因為32個隊能力值都不同,所以只需要看中國隊的能力值排名,就決定了最終的答案。具體的話隊友應該是手玩了各個情況,寫了一堆if else過的。

程式碼

點選檢視程式碼
#include <bits/stdc++.h>
#define int long long
#define pb push_back
#define pii pair<int, int>
using namespace std;
bool debug = 1;
#define dbg(x)                                                   \
    if (debug)                                                   \
        cerr << BRIGHT_CYAN << #x << COLOR_RESET << " = " << (x) \
             << NORMAL_FAINT << COLOR_RESET << endl;
const string COLOR_RESET = "\033[0m", BRIGHT_CYAN = "\033[1;36m",
             NORMAL_FAINT = "\033[0;2m";

void solve() {
    int a[32];
    int de = 0;
    for (int i = 0; i < 32; i++) {
        cin >> a[i];
        if (a[i] <= a[0])
            de++;
    }
    if (de == 32) {
        cout << 1;
    } else if (de >= 28) {
        cout << 2;
    } else if (de >= 14) {
        cout << 4;
    } else if (de >= 7) {
        cout << 8;
    } else if (de >= 3) {
        cout << 16;
    } else
        cout << 32;

    cout << endl;
}

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

//__builtin_popcountll()
// cout<<fixed<<setprecision(2);

C

題意

給定 \(n\) 個限制 \((l_i, r_i)\), 問長度為 \(n\) 的全排列中,且滿足第 \(i\) 個數位於區間 \((l_i, r_i)\)內的排列數個數的奇偶性。

首先題目既然只問的奇偶性,就說明不太可能暴力求出來真正的總個數。然後隊友畫畫圖,最後猜了個結論,對每個限制 \((l_i, r_i)\) 建一條邊 \(l-1, r\),最後看建出來的圖是不是一棵樹。瞪眼法秒了,具體原理還不太清楚,看群裡別人說好像跟什麼滿秩,線性代數什麼的知識有關。

程式碼

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define pb push_back
#define pii pair<int,int>
using namespace std;
bool debug = 1;
#define dbg(x) if(debug) cerr<<BRIGHT_CYAN<<#x<<COLOR_RESET<<" = "<<(x)<<NORMAL_FAINT<<COLOR_RESET<<endl;
const string COLOR_RESET = "\033[0m", BRIGHT_CYAN = "\033[1;36m", NORMAL_FAINT = "\033[0;2m";


void solve(){
    int n;
    cin >> n;
    int sum = 0;
    vector<int> a[n + 1]{};
    vector<int> vis(n+1);
    for (int i = 1; i <= n;i++)
    {
        int u, v;
        cin >> u >> v;
        u--;
        a[u].pb(v);
        a[v].pb(u);
    }
    function<void(int)> dfs = [&](int u) {
        if(vis[u]==0)
            sum++, vis[u] = 1;
        for(auto p:a[u]){
            if(vis[p]==0)
                dfs(p);
        }
    };
    dfs(0);
    if(sum==n+1)
        cout << 1;
    else
        cout << 0;
    cout << '\n';
}


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

//__builtin_popcountll()
// cout<<fixed<<setprecision(2);

F

題意

給一個陣列,每次操作選擇一個區間,滿足區間內的數不能完全相同,然後將區間裡的數全都改為區間最大值,問最多能操作多少次

首先容易去考慮,對於每個數,利用單調棧求出它作為區間內唯一最大值的範圍,那麼操作的時候,就可以從小到大依次操作這些區間,然後基本上就做完了,具體的式子可能還得推推,看程式碼似乎就是把這樣的區間長度加起來。

程式碼

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define pb push_back
#define pii pair<int,int>
using namespace std;
bool debug = 1;
#define dbg(x) if(debug) cerr<<BRIGHT_CYAN<<#x<<COLOR_RESET<<" = "<<(x)<<NORMAL_FAINT<<COLOR_RESET<<endl;
const string COLOR_RESET = "\033[0m", BRIGHT_CYAN = "\033[1;36m", NORMAL_FAINT = "\033[0;2m";

const int N = 2e5 + 999;
int n, a[N], stk[N], top, l[N], r[N];
void solve(){
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    stk[top = 0] = 0;
    for (int i = 1; i <= n; i++){
        while(top && a[i] > a[stk[top]])
            top--;
        l[i] = stk[top] + 1;
        stk[++top] = i;
    }
    stk[top = 0] = n + 1;
    a[n + 1] = 0;
    for (int i = n; i >= 1; i--){
        while(top && a[i] > a[stk[top]])
            top--;
        r[i] = stk[top] - 1;
        if(a[r[i] + 1] == a[i])
            r[i] = i;
        stk[++top] = i;
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
        ans += r[i] - l[i];
    cout << ans << '\n';
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    cin>>t;
    while(t--)
        solve();
    return 0;
}

//__builtin_popcountll()
// cout<<fixed<<setprecision(2);

G

題意

給一個長度 \(2\times 10^3\) 的陣列 \(a[n]\), 可以得到一個 \(n \times n\) 的二維陣列 \(b[i][j]\), 表示陣列\(a\) 在區間 \([i, j]\) 的中位數。再由陣列 \(b[n][n]\) 得到一個二維陣列 \(c[i][j]\) 表示陣列 \(b[n][n]\)\((i, i) to (j, j)\) 這個矩形範圍內的中位數。最後問你陣列 \(c[n][n]\) 的中位數是多少。

首先看到中位數很容易去考慮二分答案後,轉01矩陣。那麼這題同理,首先因為 \(n\) 很小,最終答案肯定是陣列 \(a[n]\) 的某個數,只要在這些數里面二分,然後陣列 \(b[i][j]\) 直接 \(O(n^2)\) 的列舉,用字首和暴力算。對陣列 \(b[n][n]\) 再去二維字首和, \(O(n^2)\) 地暴力求出陣列 \(c\),最後判斷陣列 \(c[n][n]\) 裡1的個數。詳見程式碼

程式碼

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define pb push_back
#define pii pair<int,int>
using namespace std;
bool debug = 1;
#define dbg(x) if(debug) cerr<<BRIGHT_CYAN<<#x<<COLOR_RESET<<" = "<<(x)<<NORMAL_FAINT<<COLOR_RESET<<endl;
const string COLOR_RESET = "\033[0m", BRIGHT_CYAN = "\033[1;36m", NORMAL_FAINT = "\033[0;2m";

const int N = 2033;

int n, a[N], rk[N], s[N], b[N][N], c[N][N], sb[N][N];

bool check(int x){
    for (int i = 1; i <= n; i++){
        s[i] = s[i - 1] + (a[i] > x);
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++)
            b[i][j] = !(j - i + 1 - s[j] + s[i - 1] >= (j - i + 2) / 2);
    /*for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cout << b[i][j] << " \n"[j == n];*/
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            sb[i][j] = sb[i - 1][j] + sb[i][j - 1] + b[i][j] - sb[i - 1][j - 1];
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++){
            c[i][j] = (sb[j][j] - sb[i - 1][j] - sb[j][i - 1] + sb[i - 1][i - 1]);
            int sz = (j - i + 2) * (j - i + 1) / 2;
            if(sz - c[i][j] >= (sz + 1) / 2)
                cnt++;
        }
    return cnt >= (n * (n + 1) / 2 + 1) / 2;
}
void solve(){
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i], rk[i] = a[i];
    sort(rk + 1, rk + n + 1);
    int l = 1, r = n, mid;
    while(l < r){
        mid = (l + r) >> 1;
        if(check(rk[mid]))
            r = mid;
        else
            l = mid + 1;
    }
    cout << rk[l];
}


signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    while(t--)
        solve();
    return 0;
}

//__builtin_popcountll()
// cout<<fixed<<setprecision(2);

H

題意

括號序列長度為 \(2 * n\) ,每個位置給定了顏色 \(col[i]\), 並且有一個價值 \(val[i]\), 括號序列的價值定義為左括號所在位置的價值和。一個括號序列合法,需要括號序列本身是正則匹配的,還對每種顏色要求了,\(n\) 個左括號中對應顏色的數量至少達到 \(cnt[i]\) 個。問所有合法的括號序列的最大價值是多少。資料範圍比較小, \(n \leq 100\) 左右。

括號序列會想起來很久之前牛客的一次練習賽(https://ac.nowcoder.com/acm/contest/75768/F)裡面有跟網路流結合起來判合法括號,看到資料範圍挺小,就讓隊友想想能不能用網路流,最大流保證括號合法,費用流求最大價值。然後隊友比劃比劃後想出來建模了,把左括號數量下界的限制,轉化為右括號的數量上界限制,先把所有價值加起來,然後匹配上一個右括號,相當於去掉所在位置的價值,所以最小費用就可以讓最後剩的價值最大。具體建模可以看看程式碼裡面,而且有一個wa點,就是如果給的左括號數量本身不合法,可能建模會建出來負的邊權,需要特判掉-1.

程式碼

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define pb push_back
#define pii pair<int,int>
using namespace std;
bool debug = 1;
#define dbg(x) if(debug) cerr<<BRIGHT_CYAN<<#x<<COLOR_RESET<<" = "<<(x)<<NORMAL_FAINT<<COLOR_RESET<<endl;
const string COLOR_RESET = "\033[0m", BRIGHT_CYAN = "\033[1;36m", NORMAL_FAINT = "\033[0;2m";
const int N = 1009;
int n, m, dist[N], head[N], flow[N], vis[N], S, T, tot, pre[N];
struct Edge{
    int v, rest, p, nxt;
} edge[N * 40];
typedef long long ll;
int col[N], cnt[N], val[N];
bool SPFA() {
    for (int i = 1; i <= n + m + 10; i++)
        dist[i] = flow[i] = 1e16, vis[i] = 0;
    queue<int> q;
    q.push(S), dist[S] = 0;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = head[u]; i; i = edge[i].nxt) {
            if (edge[i].rest && dist[edge[i].v] > dist[u] + edge[i].p) {
                dist[edge[i].v] = dist[u] + edge[i].p;
                pre[edge[i].v] = i;
                flow[edge[i].v] = min(flow[u], edge[i].rest);
                if (!vis[edge[i].v])
                    q.push(edge[i].v), vis[edge[i].v] = 1;
            }
        }
    }
    return dist[T] < 1e15;
}
void maxflow() {
    ll ans = 0, f = 0;
    //cout << "maxflow" << endl;
    while (SPFA()) {
        //cout << "SPFA" << endl;
        //cout << flow[T] << " " << dist[T] << endl;
        f += flow[T];
        ans += flow[T] * dist[T];
        int x = T;
        while (x != S) {
            int y = pre[x];
            edge[y].rest -= flow[T];
            edge[y ^ 1].rest += flow[T];
            x = edge[y ^ 1].v;
        }
    }
    if(f < n / 2)
        cout << "-1\n";
    else {
        ans = -ans;
        for (int i = 1; i <= n; i++)
            ans += val[i];
        cout << ans << '\n';
    }    
}
void add(int u, int v, int c, int w, int t = 1){
    edge[++tot] = (Edge){v, c, w, head[u]};
    head[u] = tot;
    if(t)
        add(v, u, 0, -w, 0);
}
void solve(){
    cin >> n >> m;
    for(int i = 1; i <= m; i++)
        cin >> cnt[i], cnt[i] = -cnt[i];
    n *= 2;
    for (int i = 1; i <= n; i++){
        cin >> col[i];
        cnt[col[i]]++;
    }
    for (int i = 1; i <= n; i++)
        cin >> val[i];
    //cout << "finish" << endl;
    memset(head, 0, sizeof head);
    S = 1, T = 2;
    tot = 1;
    add(S, n + 2, n / 2, 0);
    for (int i = n; i > 1; i--)
        add(i + 2, i + 1, (i - 1) / 2, 0), add(i + 2, n + 2 + col[i], 1, val[i]);
    add(1 + 2, n + 2 + col[1], 1, val[1]);
    bool flag = 1;
    for (int i = 1; i <= m; i++){
        add(n + 2 + i, T, cnt[i], 0);
        if(cnt[i] < 0)
            flag = 0;
    }
    if(flag) maxflow();
    else
        cout << "-1\n";
}


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

//__builtin_popcountll()
// cout<<fixed<<setprecision(2);

M

題意

給出每個提交,包含隊伍名、題號、透過情況,問哪個題透過的隊伍數最多

整場就這個題是我寫的/(ㄒoㄒ)/~~,直接用STL暴力對每個題號的透過隊伍名字放set去重,然後遍歷map已經是按字典序從小到大,然後判斷一下就行。

程式碼

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long 
#define pb push_back
#define pii pair<int,int>
using namespace std;
bool debug = 1;
#define dbg(x) if(debug) cerr<<BRIGHT_CYAN<<#x<<COLOR_RESET<<" = "<<(x)<<NORMAL_FAINT<<COLOR_RESET<<endl;
const string COLOR_RESET = "\033[0m", BRIGHT_CYAN = "\033[1;36m", NORMAL_FAINT = "\033[0;2m";


void solve(){
    int n; cin >> n;
    //teamA A accepted
    map<string, set<string>> mp;
    for (int i = 1; i <= n; i++) {
        string a, b, c;
        cin >> a >> b >> c;
        if (c[0] == 'a') {
            mp[b].insert(a);
        }
    }
    int mx = 0;
    string ans;
    for (auto it : mp) {
        if (it.second.size() > mx) {
            mx = it.second.size();
            ans = it.first;
        }
    }
    cout << ans << "\n";
}


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

//__builtin_popcountll()
// cout<<fixed<<setprecision(2);

L (參考思路)

題意

\(n\) 個點,恰好有一個點沒有奶牛,其他都有奶牛,還有 \(l\) 個按鈕,每個按鈕按下後,可以控制點 \(i\) 處的奶牛移動到 \(t_i\) 位置,只有保證奶牛不能移動到同一個點才能去按按鈕。還有 \(q\) 次詢問,每次詢問,初始沒有點的奶牛在 \(a\) 處,最終想要讓 \(b\) 點沒有奶牛,並且僅使用前 \(c\) 個按鈕,問能否實現,用0/1回答。

首先注意到,一個按鈕如果能使用,要麼移動的到的 \(t_i\) 恰好是一個排列,要麼恰有2個 \(t_i\) 相同,且洞位於其中一個位置,否則按按鈕必會使奶牛相撞。所以,按一次按鈕,相當於讓洞移動,而詢問相當於問洞從 \(a\) 出發,到達 \(b\),我們就去考慮按按鈕對於沒有奶牛的洞的影響。對於排列的情況,相當於有一個置換環,因為可以無限次使用按鈕,所以同一個環上的點一定可以互相到達。而第二種情況,相當於兩條邊,(方向還沒想好),因為洞只能在重複的那兩個點之一。詢問相當於問操作前 \(c\) 個按鈕,讓洞從 \(a\) 移動到 \(b\) 點。所以可以對詢問離線,維護每個點到其他點的連通性,但是一直沒想到好的維護方法,最後想用bitset衝一下,但是時間來不及了,過樣例只剩40s直接交了,賽後看到wa了。不過也知道了是因為前面兩條邊那個情況應該哪個點往哪個點建邊這個沒考慮清楚,還是前面做太慢了這題沒做出來可惜。

相關文章