暑假N天乐【比赛篇】 —— 2019牛客暑期多校训练营(第三场)
这场相对来说友好一点,本来前几天就补差不多了,然后忘记发了...
以下题解包括:\(A \ \ \ B \ \ \ F \ \ \ G \ \ \ H \ \ \ J\)
\(D\) 题队友补了,我也懒得想数学公式题,所以就请移步别的题解了。
比赛地址: https://ac.nowcoder.com/acm/contest/883#question
【A】 Graph Games 分块+随机化
给定 \(n\) 个点和 \(m\) 条边,定义 \(S(x)\) 是和 \(x\) 点邻接的所有点的集合。进行 \(q\) 次操作,分为两种:
- 翻转 \([l,r]\) 区间里边的状态,存在-->删边、不存在-->加边
- 询问 \(S(u)\) 是否等于 \(S(v)\)
其中 \(n \leq 1e^5 \ \ m \leq 2e^5 \ \ q \leq 2e^5\)。
由于需要满足整个邻接的集合都相同,如果每个点进行核对,这个复杂度就已经炸了。于是考虑给每一个点赋上一个随机数,利用随机数的异或进行判断是否集合相同,可以证明(大概)不会出现冲突的情况。
又因为区间巨大,需要利用分块的操作来降低复杂度,对于块内更新就直接更新即可;对于块间更新,那么就需要用到 \(lazy\) 标记,用 \(lazy == 1\) 表示当前块内需要被考虑。\(sum\) 数组存储每块里每个点待更新的权值,\(s\) 数组存储每个点单点更新的值即可。
建议配合代码理解。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 2e5+5;
const int sqrtn = 5e2+5;
int n, m;
int num;
int block;
int u[maxn];
int v[maxn];
int val[maxn]; // 随机权值(大概可以保证异或值不发生冲突【重复】)
int belong[maxn];
int l[sqrtn];
int r[sqrtn];
int lazy[sqrtn]; // 整块翻转标记
ll s[maxn]; // 每个点的异或值
ll sum[sqrtn][maxn];// 每块里每个点待更新的权值
void build() { // 分块建立
block = sqrt(m);
num = m / block;
if(m % block != 0) {
num ++;
}
for(int i = 1; i <= num; i++) {
l[i] = (i-1)*block + 1;
r[i] = i*block;
lazy[i] = 1; // 初始赋值 1 代表当前块可以使用
for(int j = 1; j <= n; j++) {
sum[i][j] = 0;
}
}
r[num] = m;
for(int i = 1; i <= m; i++) {
belong[i] = (i-1)/block+1;
}
for(int i = 1; i <= n; i++) {
s[i] = 0;
}
}
void update(int l, int r) { // 分块更新
if(belong[l] == belong[r]) {
for(int i = l; i <= r; i++) {
s[u[i]] ^= val[v[i]];
s[v[i]] ^= val[u[i]];
}
return ;
}
for(int i = l; belong[i] == belong[l]; i++) {
s[u[i]] ^= val[v[i]];
s[v[i]] ^= val[u[i]];
}
for(int i = r; belong[i] == belong[r]; i--) {
s[u[i]] ^= val[v[i]];
s[v[i]] ^= val[u[i]];
}
for(int i = belong[l]+1; i < belong[r]; i++) {
lazy[i] ^= 1; // 翻转
}
}
int main() {
srand(time(NULL));
for(int i = 0; i < maxn; i++) {
// 每个点赋予随机权值,加点删点都是异或操作
val[i] = rand() + 1;
}
int t;
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &m);
build(); // 按 m 分块
for(int i = 1; i <= m; i++) {
scanf("%d%d", &u[i], &v[i]);
sum[belong[i]][u[i]] ^= val[v[i]];
sum[belong[i]][v[i]] ^= val[u[i]];
}
int q;
scanf("%d", &q);
for(int i = 1; i <= q; i++) {
int f, x, y;
scanf("%d%d%d", &f, &x, &y);
if(f == 1) {
update(x, y);
}
else {
ll ans1 = s[x]; // 单点的情况
ll ans2 = s[y];
for(int i = 1; i <= num; i++) { // 整块的情况
if(lazy[i]) {
ans1 ^= sum[i][x];
ans2 ^= sum[i][y];
}
}
if(ans1 == ans2) {
printf("1");
}
else {
printf("0");
}
}
}
printf("\n");
}
return 0;
}
【B】 Crazy Binary String 贪心
给定一个 \(01\) 串,分别选择一个最长的子串和子序列,使其中 \(0\) 和 \(1\) 的个数相同。
对于子序列来说显然就是区间内 \(min(sum_1, sum_0)\);
对于子串来说需要维护前缀和,然后寻找第一次出现的相同值的位置。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e5+5;
char s[maxn];
map<int, int> mp;
int main() {
int n;
while(~scanf("%d", &n)) {
scanf("%s", s+1);
int a = 0, b;
mp.clear();
int x = 0, y = 0;
int cnt = 0;
for(int i = 1; i <= n; i++) {
if(s[i] == '0') {
x ++;
cnt --;
if(cnt == 0) {
a = max(a, i);
continue;
}
if(mp[cnt] == 0) {
mp[cnt] = i;
continue;
}
a = max(a, i-mp[cnt]);
}
else {
y ++;
cnt ++;
if(cnt == 0) {
a = max(a, i);
continue;
}
if(mp[cnt] == 0) {
mp[cnt] = i;
continue;
}
a = max(a, i-mp[cnt]);
}
}
b = min(x, y) * 2;
printf("%d %d\n", a, b);
}
return 0;
}
【F】 Planting Trees 单调队列
给定一个 \(n*n\) 的矩阵和每个点的权值 \(a_{ij}\),要找到一个最大的矩形,使得矩形内部权值的最大值和最小值之差不超过 \(m\)。
需要定义两个单调队列,一个维护当前最大值,另一个维护最小值,然后...说起来太复杂了,代码看看就好。另外 \(deque\) 直接用的话我是 TLE 了,所以是手写。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 5e2+5;
int cmax[maxn], cmin[maxn]; // 列最大最小值
int qmax[maxn*2], qmin[maxn*2]; // 单调队列(双端)
int a[maxn][maxn];
int l1, r1, l2, r2;
int ans, n, m;
int solve() {
l1 = l2 = 0;
r1 = r2 = 0;
int x = 1;
int temp = 0;
memset(qmin, 0, sizeof(qmin));
memset(qmax, 0, sizeof(qmax));
for(int i = 1; i <= n; i++) {
while(l1 < r1 && cmax[qmax[r1-1]] < cmax[i]) {
r1--;
}
qmax[r1++] = i;
while(l2 < r2 && cmin[qmin[r2-1]] > cmin[i]) {
r2--;
}
qmin[r2++] = i;
while(l1 < r1 && l2 < r2 && cmax[qmax[l1]] - cmin[qmin[l2]] > m && x <= i) {
x = min(qmax[l1], qmin[l2])+1;
if(qmax[l1] < qmin[l2]) {
l1++;
}
else if(qmax[l1] > qmin[l2]) {
l2++;
}
else {
l1++;
l2++;
}
}
temp = max(temp, i-x+1);
}
return temp;
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
ans = 0;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j++) {
scanf("%d", &a[i][j]);
}
}
for(int i = 1; i <= n; i++) {
for(int k = 1; k <= n; k++) {
cmax[k] = 0;
cmin[k] = inf;
}
for(int j = i; j <= n; j++) {
for(int k = 1; k <= n; k++) {
cmax[k] = max(cmax[k], a[j][k]);
cmin[k] = min(cmin[k], a[j][k]);
}
ans = max(ans, solve()*(j-i+1));
}
}
printf("%d\n", ans);
}
return 0;
}
【G】 Removing Stones RMQ+二分
给定 \(n\) 堆石头,每次可以选择两堆非空的各取走一个石头,如果最后能取光它就能获胜。 问存在多少个区间 \([l,r]\),满足必胜空间的定义。另外,如果区间内石头总数是奇数,那么就先去掉个数最小的一堆的一个石头。
游戏必胜的条件:区间的石头总数和 \(sum\) >= 两倍的区间最大值 \(max\)。
那么先利用 \(RMQ\) 就可以求出区间最值,然后把区间的贡献都算在区间中拥有最大数量石头的那堆上,先处理出一个石头的管辖边界 \([l,r]\),然后枚举一边,二分找另一边。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 300000+5;
int dp_max[maxn][25]; // 维护区间最大值
int pos[maxn][25]; // 维护区间最大值的位置
int a[maxn];
int n;
ll sum_f[maxn]; // 前缀和
ll sum_b[maxn]; // 后缀和
void RMQ() {
for(int i = 1; i <= n; i++) {
dp_max[i][0] = a[i];
pos[i][0] = i;
}
for(int j = 1; (1<<j) <= n; j++) {
for(int i = 1; i+(1<<j)-1 <= n; i++) {
if(dp_max[i][j-1] >= dp_max[i+(1<<(j-1))][j-1]) {
dp_max[i][j] = dp_max[i][j-1];
pos[i][j] = pos[i][j-1];
}
else {
dp_max[i][j] = dp_max[i+(1<<(j-1))][j-1];
pos[i][j] = pos[i+(1<<(j-1))][j-1];
}
}
}
}
int query_pos(int l, int r) { // 查询区间最值所在位置
int k = log2(r-l+1);
if(dp_max[l][k] >= dp_max[r-(1<<k)+1][k]) {
return pos[l][k];
}
return pos[r-(1<<k)+1][k];
}
ll solve(int l, int r) {
if(l >= r) {
return 0;
}
int p = query_pos(l, r); // 区间最值位置
// printf("l == %d r == %d p == %d\n", l, r, p);
ll ans = 0;
if(p-l < r-p) { // 更靠近左端点
for(int i = l; i <= p; i++) {
ans += (r - (lower_bound(sum_f+p, sum_f+r+1, sum_f[i-1]+2*a[p])-(sum_f+1)));
// 区间和必须 >= 二倍区间最大值【条件x】
// 对答案的贡献 == (右端点位置 - 满足条件的最左端【满足条件x】)
}
}
else {
for(int i = p; i <= r; i++) {
ans += ((n-l+1) - (lower_bound(sum_b+(n-p+1), sum_b+(n-l+1)+1, sum_b[n-i]+2*a[p])-(sum_b+1)));
// 后缀是反着存的,所以处理时也是反着
}
}
return ans + solve(l, p-1) + solve(p+1, r);
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
sum_f[0] = sum_b[0] = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
sum_f[i] = sum_f[i-1] + a[i];
}
for(int i = 1; i <= n; i++) {
sum_b[i] = sum_b[i-1] + a[n-i+1];
}
RMQ();
printf("%lld\n", solve(1, n));
}
return 0;
}
【H】 Magic Line 数学
给定二维平面上的 \(n\) (偶数)个点,需要画一条直线将这 n 个点平均分成两块,输出该直线上的任意两点坐标(整数)。
题解上是直接双关键字排序就能 AC,比赛时候想的还是有点太多了,我的解法是先找一个在第三象限的点【离得相当远】,然后以这个点为“ \(p\) 点”进行极角排序,然后取排序为“中点”的点和 \(p\) 点连线取另一端,然后再横坐标 -1 即可(保证不和“中点”相交)。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const int maxn = 1e5+5;
struct node {
ll x, y;
}p[maxn];
ll jud(node p1, node p2, node p0) {
ll ans = (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y);
return ans;
}
bool cmp(node a, node b) {
ll c = jud(node{-100000000, -10000}, b, a);
return c < 0;
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%lld%lld", &p[i].x, &p[i].y);
}
sort(p+1, p+1+n, cmp);
ll x = p[n/2].x;
ll y = p[n/2].y;
ll xx = 2*x + 100000000;
ll yy = 2*y + 10000;
printf("-100000000 -10000 %lld %lld\n", xx-1, yy);
}
return 0;
}
【J】 LRU management 模拟
模拟 LRU 算法:
- 添加一个 \(block\) 到缓存中,如果已经存在,将它移到末尾
- 询问一个 \(block\) 是否在缓存中,如果存在,则输出它的前驱(-1)或者它本身(0)或者它的后继(1)的数据
需要和队友一起写的大模拟,就很烦。【直接用 字符串 会tle(怎么搞都过不了那种),所以需要转化成数字】。
牛客评测姬祖传 TLE,只要你试得够多次,必定能AC。
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
struct node {
int pos;
ll name;
int val;
bool operator < (const node &x) const {
return pos < x.pos;
}
};
set<node> SS;
unordered_map<ll, int> vis;
int main() {
int t;
scanf("%d", &t);
while(t--) {
SS.clear();
vis.clear();
int q, m;
scanf("%d%d", &q, &m);
int tot = 0;
while(q--) {
int op, v;
char x[15];
scanf("%d%s%d", &op, x, &v);
int l = strlen(x);
ll s = 0;
for(int i = 0; i < l; i++) {
s = s*11+x[i]-'0';
}
if(op == 0) {
if(vis[s] == 0) {
if(SS.size() == m) {
vis[SS.begin()->name] = 0;
SS.erase(SS.begin());
}
SS.insert(node{++tot, s, v});
vis[s] = tot;
printf("%d\n", v);
}
else {
auto it = SS.lower_bound(node{vis[s], 0, 0});
node temp = *it;
SS.erase(it);
temp.pos = ++tot;
SS.insert(temp);
vis[s] = tot;
printf("%d\n", temp.val);
}
}
else if(op == 1){
if(vis[s] == 0) {
printf("Invalid\n");
}
else {
auto it = SS.lower_bound(node{vis[s], 0, 0});
if(v == 0) {
printf("%d\n", (*it).val);
}
else if(v == 1) {
it++;
if(it == SS.end()) {
printf("Invalid\n");
}
else {
printf("%d\n", (*it).val);
}
}
else if(v == -1){
if(it == SS.begin()) {
printf("Invalid\n");
}
else {
it--;
printf("%d\n", (*it).val);
}
}
}
}
}
}
return 0;
}