2021 CCPC网络赛重赛

1005:
二分题,题目不难,思路也简单清晰的,考的是细节
每个数首先如果刚好有我这个值,那么肯定直接拿,否则要靠前缀和拿多轮的话肯定是拿的轮数越少越好,那么就把模完以后相同的前缀放到一个\(vector\)里面,直接二分找到小于等于我的那个即可,负的情况就是大于等于我的那个。注意模0的情况。

#define int long long
const int maxn = 1e5 + 10, mod =  998244353;
map<int, int> mp, mp2;
int tot = 0;
struct node{
    int x, y;
    node(int x = 0, int y = 0) : x(x), y(y){}
    bool operator < (const node &a) const {
        return x < a.x;
    }
};
vector<node> v[maxn];
int a[maxn];
void run() {
    int n, m, pre = 0;
    cin >> n >> m;
    mp.clear();
    mp2.clear();
    tot = 0;
    for(int i = 1; i <= n; ++ i) cin >> a[i], pre += a[i];
    int sum = pre;
    pre = 0;
    if(sum == 0) {
        mp2[0] = 0;
        for(int i = 1; i <= n; ++ i) {
            pre += a[i];
            if(!mp2.count(pre)) mp2[pre] = i;
        }
        for(int i = 1; i <= m; ++ i) {
            int x; cin >> x;
            if(mp2.count(x))    cout << mp2[x] << '\n';
            else cout << -1 << '\n';
        }
        mp2.clear();
        return ;
    }
    mp[0] = ++tot;
    v[1].push_back({0, 0});
    mp2[0] = 0;
    //if(sum >= 0) {
        for(int i = 1; i <= n; ++ i) {
            pre += a[i];
            int now = pre % sum;
            if(now < 0) now += abs(sum);//assert(now >= 0)!
            if(!mp.count(now))  mp[now] = ++ tot;
            if(mp2.count(pre))  continue;
            int id = mp[now];
            v[id].push_back({pre, i});
            mp2[pre] = i;
        }
        for(auto it : mp)   if(v[it.second].size())    sort(v[it.second].begin(), v[it.second].end());
        for(int i = 1; i <= m; ++ i) {
            int x; cin >> x;
            int now = x % sum;
            if(now < 0) now += abs(sum);
            if(!mp.count(now)) {
                cout << "-1\n";
            }
            else {
                int id = mp[now];
                int pos = lower_bound(v[id].begin(), v[id].end(), x) - v[id].begin();
                if(sum >= 0) {
                    if(pos != v[id].size() && v[id][pos].x == x)   cout << v[id][pos].y << '\n';
                    else if(pos == 0)   cout << "-1\n";
                    else cout << v[id][pos - 1].y + (x - v[id][pos - 1].x) / abs(sum) * n << '\n';
                }
                else {
                    if(pos == v[id].size()) cout << "-1\n";
                    else cout << v[id][pos].y + (v[id][pos].x - x) / abs(sum) * n << '\n';
                }
            }
        }
        for(auto it : mp)   v[it.second].clear();
    //}
    return ;
}

1010:构造题
很清晰就是如果任意一个点到其他点都有两条以上不同路径,那么图上必定有环并且所有点都在环上
然后可以发现给的边是互不连通的,那么必然有方法构造出2n条边环的情况。关键是字典序最小。
可以并查集贪心拿取,因为每个点只连两条边,所以连玩的点就扔掉,复杂度可以\(O(n)\)

#define int long long
const int maxn = 1e5 + 10, mod =  998244353;
struct node{
    int x, y;
    node(int x = 0, int y = 0) : x(x), y(y){}
    bool operator < (const node &a) const {
        if(x != a.x)    return x < a.x;
        else return y < a.y;
    }
}a[maxn];
bool vis1[maxn], vis2[maxn];
int fa[maxn * 2];
int find(int x) {
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void un(int x, int y) {
    if(find(x) != find(y))
        fa[fa[x]] = fa[y];
}
void run() {
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; ++ i)    vis1[i] = vis2[i] = 0;
    for(int i = 1; i <= m; ++ i)    cin >> a[i].x >> a[i].y, vis1[a[i].x] = 1, vis2[a[i].y] = 1;
    cout << 2 * n - m << '\n';
    vector<int> v1, v2;
    for(int i = 1; i <= n; ++ i) {
        if(!vis1[i])    v1.push_back(i);
        if(!vis2[i])    v2.push_back(i);
    }
    vector<node> ans;
    vector<int> sz(2 * n + 1);
    //for(int i = 0; i < v1.size(); ++ i) a[++ m] = {v1[i], v2[i]}, ans.push_back({v1[i], v2[i]});
    for(int i = 1; i <= n; ++ i)    fa[i] = i, fa[i + n] = i + n;
    for(int i = 1; i <= m; ++ i)    un(a[i].x, a[i].y + n), sz[a[i].x] ++, sz[a[i].y + n] ++;
    deque<int> q;
    for(int i = 1; i <= n; ++ i)    q.push_back(i);
    for(int i = 1; i < n; ++ i) {
        vector<int> tmp;
        while(1) {
            int top = q.front();
            q.pop_front();
            if(find(i) != find(top + n)) {
                un(i, top + n), ans.push_back({i, top});
                sz[i] ++; sz[top + n] ++;
            }
            if(sz[top + n] < 2) tmp.push_back(top);
            if(sz[i] == 2)  break;
        }
        for(int j = tmp.size() - 1; j >= 0; -- j)   q.push_front(tmp[j]);
        /*int top = q.front();
        q.pop_front();
        if(find(i) != find(top + n))    un(i, top + n), ans.push_back({i, top});
        else {
            int tpp = q.front();
            q.pop_front();
            q.push_front(top);
            un(i, tpp + n), ans.push_back({i, tpp});
        }*/
    }
    ans.push_back({n, q.front()});  q.pop_front();
    if(q.size())    ans.push_back({n, q.front()});
    sort(ans.begin(), ans.end());
    for(auto it : ans)  cout << it.x << ' ' << it.y << '\n';
    return ;
}

1011:思维题
很巧妙的题目,真的感觉比前两题难度大,只能说太离谱
2021 CCPC网络赛重赛


#define int long long
const int maxn = 1e5 + 10, mod =  998244353;
int fa[maxn], d[maxn];
int find(int x) {
    if(x == fa[x])  return x;
    int y = fa[x];
    fa[x] = find(fa[x]);
    d[x] += d[y];
    return fa[x];
}
struct node{
    int x, id;
    node(int x = 0, int id = 0) : x(x), id(id){}
    bool operator < (const node &a) const {
        return x < a.x;
    }
}a[maxn];
int un(int x, int y) {
    find(x) != find(y);
    d[fa[x]] = 1;
    fa[fa[x]] = fa[y];
}
//按点权从小到大枚举结点 u,并将u作为所有与u 相连的(已经枚举过的)连通块的根。从而建立一棵新树,每个结点的深度(根的深度为 1)即是答案
vector<int> g[maxn];
int b[maxn];
void run() {
    int n; cin >> n;
    for(int i = 1, u, v; i < n; ++ i) {
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i = 1; i <= n; ++ i) {
        cin >> b[i];
        a[i] = {b[i], i};
        fa[i] = i;
        d[i] = 0;
    }
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++ i)
        for(auto it : g[a[i].id])
            if(b[it] < a[i].x)
                un(it, a[i].id);
    for(int i = 1; i <= n; ++ i) {
        find(i);
        cout << d[i] + 1 << '\n';
        g[i].clear();
    }
    return ;
}
``
上一篇:并查集经典:食物链


下一篇:P3320 [SDOI2015]寻宝游戏(数剖+定理