2023.12.12 校赛解题报告

发布时间 2023-12-13 22:02:09作者: Dita

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 个,实际就是消去最
下⾯⼀⾏。

Alt text

我们不妨再把点阵转化为⼀个⽹格图。

从原点开始,每⼈每次可以选择向右或向上移动⼀格,向右代表消去最左边⼀⾏,向上代表消去最下⾯⼀⾏。很容易发现,当⾛到⽹格图的边界(下图中的实线部分)时,所有糖刚好被取
完。

这样这个题就是⼀个简单的 \(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;
}