【CODEFORCES】Molly's Chemicals
CODEFORCESの問題
参考書
難しさ(初心者目線)
・考え方****
・実装**
・面白さ****
問題概略
・n個の異なった薬品を持っている
・薬品iの効果はである
・n個の薬品が並べられるので連続した薬品を選んで混ぜて整数kの累乗になるようにしたい
・この時の混ぜ方は何通りあるか
入力
n k
1≦n≦
1≦|k|≦10
≦≦
例
Input
4 2
2 2 2 2
Output
8
n=4, k=2の問題
になるのは左から1個目のみ,2個目のみ,3個目のみ,4個目のみ選んだ場合
になるのは左から[1,2]と[2,3]と[3,4]のセットを選んだ時
同様に,になる場合も調べたら8通りになる
Note
であることに注意
考え方
左からの合計値sumをmapに保存していきsum-kx(図中黄色い丸)が存在すれば残りはkx(図中青い丸)である
forは太い青い矢印とkxを求める部分
愚直な実装は青い太い矢印の2重ループだと思うが、0(N2)で間に合わない
kxは1015程度まで調べれば良いので数回であるから早い
コード
#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さんの問題
参考書
難しさ(初心者目線)
・考え方*
・実装**
・面白さ*
コード
#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さんの問題
参考書
難しさ(初心者目線)
・考え方*
・実装*
・面白さ*
コード
#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さんの問題
参考書
難しさ(初心者目線)
・考え方***
・実装***
・面白さ***
コード
#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さんの問題
参考書
難しさ(初心者目線)
・考え方***
・実装***
・面白さ**
コード
#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さんの問題
参考書
難しさ(初心者目線)
・考え方***
・実装***
・面白さ***
コード
#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
参考書
難しさ(初心者目線)
・考え方****
・実装****
・面白さ****
問題文
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木を用いている.