CCPC 2021桂林铜牌题G(二分),D(贪心)

G. Occupy the Cities

题意:给一个01串,每一轮,1都可以选择一个临近的0把0变成1,请问要把所有0都变成1需要多少轮。

思路:
这个题的思路相当奇怪了,我们VP的时候考虑了如下好多情况:暴力,贪心,结论,DP,二分。

神奇的是理论阶段的复杂度证明和可行性证明都对。于是便开启了漫长的wa之路

废话少说,看二分思路。

我们二分一个x值,代表我们一共几轮就要达到最终的全1状态。

对于每一个0来讲,我们需要几步才能把它变成1呢?假设距离这个0最近的一个1的距离是dis,那么显然有两种情况:dis或者dis+1(你先别说11这种情况,我们就先这么考虑),然后发现如果一定要求距离时dis的话,那么在dis轮的每一步都必须向这个0所在的位置前进,我们称之为趋向,每个1的趋向确定后就不可再次更改。如果不要求一定是dis,那么就可以

当有确定趋向的需求时
我们优先去考虑后方未被使用过的1来确定他的趋向,当我们无法选择后方时再选择前方,当都不能确定趋向时(那就寄啦!)

所以我们得到这样的结论:二分的ans值就是0与1距离的最小值满足达到最终状态的要求。

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define endl '\n'
const int N = 1e6+100;

int pre[N],suf[N],vis[N];
char s[N];
int n;
bool check(int x)
{
    for(int i=0;i<=n+1;i++)
        vis[i]=0;
    for(int i=1;i<=n;i++)
    {
        if(s[i]=='1')
            continue;
        int minn=min(pre[i]-i,i-suf[i])+1;//获得较远距离
        if(minn<=x)//较远距离可以接受
            continue;
        if(minn-1>x)//较近距离不可以接受
            return false;
        if(minn-1==x)//必须用较近距离时,判断可不可以用
        {
            if(suf[i]>=1&&minn-1==i-suf[i]&&!vis[suf[i]])//先用左侧的1,
                vis[suf[i]]=1;
            else if(pre[i]<=n&&minn-1==pre[i]-i&&!vis[pre[i]])
                vis[pre[i]]=1;
            else
                return false;
        }
    }
    return true;
}
signed main()
{
    //cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    int t;
    for(cin>>t;t;t--)
    {
        cin>>n;
        cin>>(s+1);
        int last=1e9;
        for(int i=n;i>=1;i--)
        {
            if(s[i]!='1')
               pre[i]=last;
            else
            {
                last=i;
                pre[i]=i;
            }
        }
        last=-1e9;
        for(int i=1;i<=n;i++)
        {
            if(s[i]!='1')
            {
                suf[i]=last;
            }
            else
            {
                last=i;
                suf[i]=i;
            }
        }
        int l=0,r=n+1;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(check(mid))
            {
                r=mid-1;
            }
            else
            {
                l=mid+1;
            }
        }
        cout<<l<<'\n';
    }
    return 0;
}

D. Assumption is All You Need

题意:给定两个排列:a和b,要求你选择最多n*(n-1)/2对逆序对交换a中的元素,让a等于b

思路:
突破点查找:逆序对,排列,n*(n-1)/2,

那么我们怎么去考虑呢。
逆序对,这个告诉我们选择的方向,我们总是会希望有尽可能多的选择权,或者叫最为稳健的选择,那么我们希望每次交换选择的逆序对都尽量让大数上前面去,或者说让尽可能大的数上前面去,这就是我们的最稳健的操作。

排列,我们需要的就是当前需要交换的数的位置x,和将要交换到的位置p之间最大值,用suf数组维护:从i开始往后的可以和目前的元素交换的最大值。

#include <bits/stdc++.h>

using namespace std;
#define endl '\n'
#define int long long

const int N = 1e7+100;
int a[N],b[N],n,t,pos[N];
int suf[N];
vector<pair<int,int>>ans;
signed main()
{
    cin.tie(0);cout.tie(0);ios::sync_with_stdio(0);
    for(cin>>t;t;t--)
    {
        ans.clear();
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>a[i],pos[a[i]]=i;
        for(int i=1;i<=n;i++)
        {
            cin>>b[i];
        }
        bool falg=true;
        for(int i=n;i>=1;i--)
        {
            if(a[i]==b[i])
                continue;
            if(a[i]>b[i])
            {
                falg=false;
                break;
            }
            int p=pos[b[i]];
            for(int j=0;j<=n+1;j++)
                suf[j]=0;
            suf[i]=i;
            for(int j=i-1;j>=p;j--)
            {
                if(a[j]<b[i])
                {
                    if(a[j]>a[suf[j+1]])
                        suf[j]=j;
                    else
                        suf[j]=suf[j+1];
                }
                else
                    suf[j]=suf[j+1];
            }
            while(p<i)
            {
                int x=p,y=suf[p+1];
                ans.push_back(make_pair(x,y));
                pos[a[x]]=y;
                pos[a[y]]=x;
                swap(a[x],a[y]);
                p=y;
            }
        }
        if(falg)
        {
            cout<<ans.size()<<endl;
            for(auto x:ans)
                cout<<x.first<<" "<<x.second<<endl;
        }
        else
        {
            cout<<"-1"<<endl;
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值