cf 1556(div1+div2)

时间:2021-08-30
本文章向大家介绍cf 1556(div1+div2),主要包括cf 1556(div1+div2)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

比赛连接:
半天才做出B,更半天才做出C——因为少考虑了一种情况,C还WA了两次,最后才过的……

A

分析:

相当于两边同时\(+x\),然后一边\(+k\)、一边\(-k\)。稍微判断一下就好了。

代码如下

#include
using namespace std;
int T,c,d;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&c,&d);
        if((c+d)&1){puts("-1"); continue;}
        int x=(c+d)/2,ans=0;
        if(x)ans++;
        if(c==x&&d==x){printf("%d\n",ans); continue;}
        if(x-c==d-x)ans++; else ans+=2;
        printf("%d\n",ans);
    }
    return 0;
}
  

B

分析:

首先,最终状态一种是奇数位置是奇数、偶数位置是偶数;一种是奇数位置是偶数、偶数位置是奇数。用这个判断一下不合法情况就行了。然后两种取优。
至于怎么挪……一开始想了半天\(set\)、链表之类,为了维护挪了以后的序列。但是后来发现不用,因为每个数最终在的位置是确定的,比如奇数就是按它们的相对顺序排列在奇数/偶数位置上(保持相对顺序使操作次数最少)。所以按顺序直接朝目标位置挪就行。
那么怎么考虑奇数和偶数挪的时候彼此的影响呢?实际上只考虑把奇数挪到合适位置上就行,挪完以后会发现偶数也自然在合适位置上了。
我们从前往后按顺序挪,那会不会出现前面的一个奇数要挪到后面去,挪的过程中把后面的那个奇数挪前了,导致答案算错而且不优?实际上也不会。因为这种可以看作是先挪了后面的、再挪前面的(因为它们都要挪到后面去,所以这样更优)。

代码如下

#include
#define ll long long
using namespace std;
int const N=1e5+5;
ll const inf=1e12;
int T,n,a[N];
int ab(int x){return x<0?-x:x;}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n); int num0=0,num1=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]); a[i]%=2;
            if(!a[i])num0++; else num1++;
        }
        if(n&1)
        {
            if(ab(num0-num1)!=1){puts("-1"); continue;}
            ll ans=0;
            if(num0>num1)
            {
                for(int i=1,p0=1,p1=2;i<=n;i++)
                {
                    if(!a[i])ans+=ab(i-p0),p0+=2;
                    // else ans+=(i-p1),p1+=2;
                }
            }
            else
            {
                for(int i=1,p1=1,p0=2;i<=n;i++)
                {
                    if(!a[i])ans+=ab(i-p0),p0+=2;
                    // else ans+=(i-p1),p1+=2;
                }
            }
            printf("%lld\n",ans);
        }
        else
        {
            if(num0!=num1){puts("-1"); continue;}
            ll a1=0,a2=0;
            for(int i=1,p0=1,p1=2;i<=n;i++)
            {
                if(!a[i])a1+=ab(i-p0),p0+=2;
                // else a1+=(i-p1),p1+=2;
            }
            for(int i=1,p1=1,p0=2;i<=n;i++)
            {
                if(!a[i])a2+=ab(i-p0),p0+=2;
                // else a2+=(i-p1),p1+=2;
            }
            printf("%lld\n",min(a1,a2));
        }
    }
    return 0;
}
  

C

分析:

这个也想了好久……我们两个两个枚举,也就是当前\(c_i\)是一群左括号,\(c_{i+1}\)是一群右括号。然后我们用一个栈记录两个值:\(rem[i]\)\(pre[i]\),分别表示前面剩下的左括号,以及那个左括号后面跟了多少个合法的最小括号序列。下面我们考虑每个合法子串的右端点。
\(c_i>c_{i+1}\),说明当前左括号多于右括号,那么\(ans += c_{i+1}\),而\(c_{i+1}\)这些右括号作为右端点也再不能往前走了。栈增加一个元素,\(res[i] = c_i - c_{i+1}, pre[i]=1\)
\(c_i<c_{i+1}\),说明当前右括号多于左括号,那么首先$ans += c_i, c_{i+1} -= c_i \(,然后右括号继续往前走,也就是提取栈里的元素,每次\)ans += pre[i], ans += rem[i], c_{i+1} -= rem[i]\(,直到栈空了或者\)c_{i+1} \leq rem[i]\(。 如果是\)c_{i+1} < rem[i]\(,说明右括号在这里用完了,那么\)ans += c_{i+1}, rem[i] -= c_{i+1}\(,然后\)pre[i]=1\(,表示当前最右的括号与栈内此元素的左括号形成了一个合法子串。 如果是\)c_{i+1} = rem[i]\(,那么左右括号在这里恰好匹配完了。对应操作一番即可。这里要注意!!匹配完以后\)ans\(还要加上栈内前一个元素的\)pre\(,表示匹配后的子串还可以连上那些合法子串计入答案!!一开始写的时候没注意这个,调了一小时。 若\)c_i=c_{i+1}\(,和上面类似。 因为每个位置至多入栈出栈一次,所以时间复杂度是\)O(n)$的。

代码如下

#include
#define ll long long
using namespace std;
int const N=1005;
int n,c[N],cnt;
ll ans,rem[N],pre[N];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&c[i]);
    for(int i=1;i<=n;i+=2)
    {
        if(i==n)continue;//!
        if(c[i]>c[i+1])
        {
            ans+=c[i+1];
            rem[++cnt]=c[i]-c[i+1]; pre[cnt]=1;
        }
        else if(c[i]==c[i+1])
        {
            ans+=c[i];
            if(cnt)ans+=pre[cnt],pre[cnt]++;//rem[cnt]=0 only at head
            else rem[++cnt]=0,pre[cnt]=1;
        }
        else
        {
            ans+=c[i]; int rt=c[i+1]-c[i];
            while(cnt&&rt>rem[cnt])//rt!=0
            {
                ans+=pre[cnt]; ans+=rem[cnt];
                rt-=rem[cnt]; cnt--;
            }
            if(cnt)
            {
                /*
                if(rt==0)//! rt=0&&rem=0
                {
                    ans+=pre[cnt]; pre[cnt]++;
                    continue;
                }
                */
                if(rt==rem[cnt])
                {
                    ans+=pre[cnt]; ans+=rem[cnt];
                    cnt--;
                    if(cnt)
                    {
                        ans+=pre[cnt];//!!
                        pre[cnt]++;
                    }
                    else rem[++cnt]=0,pre[cnt]=1;
                }
                else
                {
                    ans+=pre[cnt]; ans+=rt;
                    rem[cnt]-=rt; pre[cnt]=1;
                }
            }
        }
        // printf("i=%d ans=%lld\n",i,ans);
    }
    printf("%lld\n",ans);
    return 0;
}
  

D

分析:

又忘记了那个重要的式子:\((x or y) + (x and y) = (x+y)\)
所以可以问六次得到前三个数的值;知道一个数的值以后就可以\(2n\)次询问把后面\(n\)个数都得到。然后排序即可。
如此简单粗暴……

代码如下

#include
#include
#define ll long long
using namespace std;
int const N=1e4+5;
int n,k,a[N];
int main()
{
    scanf("%d%d",&n,&k);
    ll s1,s2,s3,sum,x,y;
    puts("or 1 2"); fflush(stdout);
    scanf("%lld",&x);
    puts("and 1 2"); fflush(stdout);
    scanf("%lld",&y);
    s1=x+y;
    puts("or 2 3"); fflush(stdout);
    scanf("%lld",&x);
    puts("and 2 3"); fflush(stdout);
    scanf("%lld",&y);
    s2=x+y;
    puts("or 1 3"); fflush(stdout);
    scanf("%lld",&x);
    puts("and 1 3"); fflush(stdout);
    scanf("%lld",&y);
    s3=x+y;
    sum=(s1+s2+s3)/2;
    a[1]=sum-s2; a[2]=sum-s3; a[3]=sum-s1;
    for(int i=4;i<=n;i++)
    {
        printf("or 1 %d\n",i); fflush(stdout);
        scanf("%lld",&x);
        printf("and 1 %d\n",i); fflush(stdout);
        scanf("%lld",&y);
        a[i]=x+y-a[1];
    }
    sort(a+1,a+n+1);
    printf("finish %d\n",a[k]);
    return 0;
}
  

原文地址:https://www.cnblogs.com/Zinn/p/15205098.html