AtCoder Grand Contest 023 B - Find Symmetries
- 問題
- 解法
2つ目の盤面が良い盤面であるかどうかチェックするには愚直にやるとの計算量時間がかかる。考えられるの個数は個なので全てのに対して2つ目の盤面をチェックする方法ではの計算量時間がかかってTLEになる。
なので盤面チェックの時間を減らすか、考えるの個数を減らすかのどちらかができないか考える。
ここで、あるに対してこのときの2つめの盤面が良い盤面なら、に対する2つ目の盤面も良い盤面であることに気がつく。
すると、考えるはだけでよいことになる。
この方法ならの計算量時間で問題が解けて制限時間に間に合う。
#include <iostream> #include <string> #include <algorithm> #include <cstdio> #include <vector> #include <queue> #include <set> #include <map> #include <numeric> #include <cmath> using namespace std; typedef long long int ll; #define all(x) x.begin(),x.end() const ll mod = 1e9+7; const ll INF = 1e9; const ll MAXN = 1e9; int main() { int n; cin >> n; vector<string> s(n); for(int i = 0; i < n; i++){ s[i].resize(n); for(int j = 0; j < n; j++){ cin >> s[i][j]; } } int ans = 0; vector<string> t(n); for(int a = 0; a < n; a++){ bool flag = true; for(int i = 0; i < n; i++){ t[i].resize(n); } for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ t[i][j] = s[(i+a)%n][j]; } } for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ if(t[i][j] != t[j][i]) flag = false; } } if(flag) ans += n; } cout << ans << endl; return 0; }
AtCoder Beginner Contest 099 D - Good Grid
- 問題
- 解法
色の塗替えの候補は30×29×28通り。
すべての色の塗り替え方について各マス目を見て違和感を計算する方法ではの時間計算量がかかりTLEになってしまう。
そこでに対してとなるマス目にどの色が何個あるかを事前に計算しておけば、色の塗り替え方に対しすべてのマス目を見る必要がなくなる。
結局の時間計算量でこの問題は解ける。
こういう問題に遭遇するとREPマクロ使いたくなる。
#include <iostream> #include <string> #include <algorithm> #include <cstdio> #include <vector> #include <queue> #include <map> #include <numeric> #include <cmath> using namespace std; typedef long long int ll; #define all(x) x.begin(),x.end() const ll mod = 1e9+7; const int INF = 1e9; const ll MAXN = 1e9; int main() { int N,C; cin >> N >> C; int d[31][31] = {0}, c[501][501]= {0}; for(int i = 1; i <= C; i++){ for(int j = 1; j <= C; j++){ cin >> d[i][j]; } } for(int i = 1; i <= N; i++){ for(int j = 1; j <= N; j++){ cin >> c[i][j]; } } map<int,int> color[3]; for(int i = 1; i <= N; i++){ for(int j = 1; j <= N; j++){ color[(i+j)%3][c[i][j]]++; } } int ans = INF; for(int i = 1; i <= C; i++){ for(int j = 1; j <= C; j++){ for(int k = 1; k <= C; k++){ if(i != j && j != k && k != i){ int res = 0; for(int l = 0; l < 3; l++){ int m = i; if(l == 1) m = j; else if(l == 2) m = k; for(auto itr = color[l].begin(); itr != color[l].end(); ++itr){ res += d[itr->first][m] * itr->second; } } ans = min(res,ans); } } } } cout << ans << endl; return 0; }
AtCoder Regular Contest 093 D - Grid Components
- 問題
- 解法
まず100×100のグリッドを用意します。
上半分を白、下半分を黒で塗ります。
上半分のうち奇数行目を見ていき各行について1個おきにマス目を黒くします。個のマスを黒く塗ったら終了。
こうすることで白の連結成分の数を変えずに黒の連結成分を1個ずつ増やせます。
下半分は偶数行目を見て同じように個のマスを白く塗ればよいです。
白の連結成分の数を変えずに黒の連結成分を1個ずつ増やすにはどうしたらよいかを考えると解法が浮かぶと思いました。
#include <iostream> #include <string> using namespace std; int main() { int a,b; cin >> a >> b; string s[100]; for(int i = 0; i < 100; i++){ for(int j = 0; j < 100; j++){ if(i<50) s[i].push_back('.'); else s[i].push_back('#'); } } int cnt = 0; for(int i = 0; i < 50; i += 2){ if(cnt == b-1) break; for(int j = 0; j < 100; j += 2){ if(cnt == b-1) break; s[i][j] = '#'; cnt++; } } cnt = 0; for(int i = 51; i < 100; i += 2){ if(cnt == a-1) break; for(int j = 0; j < 100; j += 2){ if(cnt == a-1) break; s[i][j] = '.'; cnt++; } } cout << 100 << " " << 100 << endl; for(int i = 0; i < 100; i++){ cout << s[i] << endl; } return 0; }
AtCoder Regular Contest 087 D - FT Robot
- 問題
- 解法
中でが出て来るたびにロボットが動く方向が軸方向→軸方向→軸方向となる。ここで軸方向と軸方向の動きは独立に考えても良い。
軸方向について動いているときを考える。このときロボットが移動できる点は(1回前に軸方向について動いたときに到達した点)±(今回動ける距離)となる。
したがって、のペアを保持すれば回目にロボットが到達できる座標がわかる。
軸方向について動いているときも同様に求められる。
文字列の最初がのときは軸の正の方向にしか動けないことに注意する。
#include <iostream> #include <string> #include <vector> using namespace std; typedef long long int ll; const ll MAXN = 8020; int main() { string s; cin >> s; int x,y; cin >> x >> y; vector<int> t; int cnt = 0; //文字列を変形する for(int i = 0; i < s.size(); i++){ if(s[i] == 'T'){ if(cnt != 0) t.push_back(cnt); t.push_back(0); cnt = 0; }else{ cnt++; if(i == s.size()-1) t.push_back(cnt); } } bool dp_x[MAXN][MAXN*2] = {false}, dp_y[MAXN][MAXN*2] = {false}; dp_x[0][8000] = true; dp_y[0][8000] = true; bool flag = false; bool xy = true; // trueならx軸について見ている int cnt_x = 0,cnt_y = 0; for(int i = 0; i < t.size(); i++){ if(t[i] == 0){ xy = !xy; }else if(xy){ cnt_x++; for(int j = 0; j < MAXN*2; j++){ if(dp_x[cnt_x-1][j]){ if(j+t[i] < MAXN*2) dp_x[cnt_x][j+t[i]] = true; if(j-t[i] >= 0 && flag) dp_x[cnt_x][j-t[i]] = true; } } }else if(!xy){ cnt_y++; for(int j = 0; j < MAXN*2; j++){ if(dp_y[cnt_y-1][j]){ if(j+t[i] < MAXN*2) dp_y[cnt_y][j+t[i]] = true; if(j-t[i] >= 0) dp_y[cnt_y][j-t[i]] = true; } } } if(!flag) flag = true; } cout << ((dp_x[cnt_x][x+8000] && dp_y[cnt_y][y+8000]) ? "Yes" : "No") << endl; return 0; }
AtCoder Regular Contest 101 D - Median of Medians
- 問題
- 解法
であり、数列の長さはなので数列を実際につくるとTLEになるので別の方法を考えないといけない。
editorialがめちゃめちゃ分かりやすいので特に書くことなし。
BITでの反転数の求め方はここ
www.geeksforgeeks.org
が分かりやすいと思いました。
#include <iostream> #include <vector> using namespace std; typedef long long int ll; #define all(x) x.begin(),x.end() struct BIT{ ll N; vector<ll> bit; BIT(ll N) : bit(N+1),N(N){}; ll sum(ll i){ ll s = 0; while(i>0){ s += bit[i]; i -= i& -i; } return s; } void add(ll i,ll x){ while(i <= N){ bit[i] += x; i += i & -i; } } }; //数列の値の範囲を[1,n]に変える vector<ll> convert_array(vector<ll> z){ ll n = z.size(); vector<ll> temp = z; sort(all(temp)); for(int i = 0; i < n; i++){ z[i] = lower_bound(all(temp), z[i]) - temp.begin() + 1; } return z; } //反転数を求める ll inverse_number(vector<ll> z){ ll n = z.size(); ll inv_num = 0; z = convert_array(z); BIT b(n); for(int i = 0; i <= n; i++){ b.bit[i] = 0; } for(int i = 0; i < n; i++){ inv_num += i - b.sum(z[i]); b.add(z[i],1); } return inv_num; } //数列の変形をしてxが中央値の候補かどうか調べる bool check(ll x,vector<ll> z){ ll n = z.size(); vector<ll> b(n); for(int i = 0; i < n; i++){ if(z[i] >= x) b[i] = 1; else b[i] = -1; } vector<ll> s(n+1,0); //ここ長さn+1になることに注意 for(int i = 1; i <= n; i++){ s[i] = s[i-1] + b[i-1]; } return (n*(n+1)/2 - inverse_number(s)) >= (n*(n+1)/2+1)/2; } int main() { ll n; cin >> n; vector<ll> a(n); for(int i = 0; i < n; i++){ cin >> a[i]; } ll low = *min_element(all(a))-1, high = *max_element(all(a))+1; while(high - low > 1){ ll mid = (low+high)/2; if(check(mid,a)) low = mid; else high = mid; } cout << low << endl; return 0; }
AtCoder Regular Contest 069 D - Menagerie
- 問題
- 解法
最初の2人の動物の種類が決まれば、3番目以降の動物の種類は順番に決まっていく、ということに気がつけば後は実装するだけ。
最初の2人の動物の種類の組合せは4通りなので、全部試してそれぞれ条件に矛盾しないかどうかを調べる。
ここでいう条件とは、動物の種類を決めていったときに1番目の動物とn+1番目の動物が同じであることと、1番目の動物の両隣が文字列と矛盾しないかどうかである。
- 感想
最初は
ans[0] = true,ans[1] = true; for(int i = 1; i < n; i++){ if(s[i] == 'o'){ if(ans[i]) ans[i+1] = ans[i-1]; else ans[i+1] = !ans[i-1]; }else{ if(ans[i]) ans[i+1] = !ans[i-1]; else ans[i+1] = ans[i-1]; } } if(check()){ for(int i = 0; i < n; i++){ if(ans[i]) cout << 'S'; else cout << "W"; }printf("\n"); return 0; }
を4回コピペしていました。
こういうときにスッとbitの形のループ?(下のコード)で書けるようになりたい。
あんまりbitの操作を使わないので積極的に使って慣れていきたい。
- コード
#include <iostream> #include <string> #include <algorithm> #include <cstdio> #include <vector> #include <queue> #include <set> #include <numeric> #include <cmath> using namespace std; typedef long long int ll; #define all(x) x.begin(),x.end() const ll mod = 1e9+7; const ll INF = 1e9; const ll MAXN = 1e9; int n; string s; vector<bool> ans; // true = S, false = W bool check(){ bool zero = (ans[0] == ans[n]); bool ss = true; if(s[0] == 'o'){ if(ans[0]){ if(ans[n-1] != ans[1]) ss = false; }else{ if(ans[n-1] == ans[1]) ss = false; } }else{ if(ans[0]){ if(ans[n-1] == ans[1]) ss = false; }else{ if(ans[n-1] != ans[1]) ss = false; } } return (zero&&ss); } void circle(bool x,bool y){ ans[0] = x,ans[1] = y; for(int i = 1; i < n; i++){ if(s[i] == 'o'){ if(ans[i]) ans[i+1] = ans[i-1]; else ans[i+1] = !ans[i-1]; }else{ if(ans[i]) ans[i+1] = !ans[i-1]; else ans[i+1] = ans[i-1]; } } } int main() { cin >> n >> s; ans.resize(n+1); for(int i = 0; i < (1<<2); i++){ circle(1&(i>>0),1&(i>>1)); if(check()){ for(int i = 0; i < n; i++){ if(ans[i]) cout << 'S'; else cout << "W"; }cout << endl; return 0; } } cout << -1 << endl; return 0; }
AtCoder Grand Contest 006 B - Median Pyramid Easy
- 問題
- 解法
実際にピラミッドを作るにはの時間計算量がかかるため無理。なので入力から直接数列を作ることを考えないといけない。
のときは無理である(中央値になりえないため)。のときはを出力すれば良い。他の場合は解説pdfにある通りで2段目の真ん中の2マスがになればよい。このようにするには、のときは数列の真ん中にを、その隣にを配置すれば良い。のときは数列の真ん中に、その隣にを配置すれば良い。
#include <iostream> #include <string> #include <algorithm> #include <cstdio> #include <vector> #include <cmath> using namespace std; typedef long long int ll; #define all(x) x.begin(),x.end() const ll mod = 1e9+7; const ll INF = 1e9; const ll MAXN = 1e9; int main() { int n,x; cin >> n >> x; if(x == 1 || x == 2*n-1) cout << "No" << endl; else{ cout << "Yes" << endl; vector<int> ans(2*n); for(int i = 0; i < ans.size(); i++){ ans[i] = i; } if(x != n){ if(x < n){ ans[n-1] = x; ans[x] = n-1; ans[n] = 1; ans[1] = n; }else{ ans[n+1] = x; ans[x] = n+1; ans[2*n-1] = n; ans[n] = 2*n-1; } } for(int i = 1; i <= 2*n-1; i++){ cout << ans[i] << endl; } } return 0; }