zoukankan      html  css  js  c++  java
  • 暑假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("
    ");
        }
        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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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
    ", temp.val);
                    }
                }
                else if(op == 1){
                    if(vis[s] == 0) {
                        printf("Invalid
    ");
                    }
                    else {
                        auto it = SS.lower_bound(node{vis[s], 0, 0});
                        if(v == 0) {
                            printf("%d
    ", (*it).val);
                        }
                        else if(v == 1) {
                            it++;
                            if(it == SS.end()) {
                                printf("Invalid
    ");
                            }
                            else {
                                printf("%d
    ", (*it).val);
                            }
                        }
                        else if(v == -1){
                            if(it == SS.begin()) {
                                printf("Invalid
    ");
                            }
                            else {
                                it--;
                                printf("%d
    ", (*it).val);
                            }
                        }
                    }
                }   
            }
        }
        return 0;
    }
    
  • 相关阅读:
    python3 爬虫继续爬笔趣阁 ,,,,,,,
    sql server 能按照自己规定的字段顺序展示
    文件下载功能django+js
    Django 实现文件下载
    队列,循环队列,乒乓队列区别
    文件系统常用操作(df, du)
    Raw与ProRes Raw(二、深入挖掘)
    图片格式入门(RAW, TIFF, JPEG)
    什么是ProRes Raw?(一、管中窥豹)
    linux的top命令分析
  • 原文地址:https://www.cnblogs.com/Decray/p/11278766.html
Copyright © 2011-2022 走看看