• 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
    ", 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] eq 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
    ", (int)op.size());
                for(auto i : op)
                    printf("%d %d
    ", 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
    ", 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
    ", 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

  • 相关阅读:
    OpenFlow 消息
    Revolving Digits
    Tavas and Malekas
    Prefixes and Suffixes
    快速构造后缀自动机
    REBXOR
    BZOJ3689 异或之
    BZOJ3689 异或之
    最长路径异或和
    BZOJ 1355 [Baltic2009] Bazinga
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11809724.html
Copyright © 2020-2023  润新知