2024ICPC网络赛第一场题解(部分)

这一场基本纯挂件,给队友翻译翻译题面,帮队友打打板子了,可惜最后40sL题冲了一个 O(n3w) 的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 个限制 (li,ri), 问长度为 n 的全排列中,且满足第 i 个数位于区间 (li,ri)内的排列数个数的奇偶性。

首先题目既然只问的奇偶性,就说明不太可能暴力求出来真正的总个数。然后队友画画图,最后猜了个结论,对每个限制 (li,ri) 建一条边 l1,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×103 的数组 a[n], 可以得到一个 n×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(n2) 的枚举,用前缀和暴力算。对数组 b[n][n] 再去二维前缀和, O(n2) 地暴力求出数组 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

题意

括号序列长度为 2n ,每个位置给定了颜色 col[i], 并且有一个价值 val[i], 括号序列的价值定义为左括号所在位置的价值和。一个括号序列合法,需要括号序列本身是正则匹配的,还对每种颜色要求了,n 个左括号中对应颜色的数量至少达到 cnt[i] 个。问所有合法的括号序列的最大价值是多少。数据范围比较小, n100 左右。

括号序列会想起来很久之前牛客的一次练习赛(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 处的奶牛移动到 ti 位置,只有保证奶牛不能移动到同一个点才能去按按钮。还有 q 次询问,每次询问,初始没有点的奶牛在 a 处,最终想要让 b 点没有奶牛,并且仅使用前 c 个按钮,问能否实现,用0/1回答。

首先注意到,一个按钮如果能使用,要么移动的到的 ti 恰好是一个排列,要么恰有2个 ti 相同,且洞位于其中一个位置,否则按按钮必会使奶牛相撞。所以,按一次按钮,相当于让洞移动,而询问相当于问洞从 a 出发,到达 b,我们就去考虑按按钮对于没有奶牛的洞的影响。对于排列的情况,相当于有一个置换环,因为可以无限次使用按钮,所以同一个环上的点一定可以互相到达。而第二种情况,相当于两条边,(方向还没想好),因为洞只能在重复的那两个点之一。询问相当于问操作前 c 个按钮,让洞从 a 移动到 b 点。所以可以对询问离线,维护每个点到其他点的连通性,但是一直没想到好的维护方法,最后想用bitset冲一下,但是时间来不及了,过样例只剩40s直接交了,赛后看到wa了。不过也知道了是因为前面两条边那个情况应该哪个点往哪个点建边这个没考虑清楚,还是前面做太慢了这题没做出来可惜。

posted @   TJUHuangTao  阅读(3448)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示