Codeforces Round #599 (Div. 2)

https://codeforces.com/contest/1243

A - Maximum Square

题意:给 \(n\) 块宽度为 \(1\) 长度为 \(a_i\) 的木板,把这些木板拼在一起,求最大形成的正方形的边长。

题解:贪心,从大到小排序,然后找第一个满足 \(a_i<i\) 的位置break掉。

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

int n, a[1005];

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int k;
    scanf("%d", &k);
    while(k--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n, greater<int>());
        int ans = 0;
        for(int i = 1; i <= n; ++i) {
            if(a[i] >= i)
                ans = i;
            else
                break;
        }
        printf("%d\n", ans);
    }
    return 0;
}

B1 - Character Swap (Easy Version)

题意:给两个互异的字符串 \(s\)\(t\) ,必须选择某个 \(s[i]\)\(t[j]\) 交换恰好1次,问能否使得两个字符串相等。

题解:交换恰好1次应该是最多减少两个不等的位置,找出不等的位置之后判断是不是恰好只有两个,若是,则直接交换然后判断相等,否则直接No。

注:由于是互异的所有不用特判0,否则0是一定合法的(交换(1,1))。

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

int n;
char s[10005], t[10005];

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int k;
    scanf("%d", &k);
    while(k--) {
        scanf("%d%s%s", &n, s + 1, t + 1);
        vector<int> dif;
        for(int i = 1; i <= n; ++i) {
            if(s[i] != t[i]) {
                dif.push_back(i);
                if(dif.size() >= 3)
                    break;
            }
        }
        if(dif.size() != 2) {
            puts("No");
        } else {
            swap(s[dif[0]], t[dif[1]]);
            if(strcmp(s + 1, t + 1) == 0)
                puts("Yes");
            else
                puts("No");
        }
    }
    return 0;
}

B2 - Character Swap (Hard Version)

题意:给两个互异的字符串 \(s\)\(t\) ,选择某个 \(s[i]\)\(t[j]\) 交换不超过 \(2n\) 次,问能否使得两个字符串相等。

题解:很显然有解的必要条件就是所有的字母出现都是偶数次,先判断这个。其次满足上面的必要条件之后必定有解,因为使用最多2次交换就可以消除至少1个位置上的不同。具体的做法是:固定已经相等的前半部分,然后假如 \(s[i] \neq t[i]\),如果有某个后面的 \(s[id]=s[i]\) ,那么就把 \(s[id]\)\(t[i]\) 交换。否则,必定存在某个后面的 \(t[id]=s[i]\) ,但是 \(t[id]\) 不能直接与 \(t[i]\) 交换,所以让它与 \(s[n]\) 交换(这样比较好写),然后把 \(s[n]\)\(t[i]\) 交换。

注:输出要先输出 \(i\) 再输出 \(j\) ,这个WA不值得。用set实现的这个算法可以应对5e4级别的,可惜数据量太小了,各路神仙直接暴力。

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

int n;
char ss[55], tt[55];
int s[55], t[55];
set<int> accs[128], acct[128];
vector<pair<int, int> > op;

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int k;
    scanf("%d", &k);
    while(k--) {
        scanf("%d%s%s", &n, ss + 1, tt + 1);
        for(int c = 'a'; c <= 'z'; ++c) {
            accs[c].clear();
            acct[c].clear();
        }
        for(int i = 1; i <= n; ++i) {
            s[i] = (int)ss[i];
            t[i] = (int)tt[i];
            accs[s[i]].insert(i);
            acct[t[i]].insert(i);
        }
        bool suc = 1;
        for(int c = 'a'; c <= 'z'; ++c) {
            if((accs[c].size() + acct[c].size()) & 1) {
                suc = 0;
                break;
            }
        }
        if(suc) {
            op.clear();
            for(int i = 1; i <= n; ++i) {
                accs[s[i]].erase(i);
                if(s[i] != t[i]) {
                    if(accs[s[i]].size()) {
                        int id = *accs[s[i]].begin();
                        accs[s[id]].erase(id);
                        accs[t[i]].insert(id);
                        acct[t[i]].erase(i);
                        acct[s[id]].insert(i);
                        op.push_back({id, i});
                        swap(s[id], t[i]);
                    } else {
                        int id = *acct[s[i]].begin();
                        accs[s[n]].erase(n);
                        accs[t[id]].insert(n);
                        acct[t[id]].erase(id);
                        acct[s[n]].insert(id);
                        op.push_back({n, id});
                        swap(t[id], s[n]);

                        id = *accs[s[i]].begin();
                        accs[s[id]].erase(id);
                        accs[t[i]].insert(id);
                        acct[t[i]].erase(i);
                        acct[s[id]].insert(i);
                        op.push_back({id, i});
                        swap(s[id], t[i]);
                    }
                }
                acct[t[i]].erase(i);
            }
            puts("Yes");
            printf("%d\n", (int)op.size());
            for(auto i : op)
                printf("%d %d\n", i.first, i.second);

        } else
            puts("No");
    }
    return 0;
}

C - Tile Painting

题意:给排成一行的 \(n\) 个格子,要求所有的 \(i,j\) 满足 \(|i-j|\)\(n\) 的因子的,都要同色,求最大的涂色数。

题解:非常显然就是所有质因子的gcd,当然因为都是质因子,所以直接判断有没有多种质因子,若有多种质因子直接输出 \(1\) 就可以。为什么要放在C题坑人,放在B题不好吗。

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

ll n;

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    while(~scanf("%lld", &n)) {
        if(n == 1) {
            puts("1");
            continue;
        }
        ll cn = n;
        vector<ll> fac;
        for(ll i = 2; i * i <= cn; ++i) {
            if(cn % i == 0) {
                fac.push_back(i);
                while(cn % i == 0)
                    cn /= i;
            }
        }
        if(cn != 1)
            fac.push_back(cn);
        if(fac.size() >= 2)
            puts("1");
        else
            printf("%lld\n", fac[0]);
    }
    return 0;
}

*D - 0-1 MST

题意:给一个 \(n\) 个节点的完全图,有 \(m\) 条权为1的边,其他的边权为0,求最小生成树的权值和。

题解:改写Prim算法,放三个容器,一个权为无穷(未知),一个权为0,一个权为1,每次优先取出0容器里面的扩展,不消耗任何东西,否则取出1容器的扩展,cost++。扩展的时候1容器可以从无穷容器夺取节点,0容器可以从1容器夺取节点,所以1容器和无穷容器应该是set。复杂度想不明白。

注:换了好几次做法,最后应该是时间不够,浪费太多在前面换来换去了,应该是类似0-1BFS的思路,改写Prim算法。???这样魔改之后不就是0-1BFS了吗?就直接用0-1BFS就可以了。

补题细节:弄一个链表/并查集存放不在0容器里面的节点,遍历有序的边表时可以顺便知道哪些节点会进入0容器,哪些节点会变成1然后暂时放进1容器,吐出一个最小度点之后会删除这个链表/并查集中的大部分节点,最差情况是最小度点也是满的,也就是1e5的平方根的完全图,这种情况退化到2次方带log?但是数据小没问题(或者另外写一个算法解决这种情况),吐出点的顺序按度数来启发式吐,这样复杂度更玄学。

实验证明这样是对的。只有0-1边权的Prim实际上就和0-1BFS没有什么不同。主要是要避免遍历0边,解决的方法是记录还没有变成0边且有机会变成0边的节点,然后启发式吐出来一次尽可能把最多的0节点确定下来,复杂度玄学。

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

const int INF = 0x3f3f3f3f;
const int  MAXN = 100005;

vector<int> G[MAXN];
int d[MAXN];

int dis[MAXN], vis[MAXN];
priority_queue<pair<int, int> > one, zero;

int pre[MAXN], nxt[MAXN];

int Prim(int s, int n) {
    int cnt = 0, cost = 0;
    dis[s] = 0;
    zero.push({-d[s], s});
    nxt[pre[s]] = nxt[s];
    pre[nxt[s]] = pre[s];
    while(cnt < n) {
        int u = -1;
        if(zero.size()) {
            u = zero.top().second;
            zero.pop();
        } else {
            u = one.top().second;
            one.pop();
            if(!vis[u])
                ++cost;
        }
        if(vis[u])
            continue;
        vis[u] = 1;
        ++cnt;

        int cur = nxt[0];
        for(int j = 0; j <= d[u]; ++j) {
            int v = G[u][j];
            while(v > cur) {
                if(dis[cur] != 0 && vis[cur] == 0) {
                    dis[cur] = 0;
                    zero.push({-d[cur], cur});
                    nxt[pre[cur]] = nxt[cur];
                    pre[nxt[cur]] = pre[cur];
                }
                cur = nxt[cur];
            }
            if(v == cur) {
                if(cur <= n && dis[cur] == INF) {
                    dis[cur] = 1;
                    one.push({-d[cur], cur});
                }
                cur = nxt[cur];
            }
        }
    }
    return cost;
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int n, m;
    while(~scanf("%d%d", &n, &m)) {
        while(one.size())
            one.pop();
        while(zero.size())
            zero.pop();
        for(int i = 1; i <= n; ++i) {
            G[i].clear();
            dis[i] = INF;
            vis[i] = 0;
        }
        for(int i = 0; i <= n; ++i) {
            pre[i] = i - 1;
            nxt[i] = i + 1;
        }
        while(m--) {
            int u, v;
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        int s = 1;
        for(int i = 1; i <= n; ++i) {
            d[i] = G[i].size();
            sort(G[i].begin(), G[i].end());
            G[i].push_back(n + 1);
            if(d[i] < d[s])
                s = i;
        }
        printf("%d\n", Prim(s, n));
    }
    return 0;
}

*E - Sum Balance

目前水平太差了,代码和题解都看不懂,等橙了再说吧。

update:还真的橙了,回来补题。

题意:给 \(k leq 15\) 个盒子,每个盒子有若干个数字,每个盒子的数不超过 \(5000\) 个。所有的数字两两互异。要求一种方案,使得从每个盒子拿出一个数字然后放到一个盒子(可以放回去)里,并且所有的盒子移动前后数字的数量不能改变,且要使得所有盒子的数字的和相等。

题解:首先,显然每个数字的和必须是相等的,意味着和必须是 \(k\) 的倍数,并且每个盒子要变成的和已经确定。这种移动方案会使得每个盒子有一个出度和一个入度,那么这个就是若干个不相交的环(每个盒子看作一个点,都有一个出度和一个入度,就是若干个环)。从元素最少(或者任意一个)的节点中,枚举出边的权值,那么入边的权值也会随之确定,由于数字的唯一的,那么要么找到这个数字没有被作为出度使用过且在环中的节点里,要么无解。若有解则可以继续往下递归,直到形成一个环。处理这个情况,每个点需要5000的复杂度。在这里之后,每个点取出某个数字作为出边,是否合法,以及合法的话使用的方案(点数)是什么已经确定。搞定了这个问题之后,使用子集dp来合并若干个环,只需要记录是否可行以及由哪个状态转移得到,最后验证所有节点的方案是否可行并输出答案。

参考博客:https://blog.csdn.net/Scar_Halo/article/details/102964709

posted @ 2019-11-07 01:27  KisekiPurin2019  阅读(380)  评论(0编辑  收藏  举报