1. 程式人生 > 其它 >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;
}