Codeforces Round #665 (Div. 2)(A - E)

Codeforces Round #665 (Div. 2)

A Distance and Axis

题意

在X正半轴上有点 A = n A=n A=n,我们想找到一个整数点 B(也在OX轴上),使得 O B OB OB 的距离减 A B AB AB 的距离的绝对值等于 k k k ,如果B找不到,可以将A点每次左移或右移一个单位,知道找到一个B点。求A的最少移动次数。

Solution

如果k大于n,那么B一定在A右边,就变成了 O B − A B = O A OB-AB=OA OBAB=OA 的距离是k,就把A移动到k位置。
如果k小于等于n,假设B的坐标是m,则 ∣ ( n − m ) − m ∣ = k |(n-m)-m|=k (nm)m=k,整理一下并且把绝对值去掉,就变成了了 2 m = n − k 2m=n-k 2m=nk 或者 2 m = n + k 2m=n+k 2m=n+k,因为m是整数,所以就能得到结论n和k奇偶性相同,答案只能是0或1。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
    {
        int n, k;
        cin >> n >> k;

        if (n >= k)
            cout << ((n - k) % 2) << endl;
        else
            cout << k - n << endl;
    }
    return 0;
}

B Ternary Sequence

题意

给定两个长度为 n n n 的序列 A , B A,B A,B,他们中分别由 x i , y i , z i x_i,y_i,z_i xi,yi,zi 0 , 1 , 2 0,1,2 0,1,2 构成,你可以重新排列A和B中的元素顺序,使得 ∑ c i \sum c_i ci 最大。
c i = { a i b i , i f   a i > b i 0 , i f   a i = b i − a i b i , i f   a i < b i c_i=\begin{cases} a_{i}b_{i}, & if\ a_i>b_i \\ 0, & if\ a_i=b_i \\ -a_{i}b_{i}, & if\ a_i<b_i \\ \end{cases} ci=aibi,0,aibi,if ai>biif ai=biif ai<bi

Solution

贪心。A、B中的0、1、2两两匹配有9种可能,可以得到(1,2)等于-2,(2,1)等于2,其他组合都等于0。
所以要使(2,1)组合尽可能多,使(1,2)组合尽可能少。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

    int t;
    cin >> t;
    while (t--)
    {
        int a[3], b[3];
        for (int i = 0; i < 3; i++)
            cin >> a[i];
        for (int i = 0; i < 3; i++)
            cin >> b[i];
        int ans = 0;
        int temp = min(a[2], b[1]); // (ai=2, bi=1) 尽可能多
        ans += temp * 2;
        a[2] -= temp;
        b[1] -= temp;
        if (a[1] > b[0] + b[1]) // 先不匹配 (ai=1, bi=2) 的组合,先尽可能把其他组合匹配完
            ans -= (a[1] - b[0] - b[1]) * 2;
        cout << ans << endl;
    }
    return 0;
}

C Mere Array

题意

给你一个长度为 n n n 的数组 a a a m i n n minn minn 是数组 a a a 的最小值,当 g c d ( a i , a j ) = = m i n n gcd(a_i,a_j)==minn gcd(ai,aj)==minn 时,可以交换 a i , a j a_i,a_j ai,aj 的位置,问能否通过交换使得数组 a a a 变成非递减的。

Solution

思维题。分析一下,因为 g c d ( a i , a j ) = = m i n n gcd(a_i,a_j)==minn gcd(ai,aj)==minn ,所以 g c d ( a i , m i n n ) = = m i n n gcd(a_i,minn)==minn gcd(ai,minn)==minn g c d ( a j , m i n n ) = = m i n n gcd(a_j,minn)==minn gcd(aj,minn)==minn ,那么要交换 a i , a j a_i,a_j ai,aj 就能通过 m i n n minn minn 作为中间值把两个数交换。
那么我们考虑哪些数需要交换位置?假设数组 b b b 是排好序的,如果 a i ≠ b i a_i\ne b_i ai=bi ,那么说明 a i a_i ai 需要交换位置,如果 a i a_i ai 不能整除 m i n n minn minn ,那么就说明 a i a_i ai 永远不能换位置,否则 a i a_i ai 就能通过 m i n n minn minn 作为中间交换变量到达它应该在的位置,可以考虑最简单的冒泡来证明。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;
int main()
{
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;

        vector<int> a(n);
        int minn = INF;
        for (int i = 0; i < n; i++)
        {
            cin >> a[i];
            minn = min(a[i], minn);
        }

        vector<int> b(a);
        sort(b.begin(), b.end());

        bool flag = true;
        for (int i = 0; i < n; i++)
        {
            if (a[i] != b[i] && a[i] % minn)
            {
                flag = false;
                break;
            }
        }

        if (flag)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
    return 0;
}

D Maximum Distributed Tree

题意

给定一棵 n n n 个结点的树,给这棵树的边分配边权,使得所有边权的乘积等于 k k k k k k m m m 个质因子的形式给出,并且要求 ∑ 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) 最大,其中 f ( i , j ) f(i,j) f(i,j) 表示 i i i j j j 的简单路径边权之和。

Solution

考虑每条边对答案的贡献。我们想一下,一条边可能出现在哪些简单路径中,答案显然是以这条边分开的上下两子树中任意结点组合的简单路径,我们记数组 s z [ i ] sz[i] sz[i] 表示 i i i 结点的子树大小(包括 i i i 结点),那么 i i i 到它的父亲这条边对答案的贡献次数就是 s z [ i ] × ( n − s z [ i ] ) sz[i]\times (n-sz[i]) sz[i]×(nsz[i]) ,统计出每条边的贡献次数 q q q ,然后我们贪心地把最大的质因子 p p p ,分配给贡献次数最多的边即可。
这里要区分 m ≤ n − 1 m\le n-1 mn1 m > n − 1 m>n-1 m>n1 这两种情况,第一种是边多质因子不够分的,只能补1,第二种是边少质因子多,我们要把多出来的几个最大的质因子相乘变成一个,这样才使最大化。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;

vector<int> e[N];
ll sz[N], p[N], q[N];

void dfs(int u, int fa)
{
    sz[u] = 1;
    for (int v : e[u])
    {
        if (v == fa)
            continue;
        dfs(v, u);
        sz[u] += sz[v];
    }
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        
        for (int i = 0; i <= n; i++)
            e[i].clear(), sz[i] = 0, p[i] = 1; // 质因子要初始化为 1
            
        for (int i = 1; i < n; i++)
        {
            int x, y;
            cin >> x >> y;
            e[x].push_back(y);
            e[y].push_back(x);
        }
        
        dfs(1, 0); // dfs求各子树总节点个数
        
        int m;
        cin >> m;
        for (int i = 1; i <= m; i++) // 输入 K 的质因子
            cin >> p[i];
            
        if (m < n - 1) // 如果质因子个数 m 不够 n-1 条边,补 1
            m = n - 1;
            
        sort(p + 1, p + m + 1);
        
        for (int i = n; i <= m; i++) // 如果质因子个数 m 超过了 n-1 则将后面的质因子乘到第 n-1 个质因子上,保证 K 的大小
            p[n - 1] = (p[n - 1] * p[i]) % MOD;
            
        for (int i = 1; i < n; i++) // 1号节点是dfs的根节点,无与父亲节点的连边,所以求剩下的 n-1 个点与其父亲节点的连边对答案的贡献次数
            q[i] = sz[i + 1] * (n - sz[i + 1]); // 不能取模,下面要对 q[] 排序,而且也没有超longlong

        sort(q + 1, q + n);

        ll ans = 0;
        for (int i = 1; i < n; i++)
            ans = (ans + ((p[i] % MOD) * (q[i] % MOD)) % MOD) % MOD;

        cout << ans << endl;
    }
    return 0;
}

E Divide Square

题意

在平面直角坐标系中 ( 0 , 0 ) (0,0) (0,0) ( 0 , 1 6 6 ) (0,16^6) (0,166) ( 1 0 6 , 0 ) (10^6,0) (106,0) ( 1 0 6 , 1 0 6 ) (10^6,10^6) (106,106) 这四个点围成的正方形区域中,有 n n n 条水平线段和 m m m 条竖直线段,并且所有的线段至少与正方形的一侧相交,保证在同一条直线上没有两条线段,问这些线段把这个大正方形分成了多少块。

Solution

线段树 / 树状数组。
在给定的条件下,只有两种情况会导致块数增加。

  • 水平线和竖直线相交会增加一块。
  • 一条线段的两端都与大正方形相交会增加一块。

加上原来的大正方形是一块。
求线段相交的次数,可以用平行于 y y y 轴的直线从左到右扫描。具体方法:直线平行于 y y y 轴,所以树状数组是记录 y y y 轴上每个点的个数,遇到水平线段的左端点就加一,遇到水平线段的右端点就减一,这样就能知道当竖直直线扫描的这个位置时,有多少条横向线段与之相交。

这里需要注意的是,树状数组计算时是从1开始的,而题目中的坐标是从0到106,所以在计算时要把坐标加一。

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int INF = 0x3f3f3f3f;
const ll INFF = 0x3f3f3f3f3f3f3f3f;
const ll MOD = 1e9 + 7;
const int M = 1e6 + 5;
const int N = 1e5 + 5;

int n, m;
ll c[M];     // Y坐标的树状数组,注意坐标范围 1e6
struct Hnode // 横线的左右端点
{
    int x, y, d; // d = 1 左端点加一  d = -1 右端点减一
    bool operator<(const Hnode &OTHER) const { return x < OTHER.x; }
} a[N << 1];

struct Snode // 竖线
{
    int x, ly, ry;
    bool operator<(const Snode &OTHER) const { return x < OTHER.x; }
} b[N];

int lowbit(int x) { return x & (-x); }

void add(int x, int d)
{
    while (x < M)
    {
        c[x] += d;
        x += lowbit(x);
    }
}

ll query(int x)
{
    ll res = 0;
    while (x)
    {
        res += c[x];
        x -= lowbit(x);
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    cin >> n >> m;

    int cnt = 0;
    ll ans = 1; // 开始是一整块

    for (int i = 1; i <= n; i++) // 横线
    {
        int y, lx, rx;
        cin >> y >> lx >> rx;
        a[++cnt] = {lx, y, 1};
        a[++cnt] = {rx + 1, y, -1};
        if (lx == 0 && rx == 1000000)
            ans++;
    }

    for (int i = 1; i <= m; i++) // 竖线
    {
        cin >> b[i].x >> b[i].ly >> b[i].ry;
        if (b[i].ly == 0 && b[i].ry == 1000000)
            ans++;
    }

    sort(a + 1, a + cnt + 1);
    sort(b + 1, b + m + 1);

    for (int i = 1, j = 0; i <= m; i++)
    {
        while (j < n * 2 && a[j + 1].x <= b[i].x)
        {
            j++;
            add(a[j].y + 1, a[j].d); // 树状数组是从下标 1 开始的,题目中坐标从 0 开始,所以这里要把对应坐标加 1
        }
        ans += query(b[i].ry + 1) - query(b[i].ly);
    }

    cout << ans << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值