【CODEFORCES】Molly's Chemicals

CODEFORCESの問題

Problem - C - Codeforces

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方****

・実装**

・面白さ****

ヒント(カーソル合わせると見れます)

問題概略

・n個の異なった薬品を持っている
・薬品iの効果は{\alpha_i}である
・n個の薬品が並べられるので連続した薬品を選んで混ぜて整数kの累乗になるようにしたい
・この時の混ぜ方は何通りあるか

入力

n k
{\alpha_1 \alpha_2 ,,, \alpha_n}

1≦n≦10^{5}
1≦|k|≦10
{-10^{9}}{\alpha_i}{10^{9}}

Input

4 2
2 2 2 2

Output

8

n=4, k=2の問題
2^{1}になるのは左から1個目のみ,2個目のみ,3個目のみ,4個目のみ選んだ場合
2^{2}になるのは左から[1,2]と[2,3]と[3,4]のセットを選んだ時
同様に2^{3},2^{4}になる場合も調べたら8通りになる

Note

k^{0}=1であることに注意

考え方

左からの合計値sumをmapに保存していきsum-kx(図中黄色い丸)が存在すれば残りはkx(図中青い丸)である
forは太い青い矢印とkxを求める部分
愚直な実装は青い太い矢印の2重ループだと思うが、0(N2)で間に合わない
kxは1015程度まで調べれば良いので数回であるから早い f:id:nenuon61:20170302210101p:plain
f:id:nenuon61:20170302210105p:plain

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
using namespace std;

#define ll         long long
#define PI         acos(-1.0)
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

//方針
//愚直に全探索(2重のfor)でやるとO(N^2)でTLEしてしまう
//[1,i](i=1~N)で作れる薬品の効果をmap[効果]=1としておく
//1~Nで見ていく
//map[1~iの和-k^x]==1だと作れる(ans++)

int main(){
    int n, k;
    cin >> n >> k;

    //map用意αが大きいのでllとしておく
    //0は何も使わない時なので作れるので1としておく
    map<ll, int> MAP;
    MAP[0] = 1;

    ll int ans = 0;
    ll int sum = 0;
    FOR(i, 0, n){
        int input;
        cin >> input;

        //これまで調べた薬品を全て使用した時の和
        sum += input;
        //k=1の時は1を何乗しても1なので1を引く
        if(k == 1){
            ans += MAP[sum-1];
        }
        //k=-1の時は1と-1になるかどうか調べる
        else if(k == -1){
            ans += MAP[sum-1] + MAP[sum+1];
        }
        else{
            //|k|>1の時は十分大きな数まで調べる
            int j = 0;
            while(1){
                ans += MAP[sum-pow(k,j)];
                j++;
                if(abs(pow(k, j))>pow(10,15)) break;
            }
        }
        //これまで調べた薬品を全て使用した時の和を保存
        MAP[sum]++;
    }
    cout << ans << endl;
}

コメント

連続した値の和がxになるかどうかという問題があったらこの方法を用いたいと思う.

【yukicoder】No.90 品物の並び替え

yukicoderさんの問題

No.90 品物の並び替え - yukicoder

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方*

・実装**

・面白さ*

ヒント(カーソル合わせると見れます)

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
using namespace std;

#define ll         long long
#define PI         acos(-1.0)
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

//方針
//Nが小さいので順列を全探索しても9!=362880パターン=4*10^5くらい
//得られる点数は2重ループで計算する.(計算量はM*M/2)
//ギリギリ??
//->N=9M=66で14msだった

int main(){
    int N, M;
    cin >> N >> M;

    //点数を保存score[後][前]
    int score[N][N];
    //memset(score, 0, sizeof(score));
    FOR(i, 0, N) FOR(j, 0, N) score[i][j] = 0;
    FOR(i, 0, M){
        int item1, item2, tmpscore;
        cin >> item1 >> item2 >> tmpscore;
        score[item2][item1] = tmpscore;
    }

    //全探索0~N-1の並び替え
    vector<int> v;
    FOR(i, 0, N) v.push_back(i);
    int ans = 0;
    do{
        int ret = 0;
        //v[usiro]が後ろにある数字v[mae]が前にある数字
        //なのでそれをscoreの配列に入れて点数を調べる
        FOR(usiro, 1, N) FOR(mae, 0, usiro) ret += score[v[usiro]][v[mae]];
        ans = max(ans, ret);
    }while(next_permutation(v.begin(), v.end()));
    cout << ans << endl;
}

コメント

Nが小さかったので全探索ができた.
並び替えはdo{処理}while(next_permutation(並び替えたいvector.begin(), vector.end()));でできる.

【yukicoder】No.207 世界のなんとか

yukicoderさんの問題

No.207 世界のなんとか - yukicoder

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方*

・実装*

・面白さ*

ヒント(カーソル合わせると見れます)

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
using namespace std;

#define pb         push_back
#define mp         make_pair
#define ll         long long
#define PI         acos(-1.0)
#define ALL(A)     (A).begin(), (A).end()
#define vsort(v)   sort(v.begin(),v.end())
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

int main(){
    ll int A, B;
    cin >> A >> B;
    string s;
    FOR(i, A, B+1){
        if(i%3==0) cout << i << endl;
        else{
            //stringにすると3を探すのが楽
            s = to_string(i);
            if(s.find("3")!=-1) cout << i << endl;
        }
    }
}

コメント

3の付く数字かどうか調べるのが厄介だけど数字をto_stringでstring型にしてからfindで3を探せば一発でわかる

【yukicoder】No.7 プライムナンバーゲーム

yukicoderさんの問題

No.7 プライムナンバーゲーム - yukicoder

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方***

・実装***

・面白さ***

ヒント(カーソル合わせると見れます)

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
using namespace std;

#define ll         long long
#define PI         acos(-1.0)
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

//方針
//整数nに対して引く可能性のある素数を全て試して最終的に勝てるパターンが1つでもあるならWin
//深さ優先全探索+メモ化再帰でこれを行う
//dfs(n, 先攻) はdfs(n-素数, 後攻)のどれかがLoseならWinになる

//素数保存用vector
vector<int> vp;

//n以下の素数作成用関数
//エラトステネスの篩
void MakePrimeNumber(int n){
    vp.clear();
    int tmp[n+1];
    FOR(i, 0, n+1) tmp[i] = 1;
    FOR(i, 2, n+1){
        if(tmp[i]==0) continue;
        vp.push_back(i);
        int j = i;
        while(j <= n){
            tmp[j] = 0;
            j += i;
        }
    }
}

//メモ化再帰
int dp[10001][2];
int dfs(int n, int me){
    //計算済みならdpを返す
    if(dp[n][me]!=-1) return dp[n][me];

    //前の人が1,0にした場合
    //前の人が負けになる
    //me=1(自分)なら自分は勝ちそうでないなら負けパターンだったということ
    if(n==1 || n==0) return me == 1 ? 1 : 0;

    //引ける素数全探索
    int j = 0;
    int ret = 0;
    while(1){
        //前の人が勝ちなら今の人が負け
        //前の人が負けなら今の人は勝ちなので"!"dfs
        if(n-vp[j]>=2) ret = ret || !dfs(n-vp[j], (me+1)%2);
        else break;
        j++;
        //素数の上限に達したらbreak
        if(j==vp.size()) break;
    }
    return dp[n][me] = ret;
}

int main(){
  int N;
  cin >> N;
  //素数生成
  MakePrimeNumber(N);
  //配列初期化
  FOR(i, 0, 10001) FOR(j, 0, 2) dp[i][j] = -1;
  cout << (dfs(N, 1)==1 ? "Win" : "Lose") << endl;
}

コメント

ゲームでお互い最善を尽くす系の問題.
深さ優先全探索を使う問題.
最善を尽くすをどう表現すれば良いか悩んだ何回かWAを出してしまった.
お互いベストを尽くすので勝てるパターンが一つでもあれば勝ち(ゲーム木を書くとわかりやすい).
また、メモ化再帰を使わないとLTEになる??.
素数を求めるのはエラトステネスの篩が早いです.

【yukicoder】No.4 おもりと天秤

yukicoderさんの問題

No.4 おもりと天秤 - yukicoder

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方***

・実装***

・面白さ**

ヒント(カーソル合わせると見れます)

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
using namespace std;

#define ll         long long
#define PI         acos(-1.0)
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

//方針
//動的計画法
//dp[i][j]:i個目まで調べた時に(天秤A-天秤B)がjになるかどうか
//今回はjが0になるかどうか目指す
//途中でjがマイナスになることがある-N*100~N*100の範囲
//そこでN+100プラスして0~N*200で考え,N*100になるかどうか見ていく
//計算量はN*N*100

int main(){
    int N;
    cin >> N;

    int dp[N+1][N*200+1];
    FOR(i, 0, N+1) FOR(j, 0, N*200+1) dp[i][j] = 0;
    dp[0][N*100] = 1;

    //動的計画法
    FOR(i, 0, N){
        //入力
        int w;
        cin >> w;
        FOR(j, 0, N*200+1){

            //dp[i][j]があり得ないなら次もないのでcontinue
            if(dp[i][j]==0) continue;
            //一応範囲以外だったらcontinue
            if(j+w>N*200 || j-w<0) continue;

            //Aに置く
            dp[i+1][j+w] = 1;
            //Bに置く
            dp[i+1][j-w] = 1;
        }
    }
    cout << ((dp[N][N*100] == 1) ? "possible" : "impossible") << endl;
}

コメント

【yukicoder】No.45 回転寿司

yukicoderさんの問題

No.45 回転寿司 - yukicoder

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方***

・実装***

・面白さ***

ヒント(カーソル合わせると見れます)

コード

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
using namespace std;

#define ll         long long
#define PI         acos(-1.0)
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

//方針
//N<=10^3なので使うor使わないの愚直に全探索はO(2^N)??なので無理
//動的計画法を用いる
//dp[N+1][use]
//N個目まで調べた時Nを使った時(use=1)の美味しさの和の最大とNを使ってない時(use=0)の最大

int main(){
    int N;
    cin >> N;

    //dp
    int dp[N+1][2];
    FOR(i, 0, N+1) FOR(j, 0, 2) dp[i][j] = -1;
    dp[0][0] = 0;

    //動的計画法
    FOR(i, 0, N){
        int v;
        cin >> v;

        //実際はif必要ない

        //一個前が使用していないなら使ってもいいし使わなくてもいい
        if(dp[i][0]>=0){
            dp[i+1][0] = max(dp[i+1][0], dp[i][0]);
            dp[i+1][1] = max(dp[i+1][1], dp[i][0] + v);
        }

        //一個前が使用してたら次使用しない
        if(dp[i][1]>=0){
            dp[i+1][0] = max(dp[i+1][0], dp[i][1]);
        }
    }
    cout << max(dp[N][0], dp[N][1]) << endl;
}

コメント

動的計画法を用いることでO(N)で解くことができた.
i回目を使わないときはi-1回目に使っていない場合と使っている場合があるどっちかの最大値を用いる.
i回目を使うときはi-1回目に使っていない場合だけという考え方も同じ.

【POJ】食物連鎖

POJの問題を解きました.

http://poj.org/problem?id=1182

参考書

プログラミングコンテストチャレンジブック [第2版]

難しさ(初心者目線)

・考え方****

・実装****

・面白さ****

問題文

N匹の動物がいます.
動物には番号がついており(1,2,…,N)です.
また、動物はA,B,Cの3種類のみとします.
この世には食物連鎖があり、ここでは以下が成り立ちます.
・AがBを食べる
・BがCを食べる
・CがAを食べる
さて、動物学を学んでるこうたくんにN匹の動物についての情報をK個聞きました.
情報は以下のような形式です.
type1:xとyは同じ種類
type2:xはyを食べる
しかしこうたくんは天然ぼけなので全て正しいことを言うわけではありません.
i個目の情報がi-1個目以下の情報と矛盾する場合とNより大きい番号の情報が与えられる場合があります.
これらの情報は無視します.
K個の情報が与えられるので何個の情報が無視されるか求めてください.
情報はtype x yの順で与えられます.

制約
1≦N≦50000
0≦K≦100000

入力
N K
type_1 x_1 y_1
.
.
.
type_K x_K y_K

出力
何個の情報が無視されるか

ヒント

・「同じ種類」「弱肉強食」の管理にUnion-Find木を使う

・種類の情報が与えられていないのでxがA,B,Cの時を同時に考える

コード

まだTime Limit Exceetedしてしまいます.

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <map>
#include <cmath>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <bitset>
using namespace std;

#define pb         push_back
#define mp         make_pair
#define ll         long long
#define PI         acos(-1.0)
#define ALL(A)     ((A).begin(), (A).end())
#define vsort(v)   sort(v.begin(),v.end())
#define FOR(I,A,B) for(int I = (A); I < (B); ++I)

#define MAX_N 50000

int par[MAX_N*3]; //oya
int myrank[MAX_N*3]; //ki no hu ka sa


////////////////
//Union-Find木//
////////////////

// n要素で初期化
void init(int n){
    FOR(i, 0, n){
        //最初は全部が根
        //par mean 親
        par[i] = i;
        myrank[i] = 0;
    }
}

//木の根を求める
int find(int x){
    //根なら根の番号を返す
    if(par[x] == x) return x;
    // 途中経過の親も根にしとく
    else return par[x] = find(par[x]);
}

//xとyの属する集合を併合
void unite(int x, int y){
    x = find(x);
    y = find(y);
    if(x == y) return;

    if(myrank[x] < myrank[y]){
        par[x] = y;
    }else{
        par[y] = x;
        if(myrank[x] == myrank[y]) myrank[x]++;
    }
}

bool same(int x, int y){
    return find(x) == find(y);
}

int main(){
    int N, K;
    cin >> N >> K;
    int type, x, y;

    //Union-Find treeを初期化
    //x, x+N, x+2Nをx-A,x-B,x-Cの要素とする
    //xがA,B,Cの3パターンについて矛盾がないか調べることができる
    //食物連鎖がある動物を同じグループに入れる
    init(N * 3);

    int ans = 0;
    FOR(k, 0, K){
        cin >> type >> x >> y;
        //0からに合わせる
        x--;y--;

        //ありえない数字
        if(x<0 || x>=N || y<0 || y>=N){
            ans++;
            continue;
        }
        //弱肉強食xがyを食う
        if(type == 2){
            //xがyを食うんだからx_Aとy_Aが同じグループだとダメ
            //x_Aとy_Cが同じグループだと強弱が逆だからダメ
            if(same(x, y) || same(x, y + 2 * N)) ans++;
            //矛盾がなかったら情報を更新
            else{
                unite(x, y + N);
                unite(x + N, y + 2 * N);
                unite(x + 2 * N, y);
            }
        }
        else{
          //同じなのに違う種類だったら矛盾
          if(same(x, y + N) || same(x, y + 2 * N)) ans++;
          //矛盾がなかったら情報を更新
          else{
              unite(x, y);
              unite(x + N, y + N);
              unite(x + 2 * N, y + 2 * N);
          }
        }
    }
    cout << ans << endl;
}

コメント

種類の情報がないので難しくて解けなかった.

食物連鎖」と「同じ種類」を表すためにUnion-Find木を用いている.