Skip to content

Instantly share code, notes, and snippets.

@snuke
Last active July 31, 2017 09:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snuke/1c76406f50ca39a3d0198bde197d5487 to your computer and use it in GitHub Desktop.
Save snuke/1c76406f50ca39a3d0198bde197d5487 to your computer and use it in GitHub Desktop.
IOI2017 Day1 Toy Train

B勝ちの頂点を求めていく。

まず、以下の条件を満たす最小のNG集合をBFSで求める。(次数記録しながらやるやつ)(NGというネーミングに特に意味はないです)

  • 充電頂点は全部NG
  • Aの頂点で、NG頂点に辺が生えてるものはNG
  • Bの頂点で、NG頂点にしか辺が生えていないものはNG

NG集合に含まれない頂点はB勝ちとなる。
というのも、NG集合に含まれない頂点集合内では、

  • 充電頂点はない
  • Aの頂点からはNGへの辺が生えていない
  • Bの頂点からはNG以外への辺が一本以上ある

を満たし、ここに到達したらBが適切にswitchを切り替えることでAが何をしようと充電頂点に行けないから。

次に、以下の頂点をB勝ちの頂点にする。

  • Aの頂点で、B勝ちの頂点にしか辺が生えていないもの
  • Bの頂点で、B勝ちの頂点に辺が生えているもの

これらがB勝ちなのは明らか。
こちらもBFSで求められる。

そして、B勝ちの頂点を削除して「まず〜」のステップに戻る。
B勝ちの頂点を削除しても残りの勝敗が変わらない理由は以下の通り。

  • Bの頂点→B勝ち頂点の辺:存在するなら「次に〜」のステップですでにB勝ち判定されているはず
  • Aの頂点→B勝ち頂点の辺:Aは絶対にこの辺を選択しないため(ちなみに選択できる辺がなくなった場合は「次に〜」のステップでB勝ち判定されるはず)

この2つのステップを更新がなくなるまで繰り返していくと、残った頂点がA勝ちになる。
証明:
この時点で残った頂点でNG集合のBFSをやって、BFS木みたいなのを作る。
Aの非充電頂点はこのBFS木の通りに遷移先を固定する。
すると、Bがどう遷移しても、どの頂点からも高々Nステップで充電頂点に到達できる。
充電頂点の遷移は、

  • A:一本は必ず辺が残っているはずなのでそれを辿る
  • B:元々の辺はどれも残ってる頂点への辺なので、どれを辿ろうとまた充電頂点に帰ってくる

ステップ数は高々Nで計算量はO(N(N+M))。
(ステップ数の最大ってどれくらいのケースが作れるかな。N/小さい定数な気はする。)

※「次に〜」のステップで充電なしAの頂点の自己ループ辺をあらかじめ削除しておくことを忘れずに。

解法のモチベーションとしては、

  • まずは毎回遷移先を選べるという設定の問題を解いてみれば、その設定でも勝敗が変わらないことが証明できるんちゃう?

    • なんらかの静的な性質がないと多項式にならなさそう
  • 各頂点について勝敗を決めた時に、それらが満たしているべき条件を考える

    • 勝ち頂点内にAがどう頑張っても充電がないサイクルがある感じだとだめとかそんなの
#include <bits/stdc++.h>
#include "train.h"
#define fi first
#define se second
#define rep(i,n) for(int i = 0; i < (n); ++i)
#define rrep(i,n) for(int i = 1; i <= (n); ++i)
#define drep(i,n) for(int i = (n)-1; i >= 0; --i)
#define each(it,c) for(__typeof((c).begin()) it=(c).begin();it!=(c).end();it++)
#define rng(a) a.begin(),a.end()
#define maxs(x,y) x = max(x,y)
#define mins(x,y) x = min(x,y)
#define pb push_back
#define sz(x) (int)(x).size()
#define pcnt __builtin_popcount
#define uni(x) x.erase(unique(rng(x)),x.end())
#define snuke srand((unsigned)clock()+(unsigned)time(NULL));
#define df(x) int x = in()
#define dame { puts("-1"); return 0;}
#define show(x) cout<<#x<<" = "<<x<<endl;
#define PQ(T) priority_queue<T,vector<T>,greater<T> >
#define bn(x) ((1<<x)-1)
#define newline puts("")
#define v(T) vector<T>
#define vv(T) vector<vector<T>>
using namespace std;
typedef long long int ll;
typedef unsigned uint;
typedef unsigned long long ull;
typedef pair<int,int> P;
typedef vector<int> vi;
typedef vector<vi> vvi;
typedef vector<ll> vl;
typedef vector<P> vp;
inline int in() { int x; scanf("%d",&x); return x;}
inline void priv(vi a) { rep(i,sz(a)) printf("%d%c",a[i],i==sz(a)-1?'\n':' ');}
template<typename T>istream& operator>>(istream&i,vector<T>&v)
{rep(j,sz(v))i>>v[j];return i;}
template<typename T>string join(const vector<T>&v)
{stringstream s;rep(i,sz(v))s<<' '<<v[i];return s.str().substr(1);}
template<typename T>ostream& operator<<(ostream&o,const vector<T>&v)
{if(sz(v))o<<join(v);return o;}
template<typename T1,typename T2>istream& operator>>(istream&i,pair<T1,T2>&v)
{return i>>v.fi>>v.se;}
template<typename T1,typename T2>ostream& operator<<(ostream&o,const pair<T1,T2>&v)
{return o<<v.fi<<","<<v.se;}
const int MX = 100005, INF = 1001001001;
const ll LINF = 1e18;
const double eps = 1e-10;
vi who_wins(vi a, vi r, vi eu, vi ev) {
int n = sz(a);
vvi to(n), ot(n);
rep(i,sz(eu)) to[eu[i]].pb(ev[i]);
rep(i,sz(eu)) ot[ev[i]].pb(eu[i]);
vi selfa(n);
rep(i,n) if (a[i] && !r[i]) {
for (int j : to[i]) if (j == i) selfa[i]++;
}
vi ans(n,1);
while (1) {
vi ng(n);
{
queue<int> q;
auto psh = [&](int v) {
if (ng[v]) return;
ng[v] = 1; q.push(v);
};
rep(i,n) if (r[i] && ans[i]) psh(i);
vi deg(n);
rep(i,n) deg[i] = sz(to[i]);
while (sz(q)) {
int v = q.front(); q.pop();
for (int u : ot[v]) {
if (a[u]) {
psh(u);
} else {
deg[u]--;
if (!deg[u]) psh(u);
}
}
}
}
bool upd = false;
rep(i,n) {
if (ans[i] && !ng[i]) {
upd = true;
ans[i] = 0;
}
}
if (!upd) break;
{
vi used(n);
queue<int> q;
auto psh = [&](int v) {
if (used[v]) return;
used[v] = 1;
ans[v] = 0;
q.push(v);
};
rep(i,n) if (!ans[i]) psh(i);
vi deg(n);
rep(i,n) deg[i] = sz(to[i])-selfa[i];
while (sz(q)) {
int v = q.front(); q.pop();
for (int u : ot[v]) {
if (!a[u]) {
psh(u);
} else {
deg[u]--;
if (!deg[u]) psh(u);
}
}
}
}
}
return ans;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment