Codeforces 1093 Div2(ABCD1D2)

前言

牛魔能不能别出 C 这种题了!!

一、A. Blocked

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    sort(a.begin()+1,a.end(),greater<>());

    for(int i=2;i<=n;i++)
    {
        if(a[i]==a[i-1])
        {
            cout<<-1<<endl;
            return ;
        }
    }

    for(int i=1;i<=n;i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

二、B. OIE Excursion

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n,m;
    cin>>n>>m;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    for(int i=1;i<=n;i++)
    {
        int j=i;
        while(j<=n&&a[j]==a[i])
        {
            j++;
        }

        int len=j-i;
        if(len>=m)
        {
            NO;
        }

        i=j-1;
    }
    YES;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

三、C. Grid L

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    ll p,q;
    cin>>p>>q;

    ll k=2*p+4*q+1;
    for(ll i=3;i*i<=k;i++)
    {
        if(k%i==0)
        {
            ll x=i;
            ll y=k/i;

            ll n=(x-1)/2;
            ll m=(y-1)/2;

            ll X=n*(m+1);
            ll Y=m*(n+1);

            if(q<=min(X,Y))
            {
                cout<<n<<" "<<m<<endl;
                return ;
            }
        }
    }
    cout<<-1<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于这种构造题,还是考虑先找到必要条件,然后证明充分性。

首先,对于一个 的网格,其必然有 条横向边,有 条纵向边。又因为一条线和一个拐必然分别提供 1 条和 2 条边,那么其就必须满足 。之后发动注意力,可以发现对于一个拐,不管怎么旋转,其必然包含一条横向边和一条纵向边,那么就存在约束

之后,考虑找出一种构造策略,来证明只要 ,必然可以放下这 q 个拐。令宽小于等于高,即 ,此时就需要证明网格中可以放下 个拐。也就是说,对于 个横边,都能独立地与一条相邻竖线配对。

首先对于最左一列,让每条横线和左侧往上的竖线匹配。对于最顶上剩下的横线,让其匹配右侧往下的竖线。那么当来到第二列时,下面的横线都可以照常和左侧往上的竖线匹配。但对于最上方的两条横线,此时由于左侧的横线被占用,那么就只能也和右侧往下的竖线匹配。之后以此类推,可以发现当每条横线都只能与右侧往下的竖线匹配时,此时构成了一个完整的 的网格,符合上述约束,证明结束。

发现 是两数相加的形式,不是很好解决,那么考虑转化成两数相乘的形式。首先考虑拆开括号得 ,然后等式两边同时乘以 2 再 +1 得 。此时可以发现右侧可以进行因式分解,有

由于要求 ,那么右侧就是两个大于等于 3 的奇数相乘。此时就可以枚举 所有大于等于 3 的奇数因子,每次代入第二个约束判断,合法就直接输出即可。时间复杂度为 ,计算量大概是

四、D1. Unique Values (Easy version)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

int ask(vector<int>a)
{
    cout<<"? "<<a.size();
    for(auto x:a)
    {
        cout<<" "<<x;
    }
    cout<<endl;
    int res;
    cin>>res;
    return res;
}

void solve()
{
    int n;
    cin>>n;
    
    auto check=[&](int p)->int
    {
        vector<int>a;
        for(int i=1;i<=p;i++)
        {
            a.push_back(i);
        }
        int left=ask(a);

        vector<int>b;
        for(int i=p+1;i<=2*n+1;i++)
        {
            b.push_back(i);
        }
        int right=ask(b);

        if(left<right)
        {
            return 2;
        }
        if(left>right)
        {
            return 1;
        }
        if((p-left)%2)
        {
            return 3;
        }
        return 0;
    };

    vector<int>pos;
    for(int t=1;t<=3;t++)
    {
        int l=1;
        int r=2*n+1;
        int m;
        int ans;
        while(l<=r)
        {
            m=l+r>>1;
            if(check(m)>=t)
            {
                ans=m;
                r=m-1;
            }
            else
            {
                l=m+1;
            }
        }
        pos.push_back(ans);
    }

    cout<<"! ";
    for(auto x:pos)
    {
        cout<<x<<" ";
    }
    cout<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

根据这个次数,不难想到需要二分。

对于当前查询集合 S,令 T 为其补集,那么若这个特殊数字在 S 中出现了 x 次,那么在 T 中就出现了 3-x 次。而对于普通数字,若两次全出现在同一个集合中,那么是完全不会产生贡献的。而若在两个集合中各出现一次,此时对两集合共同贡献一次,所以可以考虑关注两集合询问结果的差异。

所以,对于询问出的 S 的结果 x 和 T 的结果 y,那么由上述分析可得,普通数字对两者的大小关系其实是没有贡献的。那么若 x>y,那么就说明特殊数字在 S 中出现了 1 次。而若 x<y,那么就说明特殊数字在 S 中出现了 2 次。

而当 x=y 时,考虑让 S 的大小减去 x,那么剩下的就是出现两次及以上的数的个数。此时,若这个结果是奇数,就说明特殊数字在 S 中出现了 3 次,否则就说明特殊数字在 S 中出现了 0 次。

有了这个结论,就可以每次问前缀和后缀的结果,然后判断是去左侧还是去右侧。那么就可以进行三次二分,每次找到当前轮中特殊数字出现的最左位置,次数为

五、D2. Unique Values (Hard version)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

int ask(vector<int>a)
{
    cout<<"? "<<a.size();
    for(auto x:a)
    {
        cout<<" "<<x;
    }
    cout<<endl;
    int res;
    cin>>res;
    return res;
}

void solve()
{
    int n;
    cin>>n;

    vector<int>to(2*n+2);
    
    auto check=[&](int p)->int
    {
        vector<int>a;
        for(int i=1;i<=p;i++)
        {
            a.push_back(to[i]);
        }
        int left=ask(a);

        return (p-left)%2;
    };

    vector<int>pos;
    int pre=1;
    for(int t=1;t<=3;t++)
    {
        int id=1;
        for(int i=pre;i<=2*n+1;i++)
        {
            to[id++]=i;
        }
        for(int i=1;i<pre;i++)
        {
            to[id++]=i;
        }

        int l=1;
        int r=2*n+1;
        int m;
        int ans;
        while(l<=r)
        {
            m=l+r>>1;
            if(check(m))
            {
                ans=m;
                r=m-1;
            }
            else
            {
                l=m+1;
            }
        }
        pos.push_back(to[ans]);
        pre=to[ans];
    }

    reverse(pos.begin(),pos.end());

    cout<<"! ";
    for(auto x:pos)
    {
        cout<<x<<" ";
    }
    cout<<endl;
}

void init()
{
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    init();
    while(t--)
    {
        solve();    
    }
    return 0;
}

对于 33 次的要求,考虑将每次问两次的部分省去。注意到如果不问后缀,那么就只能通过让 S 的大小减去 x 的结果进行判断。此时,如果是奇数,那么就能直接确定特殊数的个数是 3,其他什么也判断不了。

也就是说,此时就只能二分找出第三个特殊数字出现的位置。那么在找出第三个位置后,考虑让数组转一下,让第三个位置来到开头,然后再去找第三个出现的位置。此时二分出的答案,就是第二个特殊数字出现的位置了。

总结

这个 D2 转一下的操作也太妙了......

END

相关推荐
浅念-1 小时前
「一文吃透 BFS:从层序遍历到锯齿形、最大宽度、每层最大值」
数据结构·算法
汉克老师1 小时前
GESP5级C++考试语法知识(十三、贪心算法(一))
算法·贪心算法·海盗船·gesp5级·gesp五级·排队接水
玩转单片机与嵌入式2 小时前
玩转边缘AI(TInyML):需要掌握的C++知识汇总!
开发语言·c++·人工智能
历程里程碑2 小时前
4 Git远程协作:从零开始,玩转仓库关联与代码同步(带实操代码讲解)
大数据·c++·git·elasticsearch·搜索引擎·gitee·github
梦想画家2 小时前
Apache AGE实战指南:从Cypher语法到核心图算法
算法·cypher·apache age
刀法如飞3 小时前
Go数组去重的20种实现方式,AI时代解决问题的不同思路
后端·算法·go
汉克老师3 小时前
GESP5级C++考试语法知识(贪心算法(一)课堂例题精讲)
c++·贪心算法·gesp5级·gesp五级·贪心规律
墨染千千秋3 小时前
C++头文件的使用,和各个头文件与头文件用处
c++
呱呱巨基3 小时前
Linux 基础IO
linux·c++·笔记·学习