Codeforces Round #258 (Div. 2)

OpenSoucre發表於2014-07-25

A - Game With Sticks

題目的意思:

  n個水平條,m個豎直條,組成網格,每次刪除交點所在的行和列,兩個人輪流刪除,直到最後沒有交點為止,最後不能再刪除的人將輸掉

解題思路:

  每次刪除交點所在的行和列,則剩下n-1行和m-1列,直到行或列被刪完為止,最多刪除的次數為min(n,m),刪除min(n,m)後剩餘的都是行或者列(注意行與行,列與列之間不可能有交點)。只需要判斷min(n,m)的奇偶性。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
    int n,m;
    cin >> n >> m;
    if(min(n,m)%2) cout<<"Akshat"<<endl;
    else cout<<"Malvika"<<endl;
}
View Code

B - Sort the Array

題目的意思:

  給一個含有n個不同數的陣列,通過反轉陣列的一個片段,該陣列變成遞增排序,問該陣列是不是滿足要求

解題思路:

  可想而知,這樣的陣列基本有序的,而且中間最多隻有一個片段是單調遞減的,這樣的陣列看起來像這樣先單調遞增,再單調遞減,在單調遞增

  假設找到單調遞減的片段(程式很簡單實現),該片段的起始索引為startIdx,結束索引為endIdx,如果陣列中存在多個這樣的片段,說明要將多個部分反轉,不滿足條件,輸出“no”,所以增加一個計數器cnt,記錄這樣的片段個數,如果cnt>1肯定不能滿足條件。

  如果只有一個這樣的片段,反轉後a[endIdx]在startIdx處,要判斷a[startIdx-1]與a[endIdx]大小,如果a[endIdx]>a[startIdx-1],說明反轉後左邊的可以保證單調遞增,現在來考慮右邊的a[endIdx+1],反轉後a[startIdx]位於a[endIdx],在這個地方要判斷endIdx+1是不是存在,如果a[endIdx+1]>a[startIdx],則反轉後右邊也是單調遞增的。

  時間複雜度O(n),注意下面程式碼中左右各增加了一個哨兵,可以避免邊界的判斷,可以繼續在下面程式優化,將空間的時間複雜度優化為O(1)  

#include <iostream>
#include <vector>
#include <algorithm>
#define MAX 1000000000+1
using namespace std;

int main(){
    int n;
    cin >> n ;
    vector<int> a(n+2,0);
    a[0]= 0;a[n+1] = MAX;
    int startIdx =1,endIdx = 1,cnt = 0;
    bool flag = false;
    for(int i = 1; i <=n; ++ i){
        cin >> a[i];
        if(a[i] < a[i-1]){
            if(!flag){ startIdx = i-1;flag = true;cnt++;}
            endIdx =i;
        }else{
            if(flag) flag = false;
        }
    }
    if(cnt > 1) cout<<"no"<<endl;
    else{
        if(a[endIdx] > a[startIdx-1] && a[endIdx+1] > a[startIdx]){
            cout<<"yes"<<endl;
            cout<<startIdx<<" "<<endIdx<<endl;
        }else cout<<"no"<<endl;
    }
}
View Code

C - Predict Outcome of the Game

題目的意思:

  三個隊進行n場比賽,已經比賽了k場,第一隊贏得次數與第二隊贏得次數的絕對值之差是d1,第二隊與第三隊贏得次數絕對值之差是d2,問是否存在n場比賽後3個隊都沒有贏,即三個隊贏得次數相等,即每個隊贏的次數是n/3,(n一定要能被3整除,否則不可能每個隊贏的次數相等)

解題思路:

  設在已經比賽的k場比賽中,第一隊贏的次數為a,第二隊贏的次數為b,第三隊贏的次數是c,則a+b+c=k

  證明a+b+c=k

  設第一隊與第二隊,第一隊贏a1次(輸了b1次),第二隊贏b1次(輸了a1次),則總共比賽了a1+b1次

  設第二隊與第三隊,第二隊贏了b2次(輸了c1次),第三隊贏了c1次(輸了b2次),則總共比賽了b2+c1次

  設第三隊與第一隊,第三隊贏了c2次(輸了a2次),第一隊贏了a2次(輸了c2次),則總共比賽了c2+a2次

  第一隊贏的次數a1+a2,第二隊贏的次數b1+b2,第三隊贏的次數c1+c2

  則 a1+b1+b2+c1+c2+a2=k   => a+b+c=k

  根據題意

  |a-b|=d1

  |b-c|=d2

  現在開始解方程

  (1)當a>=b時 a = d1+b, c = k-a-b=k-d1-2b

    |b-c|=|b-k+d1+2b|=|3b-k+d1|=d2

    當3b-k+d1>=0時

      3b = d2-d1+k

      要滿足的條件是d2-d1+k要能整除3,且a>=0 b>=0 c>=0,還要滿足a<=n/3 b<=n/3 c<=n/3 (因為每個隊最多贏的次數是n/3)

      如果條件都滿足,則存在

    當3b-k+d1< 0時

      3b=k-d1-d2

      要滿足的條件是k-d1-d2要能整除3,且a>=0 b>=0 c>=0,還要滿足a<=n/3 b<=n/3 c<=n/3 (因為每個隊最多贏的次數是n/3)

      如果條件都滿足,則存在

  (2)當a < b時 a=b-d1, c = k-a-b=k+d1-2*b

    |b-c|=|b-k-d1+2*b|=|3b-k-d1|=d2

    當3b-k-d1>=0時

      3b=k+d2+d1

      要滿足的條件是d2+d1+k要能整除3,且a>=0 b>=0 c>=0,還要滿足a<=n/3 b<=n/3 c<=n/3 (因為每個隊最多贏的次數是n/3)

      如果條件都滿足,則存在

    當3b-k-d1< 0時

      3b=k+d1-d2

      要滿足的條件是k+d1-d2要能整除3,且a>=0 b>=0 c>=0,還要滿足a<=n/3 b<=n/3 c<=n/3 (因為每個隊最多贏的次數是n/3)

      如果條件都滿足,則存在

  不用考慮剩餘的n-k場比賽,因為前面k場比賽中,三個隊贏的次數已經確定,剩下的n-k場比賽,第一隊贏的肯定是n/3-a,第二隊贏的是n/3-b,第三隊贏的是n/3-c  

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#define ll long long
using namespace std;

int main(){
    int t;
    cin >> t;
    while(t-->0){
        ll n,k,d1,d2;
        cin >> n >> k >> d1 >> d2;
        if(n%3){cout<<"no"<<endl;continue;}
        ll a = 0,b = 0, c =0;
        int flag = 0;
        if((d2-d1+k)>=0 && ((d2-d1+k)%3 == 0)){
            b = (d2-d1+k)/3;
            c = k-d1-2*b;
            a = d1+b;
            if(c>=0 && a>=0 && a-b>=0 && a<=n/3 && b<=n/3 && c<=n/3){
                flag|=1;
            }
        }
        if((k-d1-d2)>=0 && ((k-d1-d2)%3 ==0)){
            b = (k-d1-d2)/3;
            c = k-d1-2*b;
            a = d1+b;
            if(c>=0 && a>=0 && a-b>=0 &&  a<=n/3 && b<=n/3 && c<=n/3){
                flag|=1;
            }
        }
        if((k+d1+d2)%3==0){
            b=(k+d1+d2)/3;
            c = k+d1-2*b;
            a = b-d1;
            if(c>=0&& a>=0 && b>=a &&  a<=n/3 && b<=n/3 && c<=n/3 ){
                flag|=1;
            }
        }
        if((k+d1-d2)>=0 && (k+d1-d2)%3 ==0){
            b = (k+d1-d2)/3;
            c = k+d1-2*b;
            a = b-d1;
            if(c>=0&& a>=0 && b>=a &&  a<=n/3 && b<=n/3 && c<=n/3){
                flag|=1;
            }
        }
        if(flag == 0) cout<<"no"<<endl;
        else cout<<"yes"<<endl;
    }
}
View Code

D - Count Good Substrings

題目意思:

  goog string:字串中連續相同的字元用一個字元替換後,形成迴文串的字串。

  給出一個字串,該字串的字串中,長度為奇數的good string有多少個,長度為偶數的good string有多少個。

解題思路:

  因為字串只有a和b組成,只要是頭尾相同的字串都是good string。

  設頭尾相同的字串都是a,str="a.....a"由於相同的字串都是用一個字串替換,所以不可能存在相鄰的字元相同,故將連續相同的字元替換後,就形成了a和b交替排列。故str變成了"ababababa....baba",這種型別的字元顯然是迴文串。所以要使子串為good string,則頭尾應該是相同的。

  現在問題是如果頭尾相同的字串,如何判斷子串的長度?這裡要利用字元的索引的奇偶性

  假設子串str="a...a",開頭a在輸入字串中的索引為i,結尾a在輸入字串中的索引為j,則str的長度為

  如果i和j都為偶數或者奇數,則str的長度是j-i+1為奇數,(奇數-奇數=偶數,偶數-偶數=偶數,偶數+奇數=奇數)

  如果i和j一奇一偶,則str的長度j-i+1為偶數,(奇數-偶數=奇數,偶數-奇數=奇數,奇數+奇數=偶數)

  所以只需要統計a,b在奇數位置和在偶數位置的個數

  長度為偶數的good string來源,開頭a的索引和結尾a的索引一奇一偶的子串,開頭b的索引和結尾b的索引一奇一偶,

  長度為奇數的good string來源,開頭a的索引和結尾a的索引奇偶性相同,開頭b索引和結尾b的索引的奇偶性相同

#include <iostream>
#include <string>
#define C(num) ((num)*(num-1)/2)
using namespace std;

int main(){
    string s;
    cin >> s;
    long long cnt_even[2]={0},cnt_odd[2]={0};
    for(int i = 0 ; i < s.length(); ++ i){
        int index = s[i]-'a';
        if(i&1) cnt_odd[index]++;
        else cnt_even[index]++;
    }
    cout<<cnt_even[0]*cnt_odd[0]+cnt_even[1]*cnt_odd[1]<<" ";
    cout<<C(cnt_even[0])+C(cnt_even[1])+C(cnt_odd[0])+C(cnt_odd[1])+s.length()<<endl;
}
View Code

E - Devu and Flowers

題目的意思:

  有n個花壇,每個花壇有f[i]支花。同一個花壇的花顏色相同,不同花壇的花顏色不同,問取s朵花一共有多少種取法

解題思路:

  用母函式解決過硬幣問題的,這題看起來應該很熟悉,就是通過母函式求組合數,不同之處,此題的資料量比較大,要經過處理。

  得到的母函式為(1 + x + x^2 + x^3 +  + ..x^f1) ...... (1 + x + x^2 + x^3 +  + ..x^fn).

  最後得到的指數為s的前面的係數就是答案,即x^s前面的係數是答案,現在關鍵問題是如何求x^s的係數

  由於(1+x+x^2+x^3....+x^f1)=(1-x(f1+1))/(1-x)

  故上面的母函式轉換為(1-x(f1+1))/(1-x).....(1-x(fn+1))/(1-x)=(1-x(f1+1))....(1-x(fn+1))*(1-x)-n

  現在考慮前面(1-x(f1+1))....(1-x(fn+1)),由於n的值不大,最大是20,故x的指數最多有2n種不同的值,220近似(103)2=106個值

  可以浪費點空間算出這些值,注意大於s的指數直接拋棄

  由於題目結果要取模,可以在算組合數時根據Lucas定理——組合數求模 去計算,在計算組合數時還要用到求逆元運算(輾轉相除法或者快速冪演算法)

  最後求出 (1-x)-n的指數及相關係數,如果其指數與前面部分的指數和是s,者前面係數即是答案。

相關文章