2024江苏省大学生程序设计竞赛JSCPC题解(E、I、K、H)
E. Divide
首先,将一个数字
考虑将每个数的下标作为第一维度,其真实数值作为第二维度。则以上数组再转化为
可以发现,求
设
int n, m;
int a[N];
struct Node {
int lson, rson;
int cnt;
}tr[N * 17 * 18];
int root[N];
int c[N];
int idx;
int insert(int p, int l, int r, int x) {
int q = ++idx;
tr[q] = tr[p];
if (l == r) {
tr[q].cnt ++;
return q;
}
int mid = l + r >> 1;
if (x <= mid) tr[q].lson = insert(tr[p].lson, l, mid, x);
else tr[q].rson = insert(tr[p].rson, mid + 1, r, x);
tr[q].cnt = tr[tr[q].lson].cnt + tr[tr[q].rson].cnt;
return q;
}
int query(int q, int p, int l, int r, int k) {
if (l == r) return l;
int cnt = tr[tr[q].lson].cnt - tr[tr[p].lson].cnt;
int mid = l + r >> 1;
if (k <= cnt) return query(tr[q].lson, tr[p].lson, l, mid, k);
else return query(tr[q].rson, tr[p].rson, mid + 1, r, k - cnt);
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
for (int i = 1; i <= n; i ++) {
c[i] = c[i - 1];
int flag = 0;
while (1) {
// 这里注意要把所有第一维度为i的点都一次性加入到主席树中,并且顺着一个根节点root[i]加入。
if (flag == 0) {
root[i] = insert(root[i - 1], 0, 1e5, a[i]);
flag = 1;
}
else {
root[i] = insert(root[i], 0, 1e5, a[i]);
}
c[i] ++;
if (a[i] == 0) break;
a[i] = a[i] / 2;
}
}
while (m --) {
int l, r, k;
cin >> l >> r >> k;
if (k > c[r] - c[l - 1]) {
cout << 0 << '\n';
continue;
}
cout << query(root[r], root[l - 1], 0, 1e5, c[r] - c[l - 1] - k) << '\n';
}
}
I. Integer Reaction
这题给了个惊醒。看到求最小值最大或者最大值最小一般可以往二分上考虑。虽说知道这一点但是赛时一直认为不能二分。。。
考虑二分时check里面是判断操作后集合
如果是
如果是
至于check内部的贪心,我们只需在每次
int n;
int a[N];
int c[N];
bool check(int x) {
multiset<PII> s;
for (int i = 1; i <= n; i ++) {
if (!s.empty() && c[i] != (*s.begin()).second) {
auto pos = s.lower_bound(PII(x - a[i], -1));
if (pos == s.end()) return false;
else s.erase(pos);
}
else s.insert({a[i], c[i]});
}
return true;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
for (int i = 1; i <= n; i ++) {
cin >> c[i];
}
int l = 2, r = 2e8;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << '\n';
}
K. Number Deletion Game
考虑什么情况下当前局面者必赢。如果当前只有一个数,那么当前局面者可以选择
因为每次都只能选
如果当前最大值
而对方玩家由于只能删除一个
故如果先手最大值数目为奇数,他必胜;否则必输。
int n;
int a[(int)1e3 + 10];
void solve() {
cin >> n;
int max_v = 0;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
max_v = max(max_v, a[i]);
}
int cnt = 0;
for (int i = 1; i <= n; i ++) {
if (a[i] == max_v) cnt ++;
}
if (cnt & 1) cout << "Alice\n";
else cout << "Bob\n";
}
H. Real Estate Is All Around
小蓝卖出一套房子不贬值,小红卖出一套房子贬值1元,小绿卖出一套房子贬值
如果所有的房子再被小红、小蓝小绿卖出部分后,还剩一部分,那么我们其实可以把这剩下的一部分房子都存在小绿手中,因为放在小红或者小蓝手中的话可能会影响他们的卖房策略,但是放在小绿手里的话,因为小绿每次买房狂潮时会优先卖出自己手中房子中价值最大的,所以新放入的剩下的房子再小绿手中并不会被卖掉,因此不会影响小绿的卖房策略。
我们可以把不卖出理解为贬值
因为每个房子都会有自己的去向,不管贬值多少肯定是都要减去一个贬值的,如果不减去贬值的话就不符合题意。所以本问题就可以转化成一个最小费用最大流的模型了。
一种建图策略是:
- 建立源点
和汇点 ,建立所有房子, 向所有房子连一条容量为 ,费用为 的边,表示每个房子最多被卖 次;每个房子向 连一条容量为 ,费用为 的边,表示每个房子如果不卖的话贬值为 。这里建立了 个点, 条边。 - 建立每个助理的分时点
,其中 代表当前助理所在时间点, 代表当前助理是谁,对每个 ,向 连一条容量为 ,费用为 的边,表示这个助理手中的所有房子可以顺着时间推移传到下一个时间点。这里建立了 个点, 条边。 - 对每次储存机会,从
房子向 、 、 连接容量都为 、费用分别为 , 、 的边,表示这个房子最多只能由一个助理卖掉,贬值分别为 或 或 。这里建立了 条边。 - 对每次买房狂潮,对当前时刻的每个助理
向 连一条容量为 、费用为 的边,表示每个助理可以卖出最多一个房子。这里建了 条边。
最终点开
注意,虽然我们策略里面是不卖的房子交给小绿,但是实际上建图时是把不卖的房子连向了
const int N = 210 * 4, M = N * 11;
int n;
int a[N];
int h[N], e[M * 2], ne[M * 2], f[M * 2], w[M * 2], idx;
int q[N], incf[N], d[N], pre[N], st[N];
int S, T;
void addEdge(int a, int b, int c, int d) {
e[idx] = b, f[idx] = c, w[idx] = d, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, f[idx] = 0, w[idx] = -d, ne[idx] = h[b], h[b] = idx ++;
}
int getID(int x, int y) {
return 3 * (x - 1) + y;
}
bool spfa() {
int hh = 0, tt = 0;
memset(d, 0x3f, sizeof d);
memset(incf, 0, sizeof incf);
memset(st, 0, sizeof st);
q[tt ++] = S, d[S] = 0, incf[S] = INF;
while (hh != tt) {
int t = q[hh ++];
if (hh == N) hh = 0;
st[t] = 0;
for (int i = h[t]; ~i; i = ne[i]) {
int ver = e[i];
if (f[i] && d[ver] > d[t] + w[i]) {
d[ver] = d[t] + w[i];
pre[ver] = i;
incf[ver] = min(incf[t], f[i]);
if (!st[ver]) {
q[tt ++] = ver;
if (tt == N) tt = 0;
st[ver] = 1;
}
}
}
}
return incf[T] > 0;
}
void EK(int &flow, int &cost) {
flow = cost = 0;
while (spfa()) {
int t = incf[T];
flow += t, cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1]) {
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
}
void solve() {
cin >> n;
memset(h, -1, sizeof h);
idx = 0;
int cnt = 0;
int tot = 3 * n;
S = ++tot, T = ++tot;
int sum = 0;
for (int i = 1; i <= n - 1; i ++) {
for (int j = 1; j <= 3; j ++) {
addEdge(getID(i, j), getID(i + 1, j), INF, 0);
}
}
for (int i = 1; i <= n; i ++) {
int op;
cin >> op;
if (op == 1) {
cin >> a[++cnt];
sum += a[cnt];
addEdge(S, ++tot, 1, 0);
addEdge(tot, T, 1, a[cnt]);
addEdge(tot, getID(i, 1), 1, 1);
addEdge(tot, getID(i, 2), 1, (a[cnt] + 9) / 10);
addEdge(tot, getID(i, 3), 1, 0);
}
else {
for (int j = 1; j <= 3; j ++) {
addEdge(getID(i, j), T, 1, 0);
}
}
}
int flow, cost;
EK(flow, cost);
cout << sum - cost << '\n';
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效