Codeforces Round #771 (Div. 2)

Posted on 2022-02-16 18:06  Capterlliar  阅读(102)  评论(0编辑  收藏  举报

A - Reverse

题意:给出一个数组,寻找一个子区间,使得其字典序最小。

解:努力把小的放到前面。找到第一个 a[i]!=i 的位置和 i 的位置,反转。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 1000005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int p[maxx];
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&p[i]);
        int l=0,r=0;
        for(int i=1;i<=n;i++){
            if(p[i]!=i){
                l=i;
                break;
            }
        }
        if(l==0){
            for(int i=1;i<=n;i++)
                printf("%d ",p[i]);
            printf("\n");
            continue;
        }
        for(int i=1;i<=n;i++){
            if(p[i]==l){
                r=i;
                break;
            }
        }
        for(int i=1;i<l;i++)
            printf("%d ",p[i]);
        for(int i=r;i>=l;i--)
            printf("%d ",p[i]);
        for(int i=r+1;i<=n;i++)
            printf("%d ",p[i]);
        printf("\n");
    }
    return 0;
}
View Code

B - Odd Swap Sort

题意:给出一个数组,每次交换和为奇数的两个数,问能否使其有序。

解:这种回答yes/no的题一般找个判断条件。已知只有奇数加偶数等于奇数,也就是说每次只能换一奇一偶,如果有两个偶数组成逆序对,那么无解,奇数同理。反之必然有解,详情请见冒泡排序。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 200005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n;
int p[maxx];
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&p[i]);
        vector<int> n1,n2;
        for(int i=1;i<=n;i++){
            if(p[i]%2==0) n2.push_back(p[i]);
            else n1.push_back(p[i]);
        }
        int flag=1;
        if(n1.size()>=2) {
            for (int i = 0; i < n1.size() - 1; i++) {
                if (n1[i] > n1[i + 1]) {
                    flag = 0;
                    break;
                }
            }
        }
        if(n2.size()>=2) {
            for (int i = 0; i < n2.size() - 1; i++) {
                if (n2[i] > n2[i + 1]) {
                    flag = 0;
                    break;
                }
            }
        }
        if(flag) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
View Code

C - Inversion Graph

题意:给出一个数组,每对逆序对之间连一条边,问最终有多少个连通块。

解:首先不能找出每个逆序对然后跑并查集。通过观察发现,对于新加入的一个数,如果前面有比它大的数,那么它们属于一个连通块,不用再看。但是这个范围很模糊,如果同时小于两个较大值,那么这两个连通块应该合并,但现在做不到这一点。考虑合并连通块,将两个数合成一个,肯定选较大的作为这个连通块代表。将这些数放进栈里,栈的大小就是连通块个数。

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 100005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n;
int p[maxx];
signed main() {
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&p[i]);
        stack<int> s;
        for(int i=1;i<=n;i++){
            if(s.empty()||p[i]>s.top())
                s.push(p[i]);
            else{
                int t=s.top();
                while(!s.empty()&&s.top()>p[i])
                    s.pop();
                s.push(t);
            }
        }
        printf("%d\n",s.size());
    }
    return 0;
}
// a b c d e f
// add g
//if b<g<c
//del c d e
View Code

D. Big Brush

题意:有一个n*m的网格每次可以涂2*2的小方块,现在给出涂完的结果,求涂的顺序。

解:先找出一个2*2的同色方块,以此为开始bfs,枚举包含它的所有方块组。已经找到的方块标记为0,表示可以当任意一种颜色使。注意开始找到的方块组要全部加入队列,避免不连通现象。稍微有点难写。

  • 判四个方块颜色是否相等(包括标记为0的)可以用set;

代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define maxx 1005
#define maxn 1005
#define maxm 200005
#define eps 0.00000001
#define inf 0x7fffffff
#define mod 998244353
//#define int long long
int n,m;
int a[maxx][maxx];
struct node{
    int i,j,col;
};
vector<node> ans;
queue<pair<int,int> > q;
int xx[4]={0,1,0,1};
int yy[4]={0,0,1,1};
int check(int x,int y) {
    if (x < 1 || y < 1 || x >= n || y >= m)
        return 0;
    set<int> s;
    for (int i = 0; i < 4; i++) {
        if (a[x + xx[i]][y + yy[i]])
            s.insert(a[x+xx[i]][y+yy[i]]);
    }
    if(s.size()!=1)
        return 0;
    for (int i = 0; i < 4; i++) {
        if (a[x + xx[i]][y + yy[i]])
            q.push(make_pair(x + xx[i], y + yy[i]));
    }
    ans.push_back((node) {x, y, *s.begin()});
    a[x][y] = a[x + 1][y] = a[x][y + 1] = a[x+1][y+1] = 0;
    return 1;
}
signed main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    }
    for(int i=1;i<n;i++){
        for(int j=1;j<m;j++)
            check(i,j);
    }
    while(!q.empty()){
        int x=q.front().first;
        int y=q.front().second;
        q.pop();
        for(int i=-1;i<=0;i++){
            for(int j=-1;j<=1;j++)
                check(x+i,y+j);
        }
    }
    int flag=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            if(a[i][j]!=0)
                flag=0;
    }
    if(flag){
        reverse(ans.begin(),ans.end());
        printf("%d\n",ans.size());
        for(auto [x,y,z]:ans)
            printf("%d %d %d\n",x,y,z);
    }else printf("-1\n");
    return 0;
}
View Code

E. Colorful Operations

题意:相当粗暴的题意。一开始数组中所有数val为0,颜色为1。有三种操作:

1.将区间[l,r]颜色赋值为x;

2.将所有颜色为x的数val+=y;

3.查询第x数的val。

解:学习了珂朵莉树。先分析一下题目。2操作很容易想到开一个数组col,存每个颜色到目前为止加了多少值,最后查询时再用当前值加上col[now]。途中这个结点会变好几次颜色,所以每变一次颜色就要加一次值。还有一个问题就是col[now]表示到目前为止累积和,而now在里面的时间很可能没有呆满,要预先减掉当前累积的值,这样再加上的时候答案才是对的。用x表示之前颜色,y表示要变成的颜色。现在来结算:val[now]=val[now]+col[x]-col[y].

现在执行1操作。区间赋值,这没什么,普通的线段树就能做,但赋值的同时要更新当前val,而每个区间内的颜色不尽相同,这样下去要变成单点修改了,不行。解决方法之一是多加一个same标签,记录一个区间颜色是否相同。这个做法实际上类似于势能线段树,复杂度远比看起来低。

另一个解法就是珂朵莉树。现在要解决的是大区间内小区间的颜色不同,如果能把这些小区间一个一个列出来就好了,听起来又精确又方便,于是珂朵莉树出现了。在数据随机的情况下,线段树能做的珂朵莉树都能做,因为它很暴力。珂朵莉树有两个操作,一是split,以左端点或右端点为界,把一个区间分成两个,二是assign,把两个区间合成一个。当区间覆盖时,以左端点为界,裂开,中间抹平,右边再裂开,完事。实现用set,删除原区间再塞一个进去。有点像分块,但比分块操作少。

复杂度可以到达nloglogn,具体证明在这(虽然没看懂