Codeforces Round #557 (Div. 2) 题解(A~E)

题目地址:https://codeforces.com/contest/1162

同时发布在本人的洛谷博客:https://www.luogu.org/blog/996-icu/


一点的肝帝场,果断没有参加,其实有点可惜,这场还蛮适合打的,看了下排行榜估计我要是参加了说不定能排到div2前一两百(说的简单真去打了就很难说),个人认为前五题除去d题的话应该都还不算难写,最多一个小时出头就可以a掉,d题跑去看题解才做出来。

F题貌似很难,没几个过,看看要是有时间的话再补题解吧。


A. Zoning Restrictions Again

第一眼神似线段树,看了眼数据范围,直接n²暴力完事儿


B. Double Matrix

看完题目马上就猜想是不是把最小的放在一边,最大的放在一边,再验证一遍就可以出答案,试了下果然a了。细想下也不难证明,最小的放在一个矩阵,如果存小的矩阵有某个位置不合法的话把存大矩阵相同位置的数放过取一定也不合法(因为比原来的数还更大),因此这题就这样五分钟解决掉了。


C. Hide and Seek

开始读错题了还以为可以移动k次…最后用的是离线的做法,先读完k个询问,处理每个位置的第一次询问时间和最后一次询问时间,若第i个点的第一次询问时间晚于第i+1个点的最后一次询问时间,那么就可以由i跳到i+1个点(i-1同理),第一个位置和最后一个位置特殊判断一下就行。貌似这题边读入边判断也行。

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 201000
typedef long long ll;
int n,k,ans,x;
int st[maxn],en[maxn];
int main()
{
    cin>>n>>k;
    if (n==1) {cout<<0;return 0;}
    for (int i=1; i<=n; i++)
        st[i]=inf;
    for (int i=1; i<=k; i++)
    {
        cin>>x;
        if (st[x]!=inf) en[x]=i;
        else st[x]=i,en[x]=i;
    }
    if (st[1]>k) ans++;
    if (st[1]>=en[2]) ans++;
    for (int i=2; i<n; i++)
    {
        if (st[i]>k) ans++;
        if (st[i]>=en[i-1]) ans++;
        if (st[i]>=en[i+1]) ans++;
    }
    if (st[n]>k) ans++;
    if (st[n]>=en[n-1]) ans++;
    cout<<ans;
    return 0;
}

D. Chladni Figure

开始想了一些奇怪的解法但都有漏洞不可行,想半天后还是直接去看了官方题解,居然是暴力orz…还是错误的估计了暴力的复杂度,没想到直接O(m*√n)(实际上一般会比根号n小不少,仅为n的因子的数量)就能过。

因为周期一定会是n的因子(如果非n的因子也成立的话,则仅转动一个单位(对称周期为1)时就必定合法,至于为啥可以自己作图想想),因此直接枚举n的因子i,对每条边l->r转变成l+i->r+i然后检测是否存在该条边。

做法已经确定,但如何实现个人感觉并不是很容易,于是去参考了一下大佬的做法,大佬对stl的使用真是炉火纯青,看完佩服的五体投地。。。

用一个unordered_set存压缩过后的边(边a->b,把a二进制左移32位再加上b就的到了一个64位的边的压缩表示),这样就可以很方便的用很短的代码实现查询边的功能。

值得一提的是枚举因子的时候ladao的代码里直接从1枚举到n,当我改成从1枚举到sqrt(n)取i和n/i时神奇的事情发生了,改完提交结果居然比dalao版本还慢了五分之一,大佬的代码果然是完美至极orz

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 201000
typedef long long ll;
ll n,m,x,y;
unordered_set<ll> uset;
vector<ll> edge;
ll chord(ll x, ll y)
{
    x%=n,y%=n;
    if (x>y) swap(x,y);
    return (x<<32)|y;
}
ll rotate(ll bian, ll t)
{
    ll x = (bian>>32);
    ll y = bian & ((1LL<<32)-1);
    return chord(x+t,y+t);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for (int i=1; i<=m; i++)
    {
        cin>>x>>y;
        uset.insert(chord(x,y));
        edge.push_back(chord(x,y));
    }
    for (int i=1; i<n; i++)
    {
        if (n%i!=0) continue;
        int flag=1;
        for (auto j : edge)
        {
            if (!uset.count(rotate(j,i)))
            {
                flag=0;break;
            }
        }
        if (flag) {cout<<"Yes";return 0;}
    }
    cout<<"No";
    return 0;
}

E. Thanos Nim

这题个人感觉并不难(而且代码巨短),但是过的人比d题还少,可能是因为博弈比较偏冷门?

相比于最原始的nim石子游戏每次取石子的堆数由1变成了n/2,开始我还一直想着可不可以用SG函数来做,但一直没想出如何用SG函数表示局面的状态,遂卡了将近20分钟,最后看着样例手推出了答案。

考虑失败的条件,即轮到你时有超过一般的石子堆为空,那么必胜的条件就是能够创造出超过一半空石子堆的局面给对手。如何才能创造出超过一半空石子堆呢,因为每次最多只能减少n/2个石子堆中石子的数量,因此只有当局面中已经存在小于等于n/2个空石子堆时(空石子堆超过n/2你就已经输了!)可以创造出对手必败的条件。

于是现在必胜条件就转化为轮到你时场面中存在少于等于一半的空石子堆,因此谁先创造出了不超过一半的空石子堆给对面就必败。那么如何让对面创造出不到一半的石子堆给自己呢?当石子堆中仅有一个的石子时选择取这个石子堆的话就必须取空这个石子堆。所以只要你能率先创造出大于n/2个石子数为1的石子堆给对手,对手就不得创造出带有空石子堆的局面给你

于是必胜条件再次转化,只要你能先创造出多于n/2个石子数为一的石子堆便可必胜,那么如何创造出这个局面呢?每次只能减少n/2个石子的值,因此只有当场面中已经存在不多于n/2个石子数为一的石子堆时(若石子为1石子堆过半则你已经输了!),可以创造多余n/2石子数为1的石子堆给对面。

于是必胜条件再再次转化,只要局面中存在少于等于一半的石子数为1的石子堆时操作者就必胜这句话是不是很熟悉呢?和前前段相比,必胜局面由存在少于等于一半的空石子堆变成了石子数为1的石子堆,

继续往下推理,必胜局面所需要不超过一半的石子堆的石子数依旧会往上+1+1,总会推到系统所给定局面中石子数最少的石子堆中石子的数目,因此,最终结论便呼之欲出了————

当给定局面中存在少于等于一半的石子数最小的石子堆时,先手者就必胜

若不符合如上情况,则先手者操作一次后,留给后手的局面中石子数最小的石子堆必然不会过半,后手者就必胜

那么代码就非常好写了

#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 201000
int n,num[maxn];
int minn=inf,minc;
int main()
{
    cin>>n;
    for (int i=1; i<=n; i++)
        cin>>num[i];
    for (int i=1; i<=n; i++)
    {
        if (num[i]<minn)
            minn=num[i],minc=1;
        else if (num[i]==minn)
            minc++;
    }
    if (minc>n/2)  cout<<"Bob";
    else  cout<<"Alice";
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值