蒲公英的约定 题解

· · 题解

题意回顾

给定平行志愿招生规则下每个人的报名志愿和中考分数,支持以下操作:

人数、学校数、操作数均不超过 3 \times 10^5 ,志愿总数不超过 10^6

分析

记人数、学校数、操作数、分数档位数和志愿的量级均为 n

考虑一次志愿录取计算的时间复杂度为 O(n \log n) ,瓶颈在于分数排序,其余复杂度为 O(n)

考虑如果直接用数据结构维护这个问题的话,很难操作,但是操作只是有减少名额,容易发现每个人录取的志愿位次必然越来越靠后。故我们只需要在对数时间内完成一个人志愿录取位次靠后的处理即可。

我们发现这个过程类似于一棵树上的搜索,对于减少名额的学校需要踢出分数靠后的学生;而对于这些学生的志愿录取位次向后推移可能会导致其他学校部分分数靠后的学生被踢出。所以实现学校踢出学生函数和学生找学校即可,注意每个学生影响范围必然是分数比他更低的学生所以每次要找到分数最高的录取结果需要改变的学生开始操作。

注意以下实现细节:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
using namespace std;
const int N = 3e5 + 5;
const int M = 1e6 + 5;
int n, m, q;
int t[N];//总录取名额
vector<int> g[N];//志愿列表
int o[N];//中考分数
set<int> scl[N];//学校分数段集合*
int tot[N];//学校录取人员总数*
struct node {
    int sc, ps;//分数及录取结果
    node(int scx, int psx) {
        sc = scx, ps = psx;
    }
};
bool operator<(node p1, node p2) {
    return (p1.sc == p2.sc) ? (p1.ps < p2.ps) : (p1.sc > p2.sc);
}
map<node, int> id;//对应录取人员组编号
vector<int> res[M];//录取人员集合*
int cur[N];//志愿论次编号*
vector<int> tmp[M];
int avi[N];
set<node> se;
void enter(int x, int k);
void FindSchool(int x) {
    for(int i = cur[x]; i < g[x].size(); i++) {
        cur[x] = i + 1;
        int school = g[x][i];
        bool ok = false;
        ok |= t[school] > tot[school];
        if(scl[school].size() > 0) ok |= o[x] >= *(scl[school].begin());
        if(ok) {
            enter(x, school);
            return;
        }
    }
}
void kick(int x) {
    set<int>::iterator it = scl[x].begin();
    while(it != scl[x].end()) {
        int score = *it;
        int idx = id[node(score, x)];
        if(tot[x] - res[idx].size() < t[x]) {
            break;
        }
        tot[x] -= res[idx].size();
        se.insert(node(score, x));
        it++;
    }
    if(it != scl[x].begin()) scl[x].erase(scl[x].begin(), it);
}
void enter(int x, int k) {
    scl[k].insert(o[x]);
    res[id[node(o[x], k)]].push_back(x);
    tot[k]++;
    kick(k);
}
int main() {
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i <= m; i++) scanf("%d", &t[i]), avi[i] = (bool)t[i];
    int u, v;
    for(int i = 1; i <= n; i++) {
        scanf("%d", &u);
        for(int j = 1; j <= u; j++) {
            scanf("%d", &v);
            g[i].push_back(v);
        }
        scanf("%d", &o[i]);
        tmp[o[i]].push_back(i);
    }
    int ct = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < g[i].size(); j++) {
            if(id.find(node(o[i], g[i][j])) == id.end()) {
                id[node(o[i], g[i][j])] = ++ct;
            }
        }
    }
    for(int i = M - 5; i >= 0; i--) {
        for(int j = 0; j < tmp[i].size(); j++) {
            int k = tmp[i][j];
            for(int p = 0; p < g[k].size(); p++) {
                int s = g[k][p];
                cur[k] = p + 1;
                if(avi[s]) {
                    enter(k, s);
                    if(tot[s] >= t[s]) avi[s] = 2, se.insert(node(s, 0));
                    break;
                }
            }
        }
        for(set<node>::iterator it = se.begin(); it != se.end(); it++) avi[it -> sc] = 0;
        se.clear();
    }
    for(int qi = 1; qi <= q; qi++) {
        scanf("%d%d", &u, &v);
        t[u] -= v;
        kick(u);
        int ans = 0;
        while(!se.empty()) {
            set<node>::iterator it = se.begin();
            int score = it -> sc;
            while(it != se.end() && (it -> sc) == score) {
                int idx = id[*it];
                ans += res[idx].size();
                for(int i = 0; i < res[idx].size(); i++) FindSchool(res[idx][i]);
                res[idx].clear();
                it++;
            }
            se.erase(se.begin(), it);
        }
        se.clear();
        printf("%d\n", ans);
    }
    return 0;
}

后记

每个六月,都是千千万万个中国人根据自己的所谓「 o 值」决定来很大程度上决定人生下一阶段走向的时刻。希望每个看到这里的人,都能在这两个六月有着满意的「 o 值」,在为梦想一步步地奋斗中,获得应得的回报,过上想要的生活。