【Codeforces】Codeforces Round #531

Problem A

从n个数的和,也就是{\frac {n(n+1)} 2}入手。
如果和为奇数,显然无法二等分,其最小的差只能为1。
如果和为偶数,显然其可以二等分,故其最小的差可以为0。
具体的分割策略的话,可以求出{\frac {n(n+1)} 4}向下取整,然后在n个数中找出部分数凑出这个数,剩下的作为另一部分。自行dp一下可以发现这总能够凑得出来的。

类比一下用不同面额的金币凑出各种金额的问题就行了。实在不行自己写个dp验证一下。

时间复杂度为O(1)

#include <iostream>

using namespace std;

int main()
{
    int n;
    while(cin >> n)
    {
        n = (n + 1) >> 1;
        cout << n%2 << endl;
    }
    return 0;
}

Problem B

首先,先列出会输出NO的情况:

  • 颜色太少(k太小)
  • 颜色太多(k太大)

对于第二种情况的话,当且仅当n<k的时候会发生。
而对于第一种情况的话,当且仅当k小于最少使用颜色数的时候会发生。
重点在于最少使用颜色数。这个其实就是数组a中重复次数最多的元素重复的次数

排除掉NO的情况之后,k种颜色的涂法就是:

  • 先按最少使用颜色数的方案涂(枚举1到k,每次用该颜色涂尽可能多的元素)
  • 然后看还剩多少种颜色需要涂,去找重复颜色的块去涂即可(例如,有两个元素都是颜色1的话,其中就有一个可以涂成别的颜色)

用最暴力的方法来做的话,时间复杂度为O(nk)

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    int a[5005], n, k;
    int ans[5005];
    while(cin >> n >> k)
    {
        for(int i = 0; i < n; i++)
        {
            cin >> a[i];
        }
        memset(ans, -1, sizeof(ans));

        if(n < k)
        {
            cout << "NO" << endl;
            continue;
        }

        int cnt = 0;
        for(int i = 1; i <= k; i++)
        {
            bool vis[5005] = {0};
            if(cnt == n)
            {
                for(int j = 0; j < n; j++)
                {
                    if(vis[ans[j]])
                    {
                        ans[j] = i;
                        i++;
                        if(i > k)
                        {
                            break;
                        }
                    }
                    else
                    {
                        vis[ans[j]] = true;
                    }
                }
            }
            else
            {
                for(int j = 0; j < n; j++)
                {
                    if(ans[j] == -1)
                    {
                        if(vis[a[j]])
                        {
                            continue;
                        }
                        vis[a[j]] = true;
                        ans[j] = i;
                        cnt++;
                    }
                }
            }
        }

        if(cnt == n)
        {
            cout << "YES" << endl;
            for(int i = 0; i < n; i++)
            {
                if(i)
                {
                    cout << " ";
                }
                cout << ans[i];
            }
            cout << endl;
        }
        else
        {
            cout << "NO" << endl;
        }
    }
    return 0;
}

Problem C

分情况讨论:
如果x>y的话,显然跟他慢慢耗总能踹爆所有门的,答案为n。
否则,只要无法一次踹爆的门他都能跟你耗让那个门耐久越来越高。所以需要挑能一次踹爆的门下手。当然他也知道这一点,所以每当你一次踹爆一个门的时候他就会修一个【能被你一次踹爆的门】,使其成为【无法被你一次踹爆的门】。直接计算即可。
时间复杂度为O(n)

#include <iostream>

using namespace std;

int a[1005];

int main()
{
    int n, x, y;
    while(cin >> n >> x >> y)
    {
        for(int i = 0; i < n; i++)
        {
            cin >> a[i];
        }

        if(x > y)
        {
            cout << n << endl;
        }
        else
        {
            int cnt = 0;
            for(int i = 0; i < n; i++)
            {
                if(a[i] <= x)
                {
                    cnt++;
                }
            }
            cout << (cnt + 1) / 2 << endl;
        }
    }
    return 0;
}

Problem D

考虑两种情况:

  • 小的数位有多出的,大的数位有缺少的:为了让字典序最小,我们需要尽可能的替换掉靠后的小的数位为更大的数位。
  • 大的数位有多出的,小的数位有缺少的:为了让字典序最小,我们需要尽可能的替换掉靠前的大的数位为更小的数位。

基于这个思想写个函数然后对所有可能的数位组合(其实就三种:0和1,0和2,1和2)调用一下就行了。

时间复杂度为O(n)

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

string s;
int cnt[5], n;

void Fix(int a, int b)
{
    int pos = 0;
    while((cnt[a] < 0) && (cnt[b] > 0))
    {
        if(s[pos] == b + '0')
        {
            s[pos] = a + '0';
            cnt[a]++;
            cnt[b]--;
        }
        pos++;
    }

    pos = n - 1;
    while((cnt[b] < 0) && (cnt[a] > 0))
    {
        if(s[pos] == a + '0')
        {
            s[pos] = b + '0';
            cnt[b]++;
            cnt[a]--;
        }
        pos--;
    }
}

int main()
{
    while(cin >> n)
    {
        cin >> s;
        for(int i = 0; i < 3; i++)
        {
            cnt[i] = -1 * (n / 3);
        }
        for(int i = 0; i < n; i++)
        {
            cnt[s[i] - '0']++;
        }

        Fix(0, 2);
        Fix(0, 1);
        Fix(1, 2);
        
        cout << s << endl;
    }
    return 0;
}

Problem E

一个显然的性质是,对于任意区间[l, r],如果a[l]=a[r],那么对于数组b而言,这个区间内的数必须相同。
于是我们便可以提出若干个这样的区间,然后对这些区间进行合并(有相交区域的区间可以合并成一个新的大区间),最终我们可以得到若干个互不相交的区间。
设这些区间数为cnt,那么答案显然就是2^{cnt-1}

提取区间的话我们可以使用一个map用于记录每种数字的最大下标。那么合并区间的话我们便可以从左往右扫,每次通过这个map来更新区间右值,直至无法更新为止。

时间复杂度为O(n{\log}n)

#include <cstdio>
#include <map>
#include <algorithm>

using namespace std;

typedef long long LL;

const LL mod = 998244353;

LL GetAns(int p)
{
    p -= 1;
    LL ans = 1;
    while(p--)
    {
        ans <<= 1;
        ans %= mod;
    }
    return ans;
}

map<int, int> mapR;
int a[200005];

int FindR(int pos)
{
    int ret = pos;
    for(int i = pos; i <= ret; i++)
    {
        ret = max(ret, mapR[a[i]]);
    }
    return ret;
}

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        mapR.clear();
        for(int i = 0; i < n; i++)
        {
            scanf("%d", &a[i]);
            mapR[a[i]] = i;
        }

        int pos = 0;
        int cnt = 0;
        while(pos < n)
        {
            pos = FindR(pos) + 1;
            cnt++;
        }

        printf("%lld\n", GetAns(cnt));
    }
    return 0;
}

Problem F

首先需要计算出对于任意两行(第i行和第j行):

  • 如果第j行接在第i行下面的话,两行中每列的两个数的差的绝对值的最小值
  • 如果以第i行为结尾,第j行为开头的话,第i行中每个数与第j行中同样位置的下一个数的差的绝对值的最小值(也就是|a[i][k-1]-a[j][k]|

对于第一个计算的量而言,将每一行看作是一个点,且每个针对有序数对<i,j>计算出来的量作为从点i到点j的有向边的权值。就可以构成一个有向图(我们假设该有向图为图A)。以同样的方法对第二个计算出来的量建图可以得到另一个有向图B。
于是问题就转换为,在有向图A中求一条路径使其经过所有的点,且【所经过的边的权值】和【这条路径上的终点和起点在图B上的边的权值】的最小值最大。
对于前者的话显然就是一个旅行商问题的变体,照着旅行商问题的解法便可以解出来。现在需要处理掉的是后者。事实上我们可以通过枚举起点来简化后者的问题。对于每个枚举的起点求出【经过所有点的权值的最小值最大】的路径。根据终点不同会有n条不同的路径。然后对这些路径再去查图B再求一遍两两最小,然后从中取最大即可。
不知道旅行商问题的同学可以自行学习一下状态压缩dp。旅行商问题算是状态压缩dp的一个入门题目。

总的时间复杂度为O((n^2m)+(n^32^n)),前者为建图过程的复杂度,后者为枚举起点+状压dp的时间复杂度。

#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;

const int INF = 1.5e9;

int e[25][25], nxt[25][25];
int a[25][10005];

void Build(int &n, int &m)
{
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            int w = INF;
            for(int k = 0; k < m; k++)
            {
                w = min(w, abs(a[i][k] - a[j][k]));
            }
            e[i][j] = w;

            w = INF;
            for(int k = 1; k < m; k++)
            {
                w = min(w, abs(a[i][k-1] - a[j][k]));
            }
            nxt[i][j] = w;
        }
    }
}

int dp[70000][25];

int GetAns(int &src, int &n)
{
    memset(dp, -1, sizeof(dp));
    dp[1 << src][src] = INF;
    for(int bitm = 1; bitm < (1 << n); bitm++)
    {
        for(int i = 0; i < n; i++)
        {
            if(!(bitm & (1 << i)))continue;
            for(int j = 0; j < n; j++)
            {
                if(i == j)continue;
                if(!(bitm & (1 << j)))continue;
                if(bitm == (1 << j))continue;
                dp[bitm][i] = max(dp[bitm][i], min(dp[bitm - (1 << i)][j], e[j][i]));
            }
        }
    }

    int ret = -1;
    for(int i = 0; i < n; i++)
    {
        ret = max(ret, min(dp[(1 << n) - 1][i], nxt[i][src]));
    }
    return ret;
}

int main()
{
    int n, m;
    while(~scanf("%d%d", &n, &m))
    {
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < m; j++)
            {
                scanf("%d", &a[i][j]);
            }
        }

        Build(n, m);
        int ans = -1;
        for(int i = 0; i < n; i++)
        {
            ans = max(ans, GetAns(i, n));
        }
        printf("%d\n", ans);
    }
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容