[Codeforces Round #665 (Div. 2)]1401

4 篇文章 0 订阅
3 篇文章 0 订阅



竞赛首页 Codeforces Round #665 (Div. 2)

A - Distance and Axis [思维]

题意

给出 x x x 轴上的点 A A A 的横坐标 n n n,问 A A A 至少需要移动多少才能存在点 B B B,使得 a b s ( ∣ O B ∣ − ∣ B A ∣ ) = k abs(|OB|-|BA|)=k abs(OBBA)=k

分析

如果 k > n k > n k>n,最小的移动应该是 n n n 移动到 k k k
否则, B B B 应该在原点到 A A A 之间,保证间距为 k k k,则只要看 ( n − k ) % 2 (n-k) \% 2 (nk)%2 是否为 0 0 0,如果为 0 0 0 表示可以左右都扩一位,所以结果为 0 0 0,否则为 1 1 1

代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, k;
        scanf("%d%d", &n, &k);
        if(n >= k)  printf("%d\n", (n - k) % 2 ? 1 : 0);
        else        printf("%d\n", k - n);
    }
    return 0;
}


B - Ternary Sequence [思维]

题意

数组 a , b a,b a,b 的值为 0 , 1 , 2 0, 1, 2 0,1,2 三种,给出数组 a , b a, b a,b 中值为 0 , 1 , 2 0, 1, 2 0,1,2 的个数
令数组 c c c 如下
在这里插入图片描述
∑ i = 1 n c i \sum_{i=1}^{n}c_i i=1nci 的最大值可以为多少

分析

可以发现有贡献的配对只有这几种情况
a i = 1 , b i = 2 a_i = 1, b_i = 2 ai=1,bi=2 a i = 2 , b i = 1 a_i=2, b_i=1 ai=2,bi=1
对于前一个,得到的贡献是 − 2 -2 2,后者得到的贡献是 2 2 2
要使前一个尽可能少,后一个尽可能多,所以用 a i = 0 a_i=0 ai=0 的先去匹配 b i = 2 b_i=2 bi=2
其次 b i = 0 b_i=0 bi=0 的得不到贡献,但 a i = 1 a_i=1 ai=1 得到的贡献是负的,所以用 b i = 0 b_i=0 bi=0 的先去匹配 a i = 1 a_i=1 ai=1
剩下的个数按照上面所说的贡献即最终可以得到的最大贡献

代码

#include <bits/stdc++.h>
using namespace std;

int a[3], b[3];

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int ans = 0;
        for(int i=0; i<3; ++i) scanf("%d", a+i);
        for(int i=0; i<3; ++i) scanf("%d", b+i);
        int mi = min(a[1], b[0]);
        a[1] -= mi, b[0] -= mi;
        mi = min(a[0], b[2]);
        a[0] -= mi, b[2] -= mi;
        ans -= 2 * min(a[1], b[2]);
        ans += 2 * min(a[2], b[1]);
        printf("%d\n", ans);
    }
    return 0;
}


C - Mere Array [思维]

题意

给出一个长度为 n n n 的数组 a a a,当满足 g c d ( a i , a j ) = = m i n ( a k ) ( 1 ≤ k ≤ n ) gcd(a_i,a_j) == min(a_k)(1 \leq k \leq n) gcd(ai,aj)==min(ak)(1kn) 可以交换两个数的位置
问最后能否得到一个非递减序列

分析

数组最终得到的序列是一定的,即可以将序列排序,从小到大
如果原本该位置上的数不是最终要在该位置上的数,则是一定需要交换的
对于 g c d ( a i , a j ) = m i n ( a k ) gcd(a_i, a_j) = min(a_k) gcd(ai,aj)=min(ak),不难想到只要每次交换可以利用这个最小的值来交换
a i ↔ m i n ( a k ) , m i n ( a , k ) ↔ a j a_i \leftrightarrow min(a_k), min(a,k) \leftrightarrow a_j aimin(ak),min(a,k)aj,这样就可以让 m i n ( a k ) min(a_k) min(ak) 保持不变,且交换所要交换的两个数
则可以交换的情况即 a i % m i n ( a k ) = = 0 a_i \% min(a_k) == 0 ai%min(ak)==0,且 b i % m i n ( a k ) = = 0 b_i \% min(a_k) == 0 bi%min(ak)==0 b i b_i bi 即最终要在该位置上的数)

代码

#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 5;

int a[maxn];
int b[maxn];

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n;
        scanf("%d", &n);
        for(int i=1; i<=n; ++i) {
            scanf("%d", &a[i]); b[i] = a[i];
        }
        bool f = true;
        sort(b + 1, b + 1 + n);
        for(int i=1; i<=n; ++i) {
            if(a[i] != b[i] && (a[i] % b[1] || b[i] % b[1])) {
                f = false; break;                
            } 
        }
        puts(f ? "YES" : "NO");
    }
    return 0;
}


D - Maximum Distributed Tree [dfs][求贡献] ★★

题意

给一棵 n n n 个结点的树,再给你一个大数 k k k,这个 k k k 是由 m m m 个质数相乘得到的
每条边都有一个边权,可以令任意边等于任意值,但要使得这些边权的乘积 = = k ==k ==k,且要求边权为 1 1 1 的数量尽可能少
f ( i , j ) f(i, j) f(i,j) 表示点 i i i j j j 的简单路径的边权和
∑ i = 1 n − 1 ∑ j = i + 1 n f ( i , j ) \sum_{i=1}^{n-1}\sum_{j=i+1}^{n}f(i,j) i=1n1j=i+1nf(i,j) 的最小值为多少
即 每两个点的简单路径的边权和 的总和 的最大值
结果 m o d    1 e 9 + 7 \mod {1e^9+7} mod1e9+7

分析

对于一棵树上,遍历两点的简单路径,可以转化为每条边的边权 ∗ * 边经过的次数,即每条边的贡献
首先考虑边权,边权为 1 1 1 的数量尽可能少其实也提示了做法;对于一个数 k k k,他可以最终拆分成 m m m 个质数,这这些质数就可以直接用来当边权
当个数 < n − 1 <n-1 <n1 的时候,其余边就可以用 1 1 1
如果 > n − 1 > n-1 >n1,则将大的边权进行相乘,这样可以最大化边权贡献

对于每条边,必然会经过的次数为 n − 1 n-1 n1 次,因为要到达其他的 n − 1 n-1 n1 个点
以这条边为分界线,可以得到两边,可看下图
在这里插入图片描述
3 − 6 3-6 36 的这条边,左边还有两个点,右边还有三个点
这些点在连接的时候都会经过这条边
相当于会多经过 2 ∗ 3 2*3 23

则用 d f s dfs dfs 统计一边的点个数,也可以说是儿子点个数,
边的贡献次数即为 ( n − 1 ) + ( n u m [ i ] − 1 ) ∗ m a x ( 0 , n − n u m [ i ] − 1 ) (n-1)+(num[i]-1)*max(0, n-num[i]-1) (n1)+(num[i]1)max(0,nnum[i]1)
要对边次数以及质数进行排序,将大的两两相乘

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;

vector<pair<int, int> > ed[maxn];
ll num[maxn];
ll a[maxn];

ll dfs(int u, int fa, int deep) {
    ll ans = 0, tmp;
    for(auto v : ed[u]) {
        if(v.first == fa) continue;
        tmp = dfs(v.first, u, deep + 1);
        ans += tmp, num[v.second] += tmp;
    }
    return ans + 1;
}

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, m;
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            num[i] = 0, ed[i].clear();
        for(int i = 1, u, v; i < n; ++i) {
            scanf("%d%d", &u, &v);
            ed[u].push_back(make_pair(v, i));
            ed[v].push_back(make_pair(u, i));
        }
        scanf("%d", &m);
        for(int i = 1; i <= m; ++i) scanf("%lld", &a[i]);
        dfs(1, 1, 0);
        for(int i = 1; i < n; ++i)
            num[i] = n-1+(num[i]-1)*max(0*1ll, n-num[i]-1);
        while(m < n-1) a[++m] = 1;
        sort(a + 1, a + 1 + m);
        sort(num + 1, num + n);
        while(m >= n)
            a[m-1] = a[m-1]*a[m]%mod, m-=1;
        ll ans = 0;
        for(int i = 1; i < n; ++i)
            ans = (ans + num[i]*a[i]%mod) % mod;
        printf("%lld\n", ans);
//        for(int i = 1; i < n; ++i)
//            printf("%d ", n-1+(num[i]-1)*max(0,n-num[i]-1));
    }
    return 0;
}

/*
1
7
2 3
3 7
3 6
6 4
6 1
1 5
4
2 3 5 7

6 6 12 6 10 6
*/


E - Divide Square [扫描线][树状数组] ★★★

题意

一个正方形边长为 1 0 6 10^6 106,由点 ( 0 , 0 ) ( 0 , 1 0 6 ) ( 1 0 6 , 0 ) ( 1 0 6 , 1 0 6 ) (0, 0)(0,10^6)(10^6, 0)(10^6,10^6) (0,0)(0,106)(106,0)(106,106) 组成
现在给这个正方形划几天水平和垂直的线段,问最后的图可以构成几个矩形

分析

参考博客 CF 1401E Divide Square(扫描线,树状数组)

虽然猜到是扫描线,但是不太懂怎么写
可以发现,每当增加一个水平线段和垂直线段的交点,就可以多增加一个矩形
即题目可以转化为,求水平线段和垂直线段的交点个数

以平行于 x x x 轴的点的两端,做平行于 y y y 轴的扫描线
那么要找的就是每条每个平行于 y y y 轴的线段可以与几个上面的扫描线相交
也就是计算区间和 x ∈ [ l , r ] x \in [l, r] x[l,r] 的左端点的个数

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e6 + 5;
const int R = 1e6;

// cf 1401 E.Divide Square
struct BIT{
    int c[maxn];
    int lowbit(int x) {
        return x & (-x);
    }
    void update(int id, int val) {
        for( ; id < maxn; id += lowbit(id)) c[id] += val;
    }
    int query(int id) {
        int ans = 0;
        for( ; id > 0; id -= lowbit(id)) ans += c[id];
        return ans;
    }
}tr;
struct Line{
    int x, y, f;
    // 横向线段端点(x, y),与在该点出需要进行的操作 f(+1 / -1)
    // 竖向线段 (f,x) 到 (f,y) 
}seq[maxn], line[maxn];

bool cmpseq(Line a, Line b) {
    return a.x < b.x;
}

bool cmpline(Line a, Line b) {
    return a.f < b.f;
}

int main() {
    int n, m, k = 0;
    scanf("%d%d", &n, &m);
    ll ans = 1, y, l, r;
    for(int i = 1; i <= n; ++i) {
        scanf("%lld%lld%lld", &y, &l, &r);
        if(l == 0 && r == R)    ans++; // 直接切割
        seq[++k] = Line{l, y, 1};
        seq[++k] = Line{r+1, y, -1};
    }
    for(int i = 1; i <= m; ++i) {
        scanf("%d%d%d", &line[i].f, &line[i].x, &line[i].y);
        if(line[i].x == 0 && line[i].y == R) ans++;
    }
    sort(seq + 1, seq + k + 1, cmpseq);
    sort(line + 1, line + m + 1, cmpline);
    for(int i = 1, j = 1; i <= m; ++i) {
        while(j <= k && seq[j].x <= line[i].f) {
            tr.update(seq[j].y+1, seq[j].f);
            ++j;
        }
        ans += tr.query(line[i].y+1) - tr.query(line[i].x); // [y+1,f+1]
    }
    printf("%lld\n", ans);
    return 0;
}


F - Reverse and Swap [线段树] ★★★

题意

给你一个长度为 1 < < n 1<<n 1<<n 的序列,有下面四种操作

  1. 1 x k:令 a [ x ] = k a[x] = k a[x]=k
  2. 2 k:将原序列从左往右分为若干个长度为 2 k 2^k 2k 的区间,每个区间都进行翻转(即 abcd → \rightarrow dcba)
  3. 3 k:将原序列从左往右分为若干个长度为 2 k 2^k 2k 的区间,相邻的区间进行交换(两两交换,即区间 [1,2,3,4] → \rightarrow [2,1,4,3])
  4. 4 l r:输出 ∑ i = l r a [ i ] \sum_{i=l}^r a[i] i=lra[i]

分析

参考博客 Codeforces 1401F Reverse and Swap
原博主已经说出了我的os,果然大佬都是神仙,我觉得这题还是比较有意思的

1 , 4 1, 4 1,4 操作就是线段树单点修改区间查询,就不讲了
要求和则需要维护一个区间和
若序列长度为 2 n 2^n 2n,则线段树需要开的结点应该为 2 n + ! 2^{n+!} 2n+!
将叶子节点看做第 0 0 0 层,则根节点就是第 n n n
可以发现第 i i i 层包含的叶子节点应该有 2 i 2^i 2i 个,那么就是我们每次要找的分割出来的区间
操作 3 3 3,整个区间进行交换,就相当于直接把父节点的左右儿子进行交换
操作 2 2 2,相当于从第 0 0 0 层到第 k k k 层每层的左右儿子都要进行交换,可以画个图理解一下
数组实现的话,就标记需要交换的层,交换的层就让左右儿子点交换一下

代码

#include <bits/stdc++.h>
#define lson id<<1
#define rson id<<1|1
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = 1e6 + 5;

ll sum[maxn<<2];
int rev[maxn<<2];

void build(int id, int l, int r) {
    rev[id] = 0;
    if(l == r) {
        scanf("%lld", &sum[id]);
        return ;
    }
    int mid = (l + r) >> 1;
    build(lson, l, mid);
    build(rson, mid+1, r);
    sum[id] = sum[lson] + sum[rson];
}

void update(int id, int l, int r, int dep, int pos, int val) {
    if(l == r) {
        sum[id] = val*1ll;
        return ;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid)  update(lson|rev[dep], l, mid, dep-1, pos, val);
    else            update(lson|rev[dep]^1, mid+1, r, dep-1, pos, val);
    sum[id] = sum[lson] + sum[rson];
}

ll query(int id, int l, int r, int dep, int ql, int qr) {
    if(ql <= l && qr >= r)  return sum[id];
    int mid = (l + r) >> 1; ll ans = 0;
    if(ql <= mid) 
        ans += query(lson|rev[dep], l, mid, dep-1, ql, qr);
    if(qr > mid)
        ans += query(lson|rev[dep]^1, mid+1, r, dep-1, ql, qr);
    return ans;
}

void Reverse(int k) {
    for(int i = 0; i <= k; ++i) rev[i] ^= 1;
}

int main() {
    int n, q;
    scanf("%d%d", &n, &q);
    build(1, 1, 1<<n);
    int op, l, r;
    while(q--) {
        scanf("%d", &op);
        if(op == 1) { // replace
            scanf("%d%d", &l, &r);
            update(1, 1, 1<<n, n, l, r);
        }else if(op == 2) { // reverse
            scanf("%d", &l);
            Reverse(l);
        }else if(op == 3) { // swap
            scanf("%d", &l);
            rev[k+1] ^= 1;
        }else{ // sum
            scanf("%d%d", &l, &r);
            printf("%lld\n", query(1, 1, 1<<n, n, l, r));
        }
    }
    return 0;
}


补完撒花✿✿ヽ(°▽°)ノ✿

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值