Codeforces Round #665 (Div. 2) 题解( 小学奥数专场 )

A、Distance and Axis(简单高中数学应用题)

在这里插入图片描述
在这里插入图片描述

  • k > n k>n ,那么直接将 A A 点移动到 k k 位置是最优的。
  • 否则我们设 B B 点至 O , A O,A 两点中较短的距离为 x x ,较长的为 y y ,则有方程 :
    { x + y = n y x = k \left\{ \begin{array}{l} x+y=n\\ y-x=k\\ \end{array} \right.

联立解得
{ y = n + k 2 x = n k 2 \left\{ \begin{array}{l} y=\frac{n+k}{2} \\ x=\frac{n-k}{2}\\ \end{array} \right.

故当
n + k % 2 = 0 n+k\%2=0 时,代价为 0;
否则将 A 右移一个单位,代价为 1。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 500007;

int n, m;
int k;

int main()
{
    int t;
    scanf("%d", &t);
    while(t -- ){
        scanf("%d%d", &n, &k);
        if(k > n)printf("%d\n", k - n);
        else if((n - k) % 2 == 0)
                puts("0");
        else puts("1");

    }
    return 0;
}

B、Ternary Sequence(分类讨论)

在这里插入图片描述
分类讨论即可,吐了。

观察题意,只能是 A A 序列跟 B B 序列相比,所以我们模拟一下即可。

我们发现只有 A i > B i A_i>B_i 的时候才有正值 2 2 ,其余的要么是 0 0 ,要么是 2 -2 。所以我们先分配 A i > B i A_i>B_i 的情况,然后再尽量往答案是 0 0 的方向分配。最后把 2 -2 的答案加上。我们全程用过以后就减掉相应的值,来完全模拟即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 500007;

int t;
int x1, y1, z1, x2, y2, z2, ans;

int main()
{
    scanf("%d", &t);
    while(t -- ){
        ans = 0;
        scanf("%d%d%d%d%d%d", &x1, &y1, &z1, &x2, &y2, &z2);
        int a = min(z1, y2);
        z1 -= a;y2 -= a;
        ans += a * 2;
        a = min(z1, z2);
        z2 -= a; z1 -= a;
        a = min(z1, x2);
        z1 -= a; x2 -= a;
        a = min(y1, y2);
        y1 -= a; y2 -= a;
        a = min(y1, x2);
        y1 -= a; x2 -= a;
        a = min(y1, z2);
        y1 -= a; z2 -= a;
        ans -= a * 2;
        a = min(z1, z2);
        z1 -= a; z2 -= a;
        a = min(z2, x1);
        z2 -= a; x1 -= a;
        a = min(z2, y1);
        z2 -= a; y1 -= a;
        ans -= a * 2;
        printf("%d\n", ans);
    }
    return 0;
}

C、Mere Array(GCD的性质)

在这里插入图片描述

这题妙啊。

我们发现本题是要求将给定序列排序,并给定了交换元素的条件。
问我们能否实现排序操作。

我们参考冒泡排序等排序算法,想要完成排序,那么必须可以将一些数交换。而我们想要交换 a i , a j a_i,a_j ,就必须满足 gcd ( a i , a j ) \gcd(a_i,a_j) 等于整个数组中最小的元素。
因此我们先将原序列复制后排序,如果序列里的数于排序之后的数不同,则说明该数需要变换位置。然后看该数能够被序列最小元素整除,如果可以的话可以交换,如果不行的话说明无法换位还不在正确的位置上,输出 N O NO ,否则输出 Y E S YES
如果这个数可以整除数列最小值,那么对于两个满足此条件的数 x x y y ,可以和最小值 z z 下列操作:

x y z
x z y
z x y
y x z

实现交换 x x y y 的位置, z z 不变,完成排序里的交换操作。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 200007;

int n, m;
int t;
int a[N], b[N];
int main()
{
    scanf("%d", &t);
    while(t -- ){
        scanf("%d", &n);
        bool flag = 0;
        for(int i = 1; i <= n; ++ i)
            scanf("%d", &a[i]);
        memcpy(b, a, sizeof a);
        sort(b + 1, b + 1 + n);
        for(int i = 1; i <= n; ++ i){
            if(a[i] != b[i] && a[i] % b[1] != 0){
                puts("NO");
                flag = 1;
                break;
            }
        }
        if(!flag)puts("YES");
    }
    return 0;
}

D、Maximum Distributed Tree(树上贪心)

E、Divide Square(计算几何)

F、Reverse and Swap(线段树、实现 区间翻转, 区间交换)

在这里插入图片描述
  首先一共有四个操作,第一个和第四个都是线段树的基本操作,直接用线段树实现。
  
  第二个和第三个操作都是在分成 2 k 2 ^ k 长度区间操作,并且题目中的序列一共有 2 n 2^n 个节点,如果放到线段树里正好是 n + 1 n+1 层,每层都由 2 k 2 ^ k 长度区间构成。第 0 0 层为叶子节点,第 n n 层为根节点
  
  对于第三个操作 s w a p swap :相当于交换线段树两个相邻且长度为 2 k 2^k 的区间,由于本题一共有 p = 1 e 5 p = 1e5 个修改查询操作,所以如果每次修改都修改的话哪怕是线段树也一定会超时。因此我们可以取巧,不真正地进行修改,而是每次查询或者修改单点操作的时候,根据修改标记来访问到相邻的区间。也就是相当于想要访问该节点的左(右)子树时候,由于区间翻转了,我们直接访问另一个右(左)子树区间即可。可以用一个变量来记录以下是否需要改变访问的左右结点。比如当前 s w a p swap 的是第 k k 层 ,那么只需要标记第 k + 1 k + 1 层,这样我们访问到该节点的第 k + 1 k + 1 层的时候,根据标记的指引就会跳转到第 k k 层的相邻区间,满足题意。
  
  第二个同理,区间翻转我们参考文艺平衡树这道题,想要实现区间翻转,只需要把区间里的所有左右子树全部交换即可。而左右子树正好都是相邻的区间,那么我们对整个区间都做一次类似操作三的标记即可。假如当前需要 r e v e r s e reverse k k 层,因为线段树是一颗满二叉树,在我们寻找该区间的时候如果到达第 k k 层的时候,该点的左右子树就表示整个要找的区间,我们需要将第 k k 层以下的层全部翻转,也就是将 0 0 ~ k k 层的节点都打赏标记即可。
  
  具体细节看代码,是在线段树模板的基础之上进行改进得到的。

这里有两个易错点:

  • 因为线段树翻转问题的解决办法是在翻转半区间打上标号,有标记的节点左右子树互换(实际体现就是查找的时候交换即可,整棵树实际上是不用动的),因此所有操作在寻找预定区间的时候就不能在根据当前根节点寻找预定区间,因为这里的p已经可能变了,就不能用 t r [ p ] . l tr[p].l t r [ p ] . r tr[p].r 所以我们应该再传入参数 l l r r 来辅助寻找预定区间。
  • 凡是涉及到位运算的都要疯狂加括号!!!位运算的优先级实在是太糟糕了
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>

typedef long long ll;
using namespace std;
const int N = 2000005;

int n, m;
int a[N];
int rev[N], cnt;
struct tree{
    int l, r;
    ll sum;
}tr[N << 2];

void pushup(int p)
{
    tr[p].sum = tr[p << 1].sum + tr[p << 1 | 1].sum;
}

void build(int p, int l, int r)
{
    tr[p] = {l, r, 0};
    if(l == r){
        tr[p].sum = a[r];
        return ;
    }
    int mid = l + r >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    pushup(p);
}

void modify(int l, int r, int p, int x, int val, int depth)
{
    if(l == r){
        tr[p].sum = val;
        return ;
    }
    int mid = l + r >> 1;
    if(x <= mid)modify(l, mid, (p << 1) + (rev[depth] == 1), x, val, depth - 1);
    else modify(mid + 1, r, (p << 1 | 1) - (rev[depth] == 1), x, val, depth - 1);
    pushup(p);
}

ll query(int rl, int rr, int p, int l, int r, int depth)
{

    if(rl >= l && rr <= r)
        return tr[p].sum;
    int mid = rl +rr >> 1;
    ll ans = 0;
    if(l <= mid)ans += query(rl, mid, (p << 1) + (rev[depth] == 1), l, r, depth - 1);
    if(r > mid)ans += query(mid + 1, rr, (p << 1 | 1) - (rev[depth] == 1), l, r, depth - 1);
    return ans ;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= (1 << n); ++ i)
        scanf("%d", &a[i]);
    build(1, 1, 1 << n);
    while(m -- ){
        int op, x, y;
        scanf("%d%d", &op, &x);
        if(op == 1){
            scanf("%d", &y);
            modify(1, 1 << n, 1, x, y, n);// 2^n 个节点, 线段树一共会有n+1层第0层为叶子节点,第n层为根节点
        }
        else if(op == 2)for(int i = 0; i <= x; ++ i)rev[i] ^= 1;
        else if(op == 3)rev[x + 1] ^= 1;
        else {
            scanf("%d", &y);
            printf("%lld\n", query(1, 1 << n, 1, x, y, n));
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45697774/article/details/108299411