cf 比赛 02

2021.04.23

训练地址

B. GCD and MST

在这里插入图片描述

  • 思路:可以模仿 Kruskal 的最小生成树的方法。我们从小到大枚举权值,然后往左往右分别找。每当出现不整除或者已经在同一连通块儿中,就break掉. 否则把当前的 a i d a_{id} aid 当作变权加入。
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N = 200010;
typedef long long ll;
typedef pair<ll, ll> P;
ll a[N], p;
int n;
int fa[N];
void init(int n)
{
    for(int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}
void unite(int a, int b)
{
    if(find(a) == find(b)) return;
    fa[find(a)] = find(b);
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%lld", &n, &p);
        init(n);
        vector<P> v;
        for(int i = 1; i <= n; i++){
            scanf("%lld", &a[i]);
            v.push_back({a[i], i});
        }
        sort(v.begin(), v.end());
        ll ans = 0;
        int cnt = 0;
        for(auto pp : v){
            int x = pp.x, id = pp.y;
            for(int i = id - 1; i > 0; i--){
                if(find(id) == find(i)) break;
                if(a[i] % a[id] != 0) break;
                ll w = min(p, a[id]);
                unite(id, i);
                ans += w;
                cnt++;
            }
            for(int i = id + 1; i <= n; i++){
                if(find(id) == find(i)) break;
                if(a[i] % a[id] != 0) break;
                ll w = min(p, a[id]);
                unite(id, i);
                ans += w;
                cnt++;
            }
        }
        ans += p * (n - 1LL - cnt);
        printf("%lld\n", ans);
    }
    return 0;
}

C. Cost Equilibrium

在这里插入图片描述

  • 不难写。当 sum % n != 0 的时候,答案就是0。
  • 然后,当出现不交叉的时候,这种排列是合法的。我们考虑这个:2, -2, 2, -2. 这样子的就会出现交叉的情况(第一个数和第四个数,第二个数和第三个数,这样显然cost不是最小的). 因此,我们如下分类讨论,先将每个数减去平均数
  • 如果正数的数量为1或者负数的数量为1,那么就是全排列的数量
  • 如果全为0的话,那么答案就是1
  • 那么,序列中就必然正数负数的数量都多于1个。只有正数和负数都在两边的时候,才是合法排列。那么还有一个问题,就是0的位置可以随便放,所以要乘上 C n c n t 0 C_{n}^{cnt0} Cncnt0. 不过最后排列要去除重复的值(多重集合全排列)。不过这个地方要小心,因为在计算组合数的时候就已经去除过重复的了,所以0不要算进去。
  • 最后一个问题,就是哈希表的扩容是比较费时间的。而且这道题第60个数据点会卡掉这个。因此我们定义的时候,就 unordered_map<ll, ll> um(N),直接规定好桶的数量,就不会超时了。当然 map 就不会存在这种扩容问题。
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
const ll mod = 1e9 + 7;
ll a[N], fact[N] = {1}, infact[N] = {1};
ll mod_pow(ll x, ll n)
{
    ll res = 1;
    while(n){
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}

ll C(ll a, ll b)
{
    return fact[a] * infact[b] % mod * infact[a - b] % mod;
}
unordered_map<ll, ll> um(N);

int main()
{
    for(ll i = 1; i < N; i++) {
        fact[i] = i * fact[i - 1] % mod;
        infact[i] = infact[i - 1] * mod_pow(i, mod - 2) % mod;
    }
    int n;
    scanf("%d", &n);
    ll sum = 0;
    for(int i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        sum += a[i];
    }
    if(sum % n){
        printf("0\n");
    }
    else{
        ll ave = sum / n;
        ll negative = 0, positive = 0, zero = 0;
        for(int i = 1; i <= n; i++){
            if(a[i] > ave) positive++;
            else if(a[i] < ave) negative++;
            else zero++;
            um[a[i] - ave]++;
        }
        if(negative == 1 || positive == 1){
            ll ans = fact[n];
            for(auto p : um){
                ans = ans * infact[p.second] % mod;
            }
            printf("%lld\n", ans);
        }
        else{
            ll ans = fact[negative] * fact[positive] % mod;
            if(negative != 0 && positive != 0) ans = ans * 2 % mod;
            ans = ans * C(n, zero) % mod;
            for(auto p : um){
                if(p.first != 0) ans = ans * infact[p.second] % mod;
            }
            printf("%lld\n", ans);
        }
    }
    return 0;
}

D. Swapping Problem

在这里插入图片描述

  • 思路:直接看题解:
    在这里插入图片描述
    在这里插入图片描述
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef long long ll;

int n;
const int N = 200010;
ll a[N], b[N];
int main()
{
    scanf("%d", &n);
    map<ll, ll> segX, segY;
    for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    for(int i = 1; i <= n; i++) scanf("%lld", &b[i]);
    for(int i = 1; i <= n; i++){
        if(a[i] <= b[i]){
            if(!segX.count(a[i])) segX[a[i]] = b[i];
            else segX[a[i]] = max(segX[a[i]], b[i]);
        }
        else{
            if(!segY.count(b[i])) segY[b[i]] = a[i];
            else segY[b[i]] = max(segY[b[i]], a[i]);
        }
    }
    ll mx = -1e18;
    for(auto & p : segX){
        mx = max(mx, p.y);
        p.y = mx;
    }
    mx = -1e18;
    for(auto & p : segY){
        mx = max(mx, p.y);
        p.y = mx;
    }
    ll res = 0, ans = 0;
    //当 a[i] <= b[i] 的时候,我们只关心 b[j] <= a[i] 的情况,因为另外一种情况可以在 a[i] > b[i] 的时候考虑到.
    for(int i = 1; i <= n; i++){
        if(a[i] <= b[i]){
            //因为it.y 保存的是左端点从 -inf ~ it.x 的,右端点的最大值.
            auto it = segY.upper_bound(a[i]);
            if(it == segY.begin()) continue;

            it--;
            res = max(res, 2LL * (min(it->y, b[i]) - a[i]));
        }
        else{
            auto it = segX.upper_bound(b[i]);
            if(it == segX.begin()) continue;

            it--;
            res = max(res, 2LL * (min(it->y, a[i]) - b[i]));
        }
    }
    for(int i = 1; i <= n; i++) ans += abs(a[i] - b[i]);
    cout << ans - res << endl;
}

E. Balance the Bits

在这里插入图片描述
如果可以构造的话,就把这两个字符串输出。
1的个数如果是奇数,无解。

  • 首先在所有1的位置,前一半全部填成左括号,后一半填成右括号。然后在所有0的位置。第一个串 ‘(’, ‘)’ 交替填,第二个串 ‘)’, ‘(’ 交替填,最后看第二个串是否是一个合法串。
  • 具体的原理是这样的。建立一个坐标系,横轴表示第一个串左括号的数量减右括号的数量;纵轴表示第二个串左括号的数量减右括号的数量。跑出第一象限(包括坐标轴),就不符合题意。在1的位置填写,相当于往右上方走。在0的填写,相当于往 y = -x 的方向震动。那么我们首先使劲往右上方走(然后再回来),其次让它来回震动,尽可能不要离 y = x 太远。这样子可以尽可能避免非法串的情况。
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
char s[N], ans1[N], ans2[N];
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        int n;
        scanf("%d%s", &n, s + 1);
        int cnt = 0;
        for(int i = 1; i <= n; i++) cnt += s[i] == '1';
        bool flag = true;
        if(cnt & 1) flag = false;
        if(flag){
            int cnt1 = 0, cnt2 = 0;
            for(int i = 1; i <= n; i++){
                if(s[i] == '1'){
                    if(cnt1 < cnt / 2){
                        ans1[i] = ans2[i] = '(';
                        cnt1++;
                    }
                    else{
                        ans1[i] = ans2[i] = ')';
                    }
                }
                else{
                    if(cnt2 & 1){
                        ans1[i] = ')', ans2[i] = '(';
                        cnt2++;
                    }
                    else{
                        ans1[i] = '(', ans2[i] = ')';
                        cnt2++;
                    }
                }
            }
            int xx = 0;
            for(int i = 1; i <= n; i++){
                if(ans2[i] == '(') xx++;
                else xx--;
                if(xx < 0){
                    flag = false;
                    break;
                }
            }
        }
        ans1[n + 1] = ans2[n + 1] = 0;
        if(flag) printf("YES\n%s\n%s\n", ans1 + 1, ans2 + 1);
        else printf("NO\n");
    }
    return 0;
}

F. 3-Coloring

  • 你和一个人玩一个游戏。你需要在一个 n ∗ n n * n nn 的 棋盘上填满颜色。总共有三种颜色可以选择。他每次都先禁用一种颜色。然后你用另外两种颜色填充一次。
  • 思路:首先优先在 i + j 为奇数的位置填2, i + j 为偶数的位置填1. 总有一个位置会先填完。加入2先填完。然后剩下空余的位置,当没有禁止1的时候填1,其他情况填3.
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N = 110;
typedef pair<int, int> P;
vector<P> odd, even;
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1, k = 0; i <= n; i++){
        for(int j = 1; j <= n; j++){
            if((i + j) & 1) odd.push_back({i, j});
            else even.push_back({i, j});
        }
    }
    int id1 = 0, id2 = 0, sz1 = odd.size(), sz2 = even.size();
    for(int i = 1; i <= n * n; i++){
        int x;
        scanf("%d", &x);
        if(id1 < sz1 && id2 < sz2){
            if(x == 1){
                printf("%d %d %d\n", 2, odd[id1].x, odd[id1].y);
                id1++;
            }
            else{
                printf("%d %d %d\n", 1, even[id2].x, even[id2].y);
                id2++;
            }
        }
        else if(id1 < sz1){
            if(x != 2){
                printf("%d %d %d\n", 2, odd[id1].x, odd[id1].y);
            }
            else{
                printf("%d %d %d\n", 3, odd[id1].x, odd[id1].y);
            }
            id1++;
        }
        else{
            if(x != 1){
                printf("%d %d %d\n", 1, even[id2].x, even[id2].y);
            }
            else{
                printf("%d %d %d\n", 3, even[id2].x, even[id2].y);
            }
            id2++;
        }
        fflush(stdout);
    }
    return 0;
}

G. Travelling Salesman Problem

在这里插入图片描述

贪心算法

  • c i + max ⁡ { 0 , a j − a i − c i } c_i + \max \{0, a_j - a_i - c_i\} ci+max{0,ajaici},那么,我们对 a 排序,如果 a j > a i + c i a_j > a_i + c_i aj>ai+ci,对于 i ∈ [ 1 , j − 1 ] i \in [1, j - 1] i[1,j1] 都成立,那么这个 a j − a i − c i a_j - a_i - c_i ajaici 的增量一定不可避免地会加上去。
  • 我们从前往后开始找,我们保存当前遍历到的 a + c a+c a+c 的最大值(设为 t t t)。如果当前的 a i > t a_i > t ai>t,那么当前的增量就不可避免。这样子,我们假设找到的这些转移的点分别为 a 1 , a j , a j ′ , a j ′ ′ , . . . a_1, a_j, a_{j'}, a_{j''},... a1,aj,aj,aj,...,那么我们可以这样一直往后跳,然后再跳到 a n a_n an
  • 再往回跳,依次再跳到之前没有遍离过的点,最后跳到 a 1 a_1 a1。因为往回跳的时候, a j − a i a_j - a_i ajai 是非正数,不会产生对答案的贡献。
#include<bits/stdc++.h>
#define a first
#define c second
using namespace std;
const int N = 100010;
typedef long long ll;
typedef pair<ll, ll> P;
P w[N];
int main()
{
    int n;
    scanf("%d", &n);
    ll ans = 0;
    for(int i = 1; i <= n; i++){
        scanf("%lld%lld", &w[i].a, &w[i].c);
        ans += w[i].c;
    }
    sort(w + 1, w + n + 1);
    ll t = w[1].a + w[1].c;
    for(int i = 1; i <= n; i++){
        if(t < w[i].a){
            ans += w[i].a - t;
        }
        t = max(t, w[i].a + w[i].c);
    }
    cout << ans << endl;
    return 0;
}

最短路做法

  • 我们先对 a 排序,然后对每一个 i 向右找到一个 a j > a i + c i a_j > a_i + c_i aj>ai+ci 的最小的 a j a_j aj. 然后对 i i i ~ j − 1 j - 1 j1 每一个建立一个0边。这有一个技巧,就是建立一个 i i i j − 1 j - 1 j1 的边,然后从 j − 1 j - 1 j1 j − 2 j - 2 j2 连一个0边, j − 2 j - 2 j2 j − 3 j - 3 j3 连一个0的边……这样子可以减少很多边.
  • 所以,我们可以最开始从 i − 1 i - 1 i1 i i i 连一个 max ⁡ { a i − a i − 1 − c i − 1 , 0 } \max\{a_i - a_{i - 1} - c_{i - 1}, 0\} max{aiai1ci1,0} 的边;从 i i i i − 1 i - 1 i1 连一个边权为0.
  • 然后从1到n 跑一个最短路. 其实本质上和贪心算法是一样的。
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N = 100010, M = N * 10;
typedef long long ll;
typedef pair<ll, ll> P;
P a[N];
int n;
int h[N], e[M], ne[M], w[M], idx;
ll d[N];
bool vis[N];
void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void build()
{
    vector<ll> aa;
    for(int i = 1; i <= n; i++) aa.push_back(a[i].x);

    for(int i = 1; i <= n; i++){
        if(i != 1) add(i - 1, i, max(0LL, a[i].x - a[i - 1].x - a[i - 1].y));
        if(i != 1) add(i, i - 1, 0);
        auto p = lower_bound(aa.begin(), aa.end(), a[i].x + a[i].y);
        if(p != aa.end()){
            add(i, p - aa.begin() + 1, *p - a[i].x - a[i].y);
        }
        add(i, p - aa.begin(), 0);
    }
}
void dijkstra()
{
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    priority_queue<P, vector<P>, greater<P>> que;
    que.push({0, 1});
    while(que.size()){
        auto p = que.top(); que.pop();
        int u = p.y;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = h[u]; i != -1; i = ne[i]){
            int v = e[i];
            if(d[v] > d[u] + w[i]){
                d[v] = d[u] + w[i];
                que.push({d[v], v});
            }
        }
    }
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%lld%lld", &a[i].x, &a[i].y);
    }
    sort(a + 1, a + n + 1);
    build();
    dijkstra();
    ll ans = 0;
    for(int i = 1; i <= n; i++){
        ans += a[i].y;
    }
    ans += d[n];
    printf("%lld\n", ans);
    return 0;
}

H. Flip the Cards

  • 题意:
    在这里插入图片描述
  • 首先,我们把1~n放在上面,把 n + 1 n+1 n+1~ 2 n 2n 2n 放在下面。统计一下 a i < b i a_i < b_i ai<bi 的集合,和 a i > b i a_i > b_i ai>bi 的集合。那么,如果下面序列可以划分为两个下降的子序列,那么就一定有解。可以模拟样例就明白了。
  • 然后,我们让下面那个序列划分成两个子序列,并且让交换次数最少。我们可以这样子弄,把序列划分成一块一块儿的。
    在这里插入图片描述
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int f[N], tag[N];
int sufmax[N];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        int x, y;
        scanf("%d%d", &x, &y);
        if(x > y){
            tag[y] = 1;
            swap(x, y);
        }
        if(x > n || y <= n){
            printf("-1");
            return 0;
        }
        f[x] = y;
    }
    sufmax[n] = f[n];
    for(int i = n - 1; i >= 1; i--){
        sufmax[i] = max(sufmax[i + 1], f[i]);
    }
    int l = 1e9, r = 1e9, mmin = 1e9;
    int a = 0, b = 0, ans = 0;
    //a表示当前这个子序列作为非反转子序列的花费,b表示当前这个子序列作为反转序列的花费.
    for(int i = 1; i <= n; i++){
        mmin = min(mmin, f[i]);
        if(l > f[i]){
            l = f[i];
            if(tag[i]) a++;
            else b++;
        }
        else if(r > f[i]){
            r = f[i];
            if(!tag[i]) a++;
            else b++;
        }
        else{
            printf("-1\n");
            return 0;
        }
        if(mmin > sufmax[i + 1]){
            ans += min(a, b);
            a = b = 0;
            mmin = 1e9;
            l = r = 1e9;
        }
    }
    printf("%d\n", ans);
    return 0;
}





2021.04.23 Contest 2050 and Codeforces Round #718 (Div.1 + Div.2) Editorial

D. Explorer Space

  • 题意:给一个方格图,有边权。问从每个点开始走 k k k 步,并且回到出发点,最小路径长度是多少。
  • 思路:如果 k k k 是奇数,那么不可能回到出发点,输出-1。如果 k k k 是偶数,那么出发走 k 2 \frac{k}{2} 2k 的最小值乘2就是答案。
  • 为了方便,我们建立邻接表,并且为每个方格点标号。 f ( u , k ) f(u, k) f(u,k) 表示从 u u u 出发走 k k k 步的最小距离。这样子, f ( u , k ) = min ⁡ { f ( v , k − 1 ) } f(u, k) = \min\{f(v, k - 1)\} f(u,k)=min{f(v,k1)}. 然后记忆化搜索就行。
#include<bits/stdc++.h>
using namespace std;
const int N = 250010, M = 1000010;
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, m, k;
int get(int x, int y)
{
    return (x - 1) * m + y;
}
int f[N][15];
int dp(int u, int depth)
{
    if(f[u][depth] != -1) return f[u][depth];
    if(depth == 0) return f[u][0] = 0;
    int tmp = 1e9;
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        tmp = min(tmp, dp(v, depth - 1) + w[i]);
    }
    return f[u][depth] = tmp;
}
int main()
{
    memset(h, -1, sizeof h);
    memset(f, -1, sizeof f);
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++){
        for(int j = 2; j <= m; j++){
            int a = get(i, j - 1), b = get(i, j);
            int c;
            scanf("%d", &c);
            add(a, b, c), add(b, a, c);
        }
    }
    for(int i = 2; i <= n; i++){
        for(int j = 1; j <= m; j++){
            int a = get(i - 1, j), b = get(i, j);
            int c;
            scanf("%d", &c);
            add(a, b, c), add(b, a, c);
        }
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            if(k & 1) printf("-1%c", j == m ? '\n' : ' ');
            else printf("%d%c", dp(get(i, j), k / 2) * 2, j == m ? '\n' : ' ');
        }
    }
    return 0;
}

E. Group Photo

在这里插入图片描述
官方题解
在这里插入图片描述

  • 在第二种情况下,很容易分析出具有单调性. 它是强制在 st + 1 的位置放上了 C,n - ed 的位置放上了 P. 那么,你会发现这样子会搜索到所有情况(比如全是 PCPCPC,或者全是 CCCPPP)。可以枚举最左边分别放P/C,右面枚举最右边分别放 P/C 来验证.
  • 这个题用了一个很巧妙的方式,用一个数组记录下了奇数位置和偶数位置的前缀和.
  • 然后,至于二分的情况,要注意左边界从-1开始枚举,因为长度为0的时候不一定合法。然后,当枚举到长度最多为len合法的时候,合法情况应该是 len + 1 中,就是 0~len 这些情况.
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
typedef long long ll;
const ll mod = 998244353;
ll a[N], sum[N], mysum[N];
int n;
ll ans;
void solve(int st, int ed)
{
    ll P = 0, C = 0;
    if(st) P += a[1];
    if(ed) C += a[n];
    int L = st + 1, R = n - ed - 1;
    for(int i = L; i <= R; i++){
        C += a[i];
        int l = -1, r = (R - i) / 2;
        while(l < r){
            int mid = (l + r + 1) / 2, len = 2 * mid;
            if(C + mysum[i + len] - mysum[i] < P + sum[R + 1] - sum[i + len] + mysum[i + len - 1] - mysum[i - 1]) l = mid;
            else r = mid - 1;
        }
        ans = (ans + l + 1) % mod;
    }

}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        ans = 0;
        for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        for(int i = 1; i <= n; i++){
            sum[i] = sum[i - 1] + a[i];
            mysum[i] = mysum[max(i - 2, 0)] + a[i];
        }

        for(int i = 1; i <= n; i++){
            if(sum[i] > sum[n] - sum[i]) ans++;
        }
        for(int i = 0; i <= 1; i++){
            for(int j = 0; j <= 1; j++){
                solve(i, j);
            }
        }
        printf("%lld\n", ans);
    }

    return 0;
}
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页