1. 程式人生 > 實用技巧 >Codeforces Global Round 10題解(A-D)

Codeforces Global Round 10題解(A-D)

A. Omkar and Password

題目:http://codeforces.com/contest/1392/problem/A

題解:看似覺得有些難手,但是仔細思考下就會發現,只要整個陣列中有1個與其它不一樣,那麼最終都會合成為1個數字,只有全部一樣的數字才不能合成。

程式碼:

#include<bits/stdc++.h>    //POJ不支援
#define rep(i,a,n) for (int i=a;i<=n;i++)//i為迴圈變數,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為迴圈變數, a為初始值,n為界限值,遞減。
#define pb push_back #define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0) #define fi first #define se second #define mp make_pair using namespace std; const int inf = 0x3f3f3f3f;//無窮大 const int maxn = 200010;//最大值。 typedef long long ll; typedef long double ld; typedef pair<ll, ll> pll; typedef pair
<int, int> pii; int main() { IOS; ll t,n; cin>>t; while(t--) { cin>>n; ll i,x; bool flag=false; ll sum=0; for(i=1;i<=n;i++) { cin>>x; if(i==1) sum=x; if(i!=1
&&x!=sum) flag=true; } if(flag==true) cout<<"1"<<endl; else cout<<n<<endl; } return 0; }

B. Omkar and Infinity Clock

題目:http://codeforces.com/contest/1392/problem/B

題解:這是一道很簡單的陣列的值變化問題,最終只會有兩組陣列出現,只要根據K的奇偶性,來判斷應該輸出哪組陣列即可

程式碼:

#include<bits/stdc++.h>    //POJ不支援
#define rep(i,a,n) for (int i=a;i<=n;i++)//i為迴圈變數,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為迴圈變數, a為初始值,n為界限值,遞減。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair

using namespace std;

const int inf = 0x3f3f3f3f;//無窮大
const int maxn = 200010;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll>  pll;
typedef pair<int, int> pii;

ll a[maxn],b[maxn];
int main()
{
    ll t,n,k;
    ll i,j;
    cin>>t;
    while(t--)
    {
        cin>>n>>k;
        ll m=-1000000000;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
            m=max(m,a[i]);
        }
        ll f=-1000000000;
        for(i=1;i<=n;i++)
        {
            a[i]=m-a[i];
            f=max(f,a[i]);
        }
        for(i=1;i<=n;i++)
        {
            b[i]=f-a[i];
        }
        if(k%2==1)
        {
            for(i=1;i<=n;i++)
            {
                if(i!=1)
                    cout<<" "<<a[i];
                else
                    cout<<a[i];
            }
        }
        else
        {
            for(i=1;i<=n;i++)
            {
                if(i!=1)
                    cout<<" "<<b[i];
                else
                    cout<<b[i];
            }
        }
        cout<<endl;
    }
    return 0;
}

C. Omkar and Waterslide

題目:http://codeforces.com/contest/1392/problem/C

題解:一開始設想的是從前往後找,發現要考慮的東西太多了,之後看了看別人的程式碼後,發現從後往前找就非常的簡單了,只要當前的數比前一個小,就一直加到前一個數一樣大;如果比他小,就繼續比較那個數與他前面的數進行比較。最終得到的一定是一個單調不遞減陣列,並且操作次數也是最少的

程式碼:

#include<bits/stdc++.h>    //POJ不支援
#define rep(i,a,n) for (int i=a;i<=n;i++)//i為迴圈變數,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為迴圈變數, a為初始值,n為界限值,遞減。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair

using namespace std;

const int inf = 0x3f3f3f3f;//無窮大
const int maxn = 200010;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll>  pll;
typedef pair<int, int> pii;
ll a[maxn];
int main()
{
    IOS;
    ll i,j,t,n;
    cin>>t;
    while(t--)
    {
        cin>>n;
        ll ans=0;
        for(i=1;i<=n;i++)
            cin>>a[i];
        for(i=n;i>=2;i--)
        {
            if(a[i]<a[i-1])
                ans+=a[i-1]-a[i];
        }
        cout<<ans<<endl;
    }
    return 0;
}

D. Omkar and Bed Wars

題目:http://codeforces.com/contest/1392/problem/D

題解:

一位博主採用的貪心的思路:

對於一條單鏈LLL...L或者RRR..RR,顯然我們每3個翻轉一下即可,最小運算元ceil(n/3)ceil(n/3)ceil(n/3).
否則就會有形如RRRLLRRRLLL的情況,我們把每個RRRLLL這種形態稱為V字.
顯然一個V字谷底能消化掉4個元素RRLL,然後我們剩下的兩邊的元素各進行一遍單鏈上的操作即可.

問題在於有沒有可能V字中R鏈左端翻轉去和右邊的V字合併呢,答案是否定的.
因為如何翻轉的話就一定要消耗1次,而在原鏈上是期望用1/3次而已(總之一定<1<1<1).
也就是說明這樣的牆頭草 棄暗投明 是不優的.

複雜度為O(n)O(n)O(n).

程式碼:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=4e5+10;
ll n,a[maxn],sta[maxn],top;
char s[maxn];
ll m;

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        scanf("%s",s+1);
        int ans=0,sum=0;
        for(int i=1;i<=n;i++)
        {
            s[i]=(s[i]=='R');
            sum+=s[i];
            s[i+n]=s[i];
        }
        if(!sum||sum==n)
        {
            cout<<(n+2)/3<<endl;
        }
        else
        {
            int i=1;
            for(i=1;i<=n;i++)
            {
                if(s[i+1]&&!s[i])
                {
                    i++;
                    break;
                }
            }
            for(int j=i;j<=i+n-1;j++)
            {
                s[j-i+1]=s[j];
            }
            sta[top=1]=1;
            for(i=2;i<=n;i++)
            {
                if(s[i]==s[i-1])
                    sta[top]++;
                else
                    sta[++top]=1;
            }
            ans=0;
            for(i=1;i<top;i+=2)
            {
                ans+=sta[i]/3+sta[i+1]/3;
            }
            if(i==top)
                ans+=((sta[top]+2)/3);
            cout<<ans<<endl;
        }
    }
    return 0;
}

還有位博主的思是DP:

,dp

1n,1n,但是1和n是特殊的元素,

1n所以直接把1和n定義到狀態裡去1n

dp[i][j][q][k]i1

1j,nq,k且1號往j方向打人,n號往q方向打人,當前位置往k方向打人1j,nq,k

但這麼設計狀態是有問題的,有後效性

我們知道一個位置不合法當且僅當前面,自己,後面打人的方向一致

也就是自己只被一個人打,而且自己沒有打他,這是不合法的

而對於當前狀態,根本沒記錄被幾個人打

後一個人轉移的時候無法判斷前一個位置是否合法

正確的狀態還要加一維自己被打的資訊

dp[i][j][q][k][w]i1

1j,nq,k,w示是否被上一個人打

這樣轉移就很方便

,dp[3][0][1][0][0](0,1)

2,1,n,,

那麼此時轉移是

dp[3][0][1][0][0]=dp[2][0][1][0][1]

32,2


,.233

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int n,t,dp[maxn][2][2][2][2];
char a[maxn],b[maxn];
int main()
{
    cin >> t;
    while( t-- )
    {
        cin >> n;
        cin >> (a+1);
        //dp[i][j][q][k][s] 前i滿足且1,n分別往j,q方向打人,s表示是否被左邊打
        for(int i=1;i<=n;i++)
        for(int j=0;j<=1;j++)
        for(int q=0;q<=1;q++)
        for(int k=0;k<=1;k++)
        for(int w=0;w<=1;w++)
            dp[i][j][q][k][w]=1e9;
        //下面是初始化
        dp[1][0][0][0][0]=( a[1]!='L' )+( a[n]!='L');
        dp[1][0][1][0][1]=( a[1]!='L' )+( a[n]!='R');
        dp[1][1][0][1][0]=( a[1]!='R' )+( a[n]!='L');
        dp[1][1][1][1][1]=( a[1]!='R' )+( a[n]!='R');
        for(int i=2;i<n;i++)
        for(int j=0;j<=1;j++)
        for(int q=0;q<=1;q++)
        {
            int s=1;
            if( a[i]=='L' )//往左邊打
            {
                dp[i][j][q][0][0]=dp[i-1][j][q][0][1];//一定被左邊打
                dp[i][j][q][0][1]=min(dp[i-1][j][q][1][0],dp[i-1][j][q][1][1] );
                dp[i][j][q][1][0]=min(dp[i-1][j][q][0][0],dp[i-1][j][q][0][1])+s;
                dp[i][j][q][1][1]=dp[i-1][j][q][1][0]+s;
            }
            else
            {
                dp[i][j][q][0][0]=dp[i-1][j][q][0][1]+s;
                dp[i][j][q][0][1]=min(dp[i-1][j][q][1][0],dp[i-1][j][q][1][1] )+s;
                dp[i][j][q][1][0]=min(dp[i-1][j][q][0][0],dp[i-1][j][q][0][1]);
                dp[i][j][q][1][1]=dp[i-1][j][q][1][0];
            }
        }
        int ans=1e9;
        for(int i=0;i<=1;i++)
        for(int j=0;j<=1;j++)
        for(int k=0;k<=1;k++)
        {
            if( i==0&&j==0&&k==0)//n只被1個人打卻沒有還手,不合法
                continue;
            if( i==1&&j==1&&k==1 )//n只被1個人打卻沒有還手,不合法
                continue;
            if( !(j==0&&k==0) )//除去n-1位置不合法的情況
                ans=min(ans,dp[n-1][i][j][k][0]);//n-1不被左邊打
            if( !(j==1&&k==1) )//除去n-1位置不合法的情況
                ans=min(ans,dp[n-1][i][j][k][1]);//n-1被左邊打
        }
        cout << ans << endl;
    }
}

總的來說DP還是稍微能夠理解,貪心的思路真的是望塵莫及額