暑假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;
}
posted @ 2019-07-31 20:05  Decray  阅读(196)  评论(0编辑  收藏  举报