SRM614 div1 easy MinimumSquare
概要
xy座標上の点がn個与えられる。このうち少なくともk点を含むような正方形のうち面積が最小のものを求めろ。
考えたこと
x座標、y座標でそれぞれソートして小さい方からk点取る貪欲をしようとする。正方形を長方形だと誤読したりなぜか実装をバグらせたりした結果1時間くらい実装にかかる。サンプルを試すと落ちる。よくよく考えてみると貪欲が嘘。二分探索を思い浮かべ幅Xの正方形をつくることができるかの判定について考えてみる。各頂点が正方形の4隅になるパターンを試して判定するコードを実装するとサンプルが通った。提出するとシステスで落ちる。よくよく考えると二分探索の判定が嘘(4隅以外にすべきパターンが自明に存在する)。
解説記事を読むと2辺固定すればk点を貪欲に取れるのでO(n^3logn)で解けるとある。実装するとk=1のときの処理がバグっていてシステスで一回落とす。k=1で4を返すようにしたら通った。
色々難しく考えすぎて方針迷走&&実装バグらせまくりと色々ひどかった。n <= 10^5に慣れすぎてn <= 100が解けない。
学び
オーダーが大きめの解法もちゃんと思考に入れる。誤読しない。変な実装ミスをしない。
// BEGIN CUT HERE // END CUT HERE //#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a <= x && x < b) #define MP make_pair #define PB push_back const int INF = (1LL << 30); const ll LLINF = (1LL << 60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; // #define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; class MinimumSquare { public: long long minArea(vector<int> X, vector<int> Y, int K) { int n = X.size(); if(K == 1) return 4; ll ret = LLONG_MAX; REP(i, n) FOR(j, i+1, n) { ll sx = min(X[i], X[j]), gx = max(X[i], X[j]); VL v; REP(k, n) if(sx<=X[k] && X[k]<=gx) v.PB(Y[k]); if((int)v.size() < K) continue; sort(ALL(v)); ll l = LLONG_MAX; FOR(k, K-1, v.size()) { chmin(l, v[k]-v[k-K+1]); } l+=2; chmax(l, gx-sx+2); chmin(ret, l*l); } return ret; } };
AOJ0633 ぬいぐるみ
問題ページ
Plush Toys | Aizu Online Judge
解法
bitDPをする。dp[S] = (集合Sの要素に含まれる種類を左から並べたときの最小の並べ替え回数) とする。dp[S|1<<i] = dp[S] + (並び替えに必要な回数) と遷移できる。並べ替えに必要な回数は並べる区間に含まれていない種類iの個数なので、種類別に累積和を取っておけばO(1)で求めることができる。したがってO(M2^M)で解ける。
//#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a<=x&&x<b) #define MP make_pair #define PB push_back const int INF = (1LL<<30); const ll LLINF = (1LL<<60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; //#define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; int a[100010], b[25][100010], cnt[25]; PII dp[1LL<<20]; signed main(void) { int n, m; cin >> n >> m; REP(i, n) { cin >> a[i]; cnt[a[i]-1]++; b[a[i]-1][i] = 1; } REP(i, m) FOR(j, 1, n) b[i][j] += b[i][j-1]; REP(i, 1LL<<20) dp[i] = {INF, INF}; dp[0] = {0, 0}; REP(i, (1LL<<m)-1) REP(j, m) if(!(i>>j&1)) { int tmp = dp[i].second == 0 ? 0 : b[j][dp[i].second-1], kind = b[j][dp[i].second+cnt[j]-1] - tmp; chmin(dp[i|1LL<<j], MP(dp[i].first+cnt[j]-kind, dp[i].second+cnt[j])); } cout << dp[(1LL<<m)-1].first << endl; return 0; }
JOI2008 春合宿 Cheating
問題ページ
http://www.ioi-jp.org/camp/2008/2008-sp-tasks/2008-sp_tr-day2_21.pdf
考えたこと
最大の最小化と言われたので二分探索をする。幅Xで実現することができるかの判定について考える。x方向、y方向についてそれぞれ貪欲に決めていくと幅Xで必要なカメラの台数をO(M)で判定できる。したがってO(Mlog(max(X,Y)))で解くことができ間に合う。
//#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a<=x&&x<b) #define MP make_pair #define PB push_back const int INF = (1LL<<30); const ll LLINF = (1LL<<60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; //#define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; int n, m; VI x, y; bool check(ll mid) { int tmp = 0; // x方向 int i = 0; while(i < (int)x.size()) { int st = i; while(i < (int)x.size() && x[i] - x[st] <= mid) ++i; ++tmp; } // y方向 i = 0; while(i < (int)y.size()) { int st = i; while(i < (int)y.size() && y[i] - y[st] <= mid) ++i; ++tmp; } if(tmp <= n) return true; return false; } signed main(void) { cin >> n >> m; REP(i, m) { int xx, yy; cin >> xx >> yy; x.PB(xx); y.PB(yy); } sort(ALL(x)); x.erase(unique(ALL(x)), x.end()); sort(ALL(y)); y.erase(unique(ALL(y)), y.end()); // (low, high] ll low = -1, high = 1000000000; while(high-low>1) { ll mid = (low+high)/2; if(check(mid)) { high = mid; } else { low = mid; } } cout << high << endl; return 0; }
JOI2008 春合宿 sheet
問題ページ
http://www.ioi-jp.org/camp/2008/2008-sp-tasks/2008-sp_tr-day1_20.pdf
考えたこと
色iが色jの上に乗っていることをjからiへの有向辺が張られている状態と考えるとトポロジカルソートをすればよい。O(HWN)で辺を張ればよく、トポロジカルソートはO(N^2)でできるので計算量は問題ない。
//#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a<=x&&x<b) #define MP make_pair #define PB push_back const int INF = (1LL<<30); const ll LLINF = (1LL<<60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; //#define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; int c[105][105], sx[1010], sy[1010], gx[1010], gy[1010]; //グラフの隣接リスト VI g[100010]; //頂点の入次数を管理 int h[100010]; signed main(void) { int hh, w, n; cin >> n >> w >> hh; REP(i, hh) REP(j, w) cin >> c[i][j]; memset(sx, 0x3f, sizeof(sx)); memset(sy, 0x3f, sizeof(sy)); REP(i, hh) REP(j, w) { chmin(sx[c[i][j]], (int)j); chmin(sy[c[i][j]], (int)i); chmax(gx[c[i][j]], (int)j); chmax(gy[c[i][j]], (int)i); } REP(i, hh) REP(j, w) FOR(k, 1, n+1) { int co = c[i][j]; if(co == 0 || co == k) continue; if(IN(sx[k], gx[k]+1, j) && IN(sy[k], gy[k]+1, i)) { // coがkの上に乗っている g[k].push_back(co); h[co]++; } } //入次数が0の頂点の集合 stack<int> st; //入次数が0の頂点であればstに追加 FOR(i, 1, n+1) if(h[i] == 0) st.push(i); //ソートされた後のグラフ VI ans; //stがなくなるまでループ while(st.size()) { //stの集合のから一つ取り出す int i = st.top(); st.pop(); ans.push_back(i); for(auto& j: g[i]) { //隣接する頂点の入次数をマイナス1 h[j]--; //これによって入次数が0になればstに追加 if(h[j] == 0) st.push(j); } } //ansを順に出力 REP(i, ans.size()) { cout << ans[i]; if(i != ans.size()-1) cout << " "; } cout << endl; return 0; }
JOI2008 春合宿 Committee
問題ページ
http://www.ioi-jp.org/camp/2008/2008-sp-tasks/2008-sp_tr-day1_20.pdf
考えたこと
N頂点の木で値の総和が最大になるような連結成分を求めればいい。dp[i] = (頂点iの部分木で最大の値の総和) とするとdp[i] = sum(dp[j], 頂点jが頂点iの子でdp[j]>0) + a[i] とすることで求められる。あとは最大のdp[i]を答えとして出力すればよい。
なぜか頂点1を必ず含むような連結成分のみを答えとして出力するような実装をして3WA出して反省。
//#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a<=x&&x<b) #define MP make_pair #define PB push_back const int INF = (1LL<<30); const ll LLINF = (1LL<<60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; //#define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; int a[100010], dp[100010]; VI g[100010]; int dfs(int num) { if(dp[num] != -1) return dp[num]; // cout << num << endl; int ret = 0; for(int i: g[num]) { if(dfs(i) > 0) ret += dfs(i); } return dp[num] = ret + a[num]; } signed main(void) { int n; cin >> n; REP(i, n) { int x; cin >> x >> a[i+1]; g[x].PB(i+1); } memset(dp, -1, sizeof(dp)); dfs(0); int ret = -INF; FOR(i, 1, n+1) chmax(ret, dp[i]); cout << ret << endl; return 0; }
AOJ0616 JOI公園
問題ページ
JOI 公園 | Aizu Online Judge
考えたこと
とりあえず広場1から各広場への最短距離はdijkstraで求まる。Xを決めれば各辺について両端の頂点への最短距離がX以下かどうかでその辺が撤去される辺か判断できるのでO(M)でできる。max(X) = 10^10 でまず間に合わない。Xとして取りうる値は各頂点で最短距離となる値しかありえず、O(N)で抑えられる。しかしこれでもO(NM)で不可能。
各辺について両端の頂点の最短距離の大きい方をXに選択するときその辺を撤去する。したがって各Xの撤去すべき辺の合計をO(M)で前計算しておける。Xの候補のうち小さいものから考えていけば一度撤去した辺が撤去されないことはありえない。したがってO(N+M)で解ける。
//#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a<=x&&x<b) #define MP make_pair #define PB push_back const int INF = (1LL<<30); const ll LLINF = (1LL<<60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; #define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; struct edge{ll to, cost;}; int n, d[100010]; vector<edge> g[100010]; void dijkstra(int s) { priority_queue<PII, vector<PII>, greater<PII>> que; fill(d, d+n, INF); d[s] = 0; que.push(PII{0, s}); while(que.size()) { PII p = que.top(); que.pop(); int v = p.second; if(d[v] < p.first) continue; for(edge e: g[v]) { if(d[e.to] > d[v] + e.cost) { d[e.to] = d[v] + e.cost; que.push(PII{d[e.to], e.to}); } } } } int a[200010], b[200010], cost[200010]; map<ll, ll> mp; signed main(void) { int m, c; cin >> n >> m >> c; ll sum = 0; REP(i, m) { cin >> a[i] >> b[i] >> cost[i]; a[i]--, b[i]--; g[a[i]].PB({b[i], cost[i]}); g[b[i]].PB({a[i], cost[i]}); sum += cost[i]; } dijkstra(0); VL v; REP(i, n) v.PB(d[i]); sort(ALL(v)); v.erase(unique(ALL(v)), v.end()); //辺について見ていく REP(i, m) { ll tmp = max(d[a[i]], d[b[i]]); if(mp.find(tmp) == mp.end()) mp[tmp] = cost[i]; else mp[tmp] += cost[i]; } ll ret = LLINF; for(int i: v) { chmin(ret, c*i+(sum-mp[i])); sum -= mp[i]; } cout << ret << endl; return 0; }
AOJ0600 バームクーヘン
問題ページ
バームクーヘン | Aizu Online Judge
考えたこと
最小値の最大化と言われたので二分探索。判定がO(f(n))であればO(f(n)log(max(A_i)))となる。始点を一箇所固定すればmid以上の最小となるように2箇所切っていくのが最善になる。全パターン確認すれば判定できるのでO(n^2)かけていいなら簡単。判定をO(nlogn)とかO(n)でできれば解けそう。累積和を取っておいて二分探索すれば各始点についてO(logn)で判定できそうなので全体でO(nlognlog(max(A_i)))で解けそう。
累積和が明らかにintに収まる範囲内ではないのでlong longを使う。円環を扱うときは二周分とっておくと実装が楽。
//#define __USE_MINGW_ANSI_STDIO 0 #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> VI; typedef vector<VI> VVI; typedef vector<ll> VL; typedef vector<VL> VVL; typedef pair<int, int> PII; #define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i) #define REP(i, n) FOR(i, 0, n) #define ALL(x) x.begin(), x.end() #define IN(a, b, x) (a<=x&&x<b) #define MP make_pair #define PB push_back const int INF = (1LL<<30); const ll LLINF = (1LL<<60); const double PI = 3.14159265359; const double EPS = 1e-12; const int MOD = 1000000007; #define int ll template <typename T> T &chmin(T &a, const T &b) { return a = min(a, b); } template <typename T> T &chmax(T &a, const T &b) { return a = max(a, b); } int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; int n; ll a[200010], b[200010]; bool check(int m) { REP(i, n) { int tmp = i==0 ? 0 : b[i-1]; int itr1 = lower_bound(b+i, b+i+n, tmp+m) - b; int itr2 = lower_bound(b+i, b+i+n, b[itr1]+m) - b; if(b[n+i-1] - b[itr2] >= m) return true; } return false; } signed main(void) { cin >> n; REP(i, n) cin >> a[i]; FOR(i, n, 2*n) a[i] = a[i-n]; b[0] = a[0]; FOR(i, 1, 2*n) b[i] = a[i] + b[i-1]; // [low, high) ll low=1, high=b[n-1]; while(high-low>1) { ll mid = (low+high)/2; if(check(mid)) { low = mid; } else { high = mid; } } cout << low << endl; return 0; }