2023.12.12 校赛解题报告
7-1 点菜
题面
Congruent 和 ComistryMo 两个人正在饭店吃饭,饭店里面一共有 \(n\) 道菜,每道菜有一个价格 \(a_i\)
\((1≤i≤n)\)。他俩会在饭店按顺序点 \(k\) 道菜(可以不连续,保证相对顺序即可), 假设点的菜序列为\(S_1−S_k\)。 他们约定:一个人付所有奇数下标已点菜品价格的最大值,即 \(Max(S_1,S_3,S_5...)\) 另一个付所有偶数下标已点菜品价格的最大值,即\(Max(S_2,S_4,S_6 ...)\) 由于他们关系很好,都希望另一个付的更少。所以你需要帮助他们两求出 \(Min(Cost_a,Cost_b)\) 其中 \(Cost_a\) 为 Congruent 付的钱,\(Cost_b\) 为 ComistryMo 付的钱。
数据范围
\(2 \leq k \leq n \leq 2 \times 10^5\), \(0 \leq a_i \leq 10^9\)
Solution
这个题主要在理解题面上,大致意思就是选一个子序列,保证 \(Min(Cost_a,Cost_b)\) 最小。
二分答案(二分出最小值)Check 中分“奇序列”和“偶序列”进行判断,选出的序列的要求是:奇序列的奇数项一定小于等于枚举的答案,并且两个奇数项之间一定会选一个;偶序列的偶数项一定小于等于枚举的答案,并且两个偶数项之间一定会选一个;
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e5 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, k, a[MAXN], l, r;
bool check(int x) {
int cnt = 0;
for(int i = 1; i <= n; i++) {
if(a[i] <= x) {
if(i != n) cnt += 2;
else cnt++;
i++;
}
}
if(cnt >= k) return true;
cnt = 1;
for(int i = 2; i <= n; i++) {
if(a[i] <= x) {
if(i != n) cnt += 2;
else cnt++;
i++;
}
}
if(cnt >= k) return true;
return false;
}
signed main() {
l = 1e9 + 9, r = 0;
n = read(), k = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
l = min(l, a[i]), r = max(r, a[i]);
}
int ans = a[1];
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout<<ans;
}
7-3 数
题面
存在一个队列,现给出q个操作,每种操作均属于以下其中的一种:
1、往队列中插入一个数x,操作指令格式为
push x
2、查询队列中有多少个不同的数,需有输出(可能为 0 ),操作指令格式为
hmy
3、查询队列中数x有多少个,需有输出(可能为0),操作指令格式为
hmx x
4、删除队列中的数x,如果有多个则只删除其中的任意一个,如果没有则该指令不执行自动跳过。操作指令格式为
erase x
5、清空队列,操作指令格式为
clr
一共有 \(q\) 次操作。
数据范围
\(1 \leq q \leq 10^5, 1 \leq x \leq 10^9\)
Solution
用 \(map\) 维护就好,对于查找不同元素,直接用 \(cnt\) 维护就好了。
Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 6;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return x * f;
}
int q, cnt;
char s[50];
map<int, int>st;
int main() {
int q = read();
for(int i = 1; i <= q; i++) {
scanf("%s", s + 1);
int x;
if(s[1] == 'p') {
x = read();
if(st[x] == 0) cnt++;
st[x]++;
}
if(s[3] == 'y') {
cout<<cnt<<"\n";
}
if(s[3] == 'x') {
x = read();
cout<<st[x]<<"\n";
}
if(s[1] == 'e') {
x = read();
if(st[x] > 0) st[x]--, cnt--;
}
if(s[1] == 'c') {
st.clear();
cnt = 0;
}
}
return 0;
}
7-4 打牌!
Congruent 和 int256 在火车上闲着无聊,于是他们决定打卡牌游戏。int256 手里有 \(n\) 张牌, Congruent 手里有 \(m\) 张牌 ,每张牌有两个数值 \(ATK\)(攻击力) 和 \(HP\)(血量)。
由于 int256 是高手,所以他的牌 \(ATK\) 均严格大于 \(HP(ATK>HP)\),但是 Congruent 是新手,所以他手里的牌都是 \(HP\) 严格大于 \(ATK\) 的 \((HP>ATK)\)。
现在你需要帮帮 Congruent 算出他手里的 \(m\) 张牌分别能打赢 int256 多少张牌。
现定义 A 牌打赢 B 牌 :A 的攻击力大于等于 B 的血量并且 A 的血量大于等于 B 的攻击力。
数据范围
\(1\leq n, m\leq 10^5, ~~0 \leq ATK, HP\leq 10^9\)
Solution
二维偏序问题,对 int256 的 HP 与 Congruent 的 ATK 离散化,将第一维排序,第二维用树状数组维护就好。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e5 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m, tmp[MAXN], tot;
struct Node{int x, y, id, kil;}a[MAXN], b[MAXN];
namespace Seg{
int T[MAXN];
void add(int x, int k) {
for(int i = x; i < MAXN; i += i & (-i)) {
T[i] += k;
}
}
int Query(int x) {
int ret = 0;
for(int i = x; i; i -= i & (-i)) {
ret += T[i];
}
return ret;
}
}
using namespace Seg;
bool cmp1(Node i, Node j) {
return i.x > j.x;
}
bool cmp2(Node i, Node j) {
return i.y > j.y;
}
bool cmp3(Node i, Node j) {
return i.id < j.id;
}
signed main() {
n = read();
for(int i = 1; i <= n; i++){
a[i].x = read(), a[i].y = read();
tmp[++tot] = a[i].y;
}
m = read();
for(int i = 1; i <= m; i++) {
b[i].x = read(), b[i].y = read();
b[i].id = i;
tmp[++tot] = b[i].x;
}
sort(tmp + 1, tmp + tot + 1);
int cnt = unique(tmp + 1, tmp + tot + 1) - tmp - 1;
for(int i = 1; i <= n; i++) {
a[i].y = lower_bound(tmp + 1, tmp + cnt + 1, a[i].y) - tmp;
add(a[i].y, 1);
}
for(int i = 1; i <= m; i++) {
b[i].x = lower_bound(tmp + 1, tmp + cnt + 1, b[i].x) - tmp;
}
sort(a + 1, a + n + 1, cmp1), sort(b + 1, b + m + 1, cmp2);
int cur = 1, ans = 0;
for(int i = 1; i <= m; i++) {
while(b[i].y < a[cur].x && cur <= n) {
add(a[cur].y, -1); cur++;
}
b[i].kil = Query(b[i].x);
}
sort(b + 1, b + m + 1, cmp3);
for(int i = 1; i <= m; i++) {
cout<<b[i].kil<<endl;
}
return 0;
}
7-5 方
给出一个矩阵,矩阵长为 \(m\) 宽为 \(n\),求矩阵中最大的 mate 值是多少。
mate 值:在矩阵中,长宽均为 \(u\) 的正方形内所包含的值中最小值为v,则该正方形的 mate 值为 \(u \times u \times v\)
数据范围
\(1 \leq m \leq 1000,1 \leq n \leq 1000,1 \leq x \leq 10\)
Solution
⼀个基础的⼆维 dp,由数据范围可知,枚举矩阵中可能出现的最⼩值 v,然后通过 dp 的⽅式计算出以点 A(x,y) 作最右下⻆且内部最⼩值为 v 的正⽅形的最⻓边是多少,
转移⽅程如下:
f[i][j][k]=min(min(f[i-1][j][k],f[i][j-1][k]),f[i-1][j-1][k])+1;
接着计算所有出现的最⻓边中能贡献的最⼤值,最后计算所有最⼩值 v 对应的 mate 值中最⼤值
是多少,输出答案
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1010;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m, ans;
int a[MAXN][MAXN], f[MAXN][MAXN][11];
signed main() {
n = read(), m = read();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) a[i][j] = read();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int k = 1; k <= a[i][j]; k++)
f[i][j][k] = min(f[i - 1][j][k], min(f[i][j - 1][k], f[i - 1][j - 1][k])) + 1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for (int k = 1; k <= 10; k++)
ans = max(ans, f[i][j][k] * f[i][j][k] * k);
cout<<ans;
return 0;
}
7-6 吃糖
题面
acco天天吃糖,他现在有 \(n\) 堆糖,每堆糖有 \(a_i\) 个糖果。
zxpeach 为了让 acco 吃更多糖,决定每次 acco 吃糖都给他一些钱,acco 有两种吃糖方式,他可以把糖最多的一堆糖全部吃掉,也可以把剩下的每一堆糖都吃一个。
每次 acco 吃完糖,zxpeach 会奖励 acco (x ^ y) 块钱,x 是剩下的糖果堆里最多的一堆糖的糖果数量,y 是还剩下的糖果堆数量,^是异或操作。
现在 acco 想知道每次最多能拿到多少钱?
数据范围
\(1 \leq n \leq 1000, ~~1 \leq a_i \leq 1000\)
Solution
不妨先按 \(a_i\) 从⼤到⼩排序。对于糖最多的⼀堆,只要不直接取完,那么这堆糖仍然是最多
的。
举个例⼦,对于 {a}={7,7,7,6,4,4,4,2,2},排完序后能够得到下图。
对于取⾛最多的⼀堆,实际就是消去最左边⼀⾏;对于取⾛每堆取⾛ 1 个,实际就是消去最
下⾯⼀⾏。
我们不妨再把点阵转化为⼀个⽹格图。
从原点开始,每⼈每次可以选择向右或向上移动⼀格,向右代表消去最左边⼀⾏,向上代表消去最下⾯⼀⾏。很容易发现,当⾛到⽹格图的边界(下图中的实线部分)时,所有糖刚好被取
完。
这样这个题就是⼀个简单的 \(dp\),设状态为 \(f[i][j]\) 表示⾛到第 \([i,j]\) 格的最⼤值,从左边和下边更新
即可。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1010;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, a[MAXN], b[MAXN], f[MAXN][MAXN], ans;
bool cmp(int x, int y) {return x > y;}
signed main() {
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= a[i]; j++) b[j]++;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= a[i]; j++) {
int ret = (a[i] - j + 1) ^ (b[j] - i + 1);
if(i > 1) f[i][j] = max(f[i][j], f[i - 1][j] + ret);
if(j > 1) f[i][j] = max(f[i][j], f[i][j - 1] + ret);
ans = max(ans, f[i][j]);
}
}
cout<<ans;
return 0;
}
7-8 列队游戏
题面
Zxpeach 正在进行冬季军训,教官和大家玩了一个游戏。
一开始,\(n\) 个人整齐的在操场上排列成一排,从左到右第 \(i\) 位同学的编号为 \(i\)。
每次教官会命令某一列排在另一列后方。并且会令某列中排名第 \(k\) 的学生出列,如果这名同学没有出列就要受到惩罚表演节目。Zxpeach 非常内向,于是 Zxpeach 编写了一个程序来计算他的实时位置来逃避节目表演,但是他觉得作为编程题来说,这也太简单了,于是他希望你编写程序查找某一时刻 \(i\) 号同学在他所在队列中的排名。
一共 \(q\) 次操作:
M x y 表示将 编号为 y 的同学所在列排在编号为 x 的同学所在列后方,若 x 和 y 所在队列相同,则不进行操作
U x 表示询问编号为 x 的同学在其队列中,从前往后排第几名
D x 表示询问编号为 x 的同学在其队列中,从后往前排第几名
数据范围
\(1 \leq n \leq 3\times 10^5,~ 1 \leq q \leq 1\times 10^5\)
Solution
带权路径压缩并查集,维护每个联通块的大小和每个点的深度。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 5e5 + 5;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int fa[MAXN], dep[MAXN], siz[MAXN], p[MAXN];
int find(int x) {
if(!fa[x]) return x;
int f = find(fa[x]);
dep[x] += dep[fa[x]];
return fa[x] = f;
}
void merge(int y, int x) {
int fx = find(x), fy = find(y);
if(fx == fy) return ;
fa[fy] = fx;
int l = p[fx];
find(l);
dep[fy] = dep[l] + 1;
p[fx] = p[fy];
siz[fx] += siz[fy];
}
signed main() {
for(int i = 1; i < MAXN; i++) p[i] = i, siz[i] = 1;
int n = read();
int T = read();
while(T--) {
char opt;
cin>>opt;
if(opt == 'M') {
int x = read(), y = read();
merge(y, x);
}
else if(opt == 'U') {
int x = read();
int fx = find(x);
cout<<dep[x] + 1<<endl;
}
else {
int x = read();
int fx = find(x);
cout<<siz[fx] - dep[x]<<endl;
}
}
return 0;
}
7-10 月老的烦恼
题面
月老 bronleaf 很烦脑,作为情人中继站的他每天都要给许多对情侣连红线。
但月老生活在二维,整个世界对他来说就是平面上的两条横线之间(众所周知,横线是无限长的),他知道每对情侣各自的位置(可能在横线上),但任意两对情侣之间不能有交集(连的红线不能交叉,也不能越出这个世界),否则会发生不好的情节()
月老想知道对于某一天的许多对情侣能否全部连线成功,但他懒得写代码了,于是想找你帮忙。
比如如下情况,每个人代表一个数字(在平面视作一个点)
相同的数(一对情侣)相连,
是能够全部一一相连的(可以是曲线)。
n,up,down,分别表示有n对情侣,世界的上界是up,下界是down。
数据范围
$ 1\leq n \leq 2 \times 10^{6}$
\(1 \leq up, down, x1, x2, y1, y2 \leq 10^{9}\)
Solution
对于只有⼀端在边界的点对或者没有端点在边界的点对不⽤处理,必定能连上且不影响其他点
对,因为这种点对⽆论怎么连都不会切割空间,所以先连这种点。
再考虑会切割空间的两种点对,两端分别在上下界的,和两端在同⼀边界的,这⾥考虑三种相
交的情况:
上下界点对和上下界点对:只要他们关于上界排完序过后下界顺序相同即可
上下界点对和同边点对:同边点对的区间不能包含任何上下界点对
同边点对:不能互相交叉
满⾜这些条件则可以全部连线
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 2e6 + 6;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, up, down;
struct Node{
int x, id, type;
bool operator < (const Node &rhs) const {
return rhs.x < x;
}
}a[MAXN], b[MAXN];
bool vis[MAXN];
int cnt1, cnt2;
stack<int>st, st2;
bool Check() {
for(int i = 1; i <= cnt1; i++) {
if(a[i].type == 1) {
if(vis[a[i].id]) {
if((int)st.top() != a[i].id) return false;
else st.pop();
}
else st.push(a[i].id), vis[a[i].id] = 1;
}
else if(st.size()) return false;
}
memset(vis, 0, sizeof vis);
for(int i = 1; i <= cnt2; i++) {
if(b[i].type == 1) {
if(vis[b[i].id]) {
if((int)st2.top() != b[i].id) return false;
else st2.pop();
}
else st2.push(b[i].id), vis[b[i].id] = 1;
}
else if(st2.size()) return false;
}
int now = 1;
for(int i = 1; i <= cnt1; i++) {
while(a[i].type == 1) i += 2;
while(b[now].type == 1) now += 2;
if(a[i].id != b[now].id) return false;
now++;
}
return true;
}
signed main() {
n = read(), up = read(), down = read();
for(int i = 1; i <= n; i++) {
int x1 = read(), y1 = read(), x2 = read(), y2 = read();
if((y1 != down && y1 != up) || (y2 != down && y2 != up)) continue;
Node u;
u.x = x1, u.id = i, u.type = 0;
if(y1 == y2) u.type = 1;
if(y1 == up) a[++cnt1] = u;
else b[++cnt2] = u;
u.x = x2;
if(y2 == up) a[++cnt1] = u;
else b[++cnt2] = u;
}
sort(a + 1, a + cnt1 + 1);
sort(b + 1, b + cnt2 + 1);
if(Check()) puts("YES");
else puts("NO");
}