読者です 読者をやめる 読者になる 読者になる

土下座しながら探索中

主に競技プログラミング

Graphviz でノード名にプライムをつけたかった

Graphvizでノード名にプライムをつけようとしたらエラーが出た

ググっても解決策がでてこなかったのでメモ

 

解決策 : ノード名を"で囲む

 

ダメな例 :

digraph g {

node0' [color="#ffffff", fontcolor="#ffffff"];
node1', node2->;

}

 

良い例 :

digraph g {

"node0'" [color="#ffffff", fontcolor="#ffffff"];
"node1'", "node2->";

}

SRM Div1 easy : BiconnectedDiv1

問題概要 :

n個のノードが存在し、それらは0からn-1まで番号が割り振られている

ノードiはi+1がn-1以下であればノードi+1に向けて重みw1[i]の辺を持つ

また、ノードiはi+2がn-1以下であればノードi+2に向けて重みw2[i]の辺を持つ

各辺は無向辺である

このようにして構成されるグラフから何本か辺を削除する

ただし、辺を削除した後はそのグラフの任意の異なる2頂点u,v間にパスが存在する必要がある

(つまり、uからvに残っている辺をたどって移動できる必要がある)

上の制約を満たした上で0本以上辺を削除したとき、残った辺の重みの総和の最小値を求めよ



解法 :

動的計画法で解いた

まず、辺を消した後に条件を満たすようなグラフがどのようなものなのかを考える
これは全てのノードが1つの閉路に含まれるグラフである
このようなグラフであれば、どこを消しても少なくとも1本はパスが存在する
全てのノードが1つの閉路に属していない場合、閉路に属していないノードから出ている辺を削除すると連結でなくなる

グラフ全体が1つの閉路になるための条件は各ノードの次数が2以上で連結であることである
(グラフが閉路かどうかの判定は各ノードの次数が偶数で連結であること 今回は閉路+αがあっても良いので2以上にした)
あとは各ノードの次数が2未満にならないように辺を削除するかしないかでDPをする

コード :

#include<bits/stdc++.h>

#define REP(i,s,n) for(int i=s;i<n;i++)
#define rep(i,n) REP(i,0,n)

using namespace std;

typedef long long ll;
const int IINF = INT_MAX;

struct Edge {
  int dst,w;
};

#define MAX 300
vector<Edge> G[MAX];
int dp[MAX];

class BiconnectedDiv1 {
public:
  int V;
  int minimize( vector <int> w1, vector <int> w2 ) {
    V = (int)w1.size() + 1;
    rep(i,MAX) G[i].clear();
    memset(dp,0,sizeof dp);
    int sum = 0;
    rep(i,(int)w1.size()) {
      G[i].push_back((Edge){i+1,w1[i]});
      G[i+1].push_back((Edge){i,w1[i]});
      sum += w1[i];
    }
    rep(i,(int)w2.size()) {
      G[i].push_back((Edge){i+2,w2[i]});
      G[i+2].push_back((Edge){i,w2[i]});
      sum += w2[i];
    }
    int maxi = 0;
    REP(v,1,V-1) {
      dp[v] = max(dp[v],dp[v-1]);
      if( (int)G[v].size() >= 3 ) {
        rep(i,(int)G[v].size()) {
          Edge &e = G[v][i];
          if( v > e.dst ) continue;
          if( (int)G[e.dst].size() < 3 ) continue;
          dp[e.dst] = max(dp[e.dst],dp[v]+e.w);
          maxi = max(maxi,dp[e.dst]);
        }
      }
    }
    return sum - maxi;
  }
};

ACM-ICPC 2016 国内予選 参加記

最後の国内予選なので参加記を残そうかと
チームは私と後輩Sと後輩Tです。(イニシャルだけだと去年と同じだけど後輩Tは去年とは別の人
[追記]後輩Tではなく後輩Mでした、後輩Mさんごめんなさい 

リハーサル

後輩Sは2,3限に授業があるため不参加
後輩Mとともに会場準備とプリンタの設定を行う
お菓子とMAXコーヒーとレッドブルとT島屋で買った100g1670円のブルーマウンテンを挽いていれたコーヒーを飲む
自己紹介を書いたりする
TwitterのTLを眺めながら後輩らの精神状況が不安定であることを確認する
コンテスト中のチームの動きを確認する
コンテスト開始を待つ

コンテスト

A問題

ソートするだけっぽい
絶対AでWAを出したくないので後輩Sに「本当にそれだけ?」とn回確認した
「条件どうこういってたけど、それはなんだったの?」「本当にそれだけ??」「えっ」
コードを書く、サンプルは通る
まぁ投げてみる => AC
良い感じ
1完

B問題

後輩Mから概要を聞く
全探索で良いみたい
書く=>サンプルが合わない
バグとりを二人でする
入力に現れる立候補者の他に1名立候補者を入れる必要があることが発覚
入れる、それに合わせてループを修正
サンプルは通る
まぁ投げてみる => AC
ちょっとつまったけど良い感じ
2完

C問題

後輩Sから概要を聞く
「エラトステネスの篩です、以上」
この説明から概要がつかめなかったで問題文に目を通す
なるほど、エラトステネスの篩っぽいものを書けば良いようだ
書く=>無限ループ
悲しい気持ちになりつつもデバッグ
無限ループは止まった
でもサンプルは合わない
後輩Sと色々話しながらバグを探す、修正
サンプルは通る
まぁ投げてみる => AC
プログラムを実行してからアウトプットが終わるまで2、3分かかっていて辛い感じだったけどまぁ通ったし良い感じ
3完

D問題

概要を聞く前に解けそうか聞いてみる
二人から分からないと返事が返ってきて悲しい気持ちになる
概要を聞く
「あれ、超見たことあるこれ絶対区間DPだゾ(適当」

という発言したはいいもののはっきりとした方針が浮かんでこない
とりあえず後ろの問題に一通り目を通す
Fぱっとみ簡単そう、でもさすがにDのほうが楽そう
Dをやることにする

冷静に考えて、bool memo[L][R]みたいな感じで全て消すことのできる区間を列挙してあとは
dp[i]:=iまでで作れる最大値 みたいなDPすれば良い

書く=>めっちゃバグる=>辛い気持ちになりつつ後輩Mと共にデバッグをする
暗黒時代の幕開け
後輩Mが正しい方針を示すもそれとは全く違う実装をして時間を無駄にする
頑張る
暗黒時代が終わる
サンプルは通る
まぁ投げてみる => AC
スコアボードを確認するとまぁ良いでしょみたいな順位にいたので良い感じ
4完

とりあえず残りの問題のどれに挑戦するか話し合う
E ... 一番ダメそう
F ... 一番いけそう
G ... 自分には無理そう、後輩Sは挑戦したいらしい
H ... 二番目にいけそう

というわけで自分はFを書き、
後輩SはGを考え、
後輩MはHを考えることに

F問題

内包関係をグラフにするとそれは木になるし、だったらあとは木の同型判定でしょ
まだ1時間以上あるしいける木がする
実装開始と共に暗黒時代が幕を開ける
残り1時間 => まだまだ書くことがある
残り30分 => まだまだまだ書くことがある
残り10分 => まだまだまだまだ書くことがある、なんだこれは

コンテストが終わった
実装力が足りていない(もしくはコーダーが足りていない

感想

アジアに向けてコーダーを育てようと決意

SRM 692 Div1 easy : HardProof

問題概要 :

任意の2点間に辺があるような重み付き有向グラフが隣接行列Dとして与えられる

このグラフはn個の頂点から成り、各頂点には0から順にn-1まで番号が割り振られている

全ての頂点を少なくとも1回以上訪れるような閉路について考えたとき、

この閉路に含まれる辺の重みの最大値と最小値の差の最小値を求めよ

解法 :

強連結成分分解 + 二分探索で解いた

まず、閉路中に存在する辺の重みの最大値と最小値を決める

ただし愚直に最大値と最小値を決めてしまうと全ての辺の重みが異なる場合にTLEする

( 最大値、最小値の取りうる数はそれぞれ2500なので総当たりすると2500 * 2500, その後の処理に2500かかるので 2500^3 で TLE する その後の処理をlogくらいに抑えれればTLEしないけど)

そこで、最小値だけを総当たりで決める

最小値が決まったとき、最小値以上の最大値の最小値は二分探索により求めることができる

このとき、与えられたグラフの中から辺の重みが最小値未満もしくは最大値より大きいものを除いたグラフについて考える

このグラフ中に、全ての頂点を少なくとも1回以上訪れるような閉路が存在すれば良い

ここでは既に余計な辺を削除してあるので、このグラフ中に平路が存在するかどうかを判定すれば良い

これはグラフ中に橋が存在するかどうかで判断できる

( 橋とは? 参考 : http://hos.ac/slides/20110504_graph.pdf

橋 : グラフ中に存在する辺のうち、それを取るとグラフの連結成分数が増えるようなもののこと)

今回グラフは有向グラフなので、強連結成分の数が1つかどうかで判断できる

強連結成分の数が2以上なら、異なる2つの強連結成分同士を結ぶような辺が橋になるからである

V,Eをそれぞれグラフの頂点集合、辺集合とすると計算量は
閉路中の辺の重みの最小値を総当たりするので |V| = 2500
二分探索で最大値を決定するので log2(|V|) = log2(2500) ≒11.287712379549449
強連結成分分解 |V| + |E| = 50 + 2500
O( |V| * log(|V|) * (|V|+|E|) )
最悪ケースでは 2500 * log2(2500) * 2550 ≒ 70548202.37218405となる
10^8以下なので十分1秒以内に終わる

コード :

#include<bits/stdc++.h>

#define REP(i,s,n) for(int i=s;i<n;i++)
#define rep(i,n) REP(i,0,n)
#define EPS 1e-10
#define equals(a,b) (fabs((a)-(b)) < EPS)

using namespace std;

const int IINF = INT_MAX;

int V;
int mat[51][51];
bool visited[51];

class HardProof {
public:

  int lower, upper;

  vector<vector<int> > G,rG;
  vector<bool> used;
  vector<int> cmp,vs;

  void add_edge(int s,int t){
    G[s].push_back(t);
    rG[t].push_back(s);
  }

  void dfs(int v){
    used[v] = true;
    for(int i=0;i<(int)G[v].size();i++) if( !used[G[v][i]] ) dfs(G[v][i]);
    vs.push_back(v);
  }

  void rdfs(int v,int k){
    used[v] = true;
    cmp[v] = k;
    for(int i=0;i<(int)rG[v].size();i++) if( !used[rG[v][i]] ) rdfs(rG[v][i],k);
  }

  int scc(){
    rep(i,V) used[i] = false;
    vs.clear();
    rep(v,V) if( !used[v] ) dfs(v);
    rep(i,V) used[i] = false;
    int k = 0;
    for(int i=vs.size()-1;i>=0;i--) if( !used[vs[i]] ) rdfs(vs[i],k++);
    return k;
  }

  bool check() {
    if( lower > upper ) return false;
    G.clear(), rG.clear(), cmp.clear(), used.clear();
    G.resize(V,vector<int>()), rG.resize(V,vector<int>()),cmp.resize(V), used.resize(V);
    rep(i,V) {
      rep(j,V) if( i != j && ( lower <= mat[i][j] && mat[i][j] <= upper ) ) {
	add_edge(i,j);
      }
    }
    return scc() == 1;
  }

  int minimumCost(vector <int> D) {
    V = sqrt((int)D.size());
    if( V == 1 ) return 0;
    rep(i,V) rep(j,V) mat[i][j] = D[i*V+j];
    vector<int> lowers;
    rep(i,V) rep(j,V) lowers.push_back(mat[i][j]);
    sort(lowers.begin(),lowers.end());
    lowers.erase(unique(lowers.begin(),lowers.end()),lowers.end());
    int mini = IINF;
    rep(i,(int)lowers.size()) {
      lower = lowers[i];
      int L = 0, R = ((int)lowers.size());
      while( L + 1 < R ) {
	int M = ( L + R ) / 2;
	upper = lowers[M];
	if( check() ) {
	  R = M;
	  mini = min(mini,upper-lower);
	} else {
	  L = M;
	}
      }
    }
    return mini;
  }
};

SRM 691 Div1 easy : Sunnygraphs

問題概要 :
n個の頂点とn本の無向辺があり、頂点は0からn-1まで番号が割り振られている
また、辺は頂点iから頂点a[i]に向けて貼られている ( ただし、i != a[i] )

このグラフに対して次の処理を行う
1. 新たな頂点 n を追加する
2. 0からn-1までの頂点集合から任意の部分集合Mを選ぶ
3. Mに属する頂点 i について、辺(i,a[i])をグラフから取り除く
4. Mに属する頂点 i について、辺(i,n)をグラフに追加する

この処理を行ったあと、0から1へのパスが存在するようなMの選び方は何通り存在するか?

解法 :
グラフの取りうる状態について考える
グラフの状態は初期状態で0と1の間にパスが存在するかしないかで分けられるため、それぞれについて考えていく
今後、説明上どの頂点からどの頂点に向けて辺が貼られているのかを明らかにするためグラフの辺は有向辺として書くが実際は無向辺であるので注意すること

まず、0と1の間にパスが存在しない場合のグラフは次のようになる
f:id:tei3344:20160531024608j:plain
このグラフは0から到達可能な頂点集合(上の図では頂点0,2)と1から到達可能な頂点集合(上の図では頂点1,3)、それ以外の頂点集合(上の図では頂点4,5)から成る
それ以外の頂点集合は空集合でも良い
0と1の間にパスをつくるためには、
0から到達可能な頂点集合から少なくとも1つ、
1から到達可能な頂点集合から少なくとも1つの頂点をMとして選ぶ必要がある
f:id:tei3344:20160531025416p:plain
0と1から到達可能な頂点集合からそれぞれ少なくとも1つの頂点をMとして選んだのであれば、
それ以外の頂点集合はMに追加してもしなくてもどちらでもよい
これより、0と1の間にパスが存在しない場合の答えは (2^(0から到達可能な頂点集合の大きさ)-1) * (2^(1から到達可能な頂点集合の大きさ)-1) * (2^(それ以外の頂点集合の大きさ))で求めることができる
答えの式中にある-1は1つも頂点を選ばない場合を取り除くためのものである

次に、0と1の間にパスが存在する場合のグラフについて考える
このときグラフは次のようになる
f:id:tei3344:20160531031045p:plain
このグラフの頂点集合は次のように分類することができる
0と1のそれぞれから探索を開始したとき、

\* 0からのみ到達可能な頂点集合 ( 上の図では頂点0,2 )
\* 1からのみ到達可能な頂点集合 ( 上の図では頂点1,4)
\* 両方から到達可能な頂点集合 ( 上の図では頂点3,5,6)
\* 両方から到達不可能な頂点集合( 上の図では頂点7,8 )
このグラフはすでに0と1の間にパスが存在するため、そのパスを壊さないような頂点Mの選び方が答えとなる

パスを壊さないような頂点の選び方は次の5通りである
\* 0からのみ到達可能な頂点集合と1からのみ到達可能な頂点集合から少なくとも1つは頂点をMとして選ぶ
f:id:tei3344:20160531031733p:plain
このとき、両方から到達可能、到達不可能な頂点集合からは頂点をMとして選んでも選ばなくても良い
そのため、このときのMの選び方は(2^(0から到達可能な頂点集合の大きさ)-1) * (2^(1から到達可能な頂点集合の大きさ)-1) * (2^(両方から到達可能な頂点集合の大きさ)) * (2^(両方から到達不可能な頂点集合の大きさ))となる


\* 0からのみ到達可能な頂点集合から少なくとも1つは頂点をMとして選ぶ, 1からのみ到達可能な頂点集合からは1つも選ばない
f:id:tei3344:20160531032412j:plain
これだけでは0と1の間にパスができないため、両方から到達可能な頂点集合から少なくとも1つはMとして選ぶ必要がある
そのため、このときのMの選び方は(2^(0から到達可能な頂点集合の大きさ)-1) * (2^(両方から到達可能な頂点集合の大きさ)-1) * (2^(両方から到達不可能な頂点集合の大きさ))となる

\* 1からのみ到達可能な頂点集合から少なくとも1つは頂点をMとして選ぶ, 0からのみ到達可能な頂点集合からは1つも選ばない
f:id:tei3344:20160531032728j:plain
これだけでは0と1の間にパスができないため、両方から到達可能な頂点集合から少なくとも1つはMとして選ぶ必要がある
そのため、このときのMの選び方は(2^(1から到達可能な頂点集合の大きさ)-1) * (2^(両方から到達可能な頂点集合の大きさ)-1) * (2^(両方から到達不可能な頂点集合の大きさ))となる

\* 0からのみ到達可能な頂点集合と1からのみ到達可能な頂点集合からは頂点を1つも選ばない、両方から到達可能な頂点集合から少なくとも1つはMとして選ぶ
f:id:tei3344:20160531032923j:plain
0からのみ到達可能な頂点集合と1からのみ到達可能な頂点集合からは1つも頂点をMとして選んでいないため、0と1の間にはパスが残ったままである
そのため、このときのMの選び方は(2^(両方から到達可能な頂点集合の大きさ)-1) * (2^(両方から到達不可能な頂点集合の大きさ))となる

\* 両方から到達不可能な頂点集合からのみ頂点をMとして選ぶ
f:id:tei3344:20160531033302j:plain
このときのMの選び方は(2^(両方から到達不可能な頂点集合の大きさ))となる

これら5通りの選び方の総和が答えとなる

コード:

#include<bits/stdc++.h>

#define REP(i,s,n) for(int i=s;i<n;i++)
#define rep(i,n) REP(i,0,n)
#define EPS 1e-10
#define equals(a,b) (fabs((a)-(b)) < EPS)

using namespace std;

const int IINF = INT_MAX;
typedef long long ll;

vector<int> G[100],checkG[100];

typedef pair<int,int> ii;

inline void fix(ii &v) { if(v.first > v.second) swap(v.first,v.second); }

void dfs(int cur,map<ii,ll> &mp) {
  rep(i,(int)G[cur].size()) {
    ii edge = ii(cur,G[cur][i]);
    if( mp.count(edge) ) continue;
    ++mp[edge];
    dfs(G[cur][i],mp);
  }
}

bool used[100];
bool checkDFS(int cur) {
  if( cur == 1 ) return true;
  bool res = false;
  rep(i,(int)checkG[cur].size()) {
    int &next = checkG[cur][i];
    if( used[next] ) continue;
    used[next] = true;
    res |= checkDFS(next);
  }
  return res;
}

class Sunnygraphs {
public:
  long long count(vector <int> a) {
    rep(i,(int)a.size()) {
      G[i].push_back(a[i]);
      checkG[i].push_back(a[i]);
      checkG[a[i]].push_back(i);
    }
    map<ii,ll> counter0,counter1;
    dfs(0,counter0);    
    dfs(1,counter1);
    long long zero = (long long)counter0.size();
    long long one  = (long long)counter1.size();
    long long otherwise = (int)a.size();
    if( !checkDFS(0) ) {
      otherwise -= ( zero + one );
      return ((1LL<<zero)-1LL) * ((1LL<<one)-1LL) * (1LL<<otherwise);
    }
    long long common = 0, answer = 0;
    for(map<ii,ll>::iterator it=counter0.begin();it!=counter0.end();it++) {
      ii v = it->first;
      if( counter1.count(v) ) {
	++common;
      }
    }
    zero -= common, one -= common;
    otherwise -= ( zero + one + common);
    if( zero && one ) {
      answer += ( ((1LL<<zero)-1LL) * ((1LL<<one)-1LL) * (1LL<<common) * (1LL<<otherwise) );
    }
    if( zero ) {
      answer += ( ((1LL<<common)-1LL) * ((1LL<<(zero))-1LL) * (1LL<<otherwise) );
    }
    if( one ) {
      answer += ( ((1LL<<common)-1LL) * ((1LL<<(one))-1LL) * (1LL<<otherwise) );
    }
    if( common ) {
      answer += ((1LL<<common)-1LL) * (1LL<<otherwise);
    }
    answer += (1LL<<otherwise);
    return answer;

  }
};

SRM 688 Div1 easy : ParenthesesDiv1Easy

問題概要 :
'('と')'からなる文字列sが与えられる
この文字列に対して次の処理(1,2を連続で)を行うことができる
1. 2つの整数l,r (l<=r)について、 s[l]からs[r]までを反転させる
例 s = "((()", l = 1, r = 3 => s = "((("
2. s[l]からs[r]までの各文字に対して、'('なら')'に、')'なら'('に変更する
例 s = "((()", l = 1, r = 3 => s = "(())"

このとき、文字列sを正しい括弧の対応付けにするまでに必要な処理の内容を出力せよ
ただし、処理は高々10回までとする
正しい括弧の対応付けにできない場合は-1を返すこと
※正しい括弧の例 (), (()), ((()))()(()())

解法 :
処理にどのような特徴があるのか考える
この処理は指定した範囲の部分文字列を単純に反転した後にその中の括弧も反転させる、というもの
そのため、指定した範囲内に存在する正しい括弧は処理を行った後も正しい括弧のままである
例えば、s = "()(", l = 0, r = 3 の処理結果は s = ")()" となる
今後処理をする際に正しい括弧を破壊する意味はないため、正しい括弧は取り除いて考えることができる
例えば、s = "()()()))((((())" は s = ")(((" として考えることができる
正しい括弧を全て取り除いて考えたとき、sのとりうる状態は以下の3通りしか存在しない

1. 全て '('
2. 全て ')'
3. ')' が1つ以上続いた後に '(' が1つ以上続く 例 s = "))))((("

これ以外だと正しい括弧が残っていることになる

結局この3通りだけしかないのであれば、

1. の場合は取り除かれなかった'('の後半分を')'に変換すれば良い
2. の場合は取り除かれなかった')'の前半分を'('に変換すれば良い
3. の場合は取り除かれなかった'('を全て')'に変換 or ')' を全て '(' に変換すると1.か2.の状態になるため上の通りに処理すれば良い

ということになる

     0123456789012345
s = "()()())()((((())"

まず、正しい括弧を取り除く

s = "()()())()( ( (( ())"

     6901
s = ")((("

次に、s を ')' か '(' のどちからのみからなるように変換する
どちらでも良いので今回は ')' だけからなるように変換する

s = ")(((", l = 9, r = 11

s = "))))"

最後に、')'の前半分を'('に変換して正しい括弧の対応付けにする

s = "))))"

s = "(())"

コード :

#define REP(i,s,n) for(int i=s;i<n;i++)
#define rep(i,n) REP(i,0,n)

using namespace std;

typedef long long ll;
typedef pair<char,int> ci;
const int IINF = INT_MAX;


class ParenthesesDiv1Easy {
public:

  deque<ci> calcDeq(string s) {
    deque<ci> deq;
    rep(i,(int)s.size()) {
      if( s[i] == '(' ) {
        deq.push_back(ci('(',i));
      } else {
        if( (int)deq.size() && deq.back().first == '(' ) deq.pop_back();
        else deq.push_back(ci(')',i));
      }
    }
    return deq;
  }

  string flip(string s,int l,int r) {
    reverse(s.begin()+l,s.begin()+r+1);
    for(int i=l;i<=r;++i) {
      if( s[i] == '(' ) s[i] = ')';
      else              s[i] = '(';
    }
    return s;
  }

  vector <int> correct( string s ) {
    int n = s.size();
    if( n & 1 ) return vector<int>(1,-1);
    deque<ci> deq = calcDeq(s);
    // ()()())()((((()) 
    bool hasL = false, hasR = false;
    int sp = IINF;
    rep(i,(int)deq.size()) {
      if( deq[i].first == '(' ) hasL = true, sp = min(sp,i);
      else                      hasR = true;
    }

    vector<int> ret;
    int len = deq.size();
    if( hasL && hasR ){
      ret.push_back(deq[sp].second);
      ret.push_back(n-1);
      s = flip(s,deq[sp].second,n-1);
      deq = calcDeq(s);
      hasL = false;
    }
    
    if( hasL ) {
      ret.push_back(deq[len/2].second);
      ret.push_back(n-1);
    } else if( hasR ) {
      ret.push_back(0);
      ret.push_back(deq[(int)(len/2)-1].second);
    }
    return ret;
  }
};

SRM 682 Div1 easy : SmilesTheFriendshipUnicorn

問題概要 :
N人の人がいて、それらの人は0からN-1まで順に番号が割り振られている
これらの人の間には友好関係があり、これは2つの整数で表される
友好関係は2つの配列A,Bとして表されA[i]番の人とB[i]番の人は友達である
A[i] != B[i]
これらの友好関係は無向グラフとして表すことができる
このとき、任意の2頂点間には必ずパスが存在する
このグラフ内に長さが4のパスが存在するか、YESかNOで答えよ
つまり、5つの異なる頂点a,b,c,d,eについて、
aとb,bとc,cとd,dとeに辺が存在する

解法 :
無向グラフ内から下の図のような部分が存在するかを判定したい
f:id:tei3344:20160223133648p:plain
このとき、
f:id:tei3344:20160223133744p:plain
すぐ上の図の赤い枠で囲まれた辺をループで愚直に決めてしまう
赤い枠で囲まれた辺は2本しかないので、2000 * 2000 で4つの頂点からなる長さ3のパスを求めることができる
残り1つの頂点は長さ3のパスの両端点について、次数からO(1)で計算できる

まず2つの辺から長さ3のパスを計算する
f:id:tei3344:20160223134116p:plain
以下の4通りのパスが存在する
f:id:tei3344:20160223134203j:plain
f:id:tei3344:20160223134217j:plain
f:id:tei3344:20160223134233j:plain
f:id:tei3344:20160223134252j:plain

この各4通りのパスについて、端点に1つ頂点が追加できれば求めたいパスになる
これは次数からO(1)で計算できる
先ほどの1つめの例で考えると、
f:id:tei3344:20160223134506j:plain
2番か4番の頂点から5番に辺があれば良い
2番の頂点に注目したとき、5番の頂点というのは1,3,4番の頂点でなければどれでもよい
つまり、2番の頂点の次数をdegとしたとき、
2-1に辺は必ず存在するので --deg
2-3に辺が存在するなら --deg
2-4に辺が存在するなら --deg
この結果が1以上であれば、1でも3でも4でもない頂点と2は繋がっている
これを5とすればよい

コード :

#include<bits/stdc++.h>

#define REP(i,s,n) for(int i=s;i<n;i++)
#define rep(i,n) REP(i,0,n)

using namespace std;

typedef long long ll;
const int IINF = INT_MAX;
const string kzy = "Yay!";
const string KT  = ":(";

struct Edge {
  int s,t;
  bool operator < (const Edge& e) const {
    if( s != e.s ) return s < e.s;
    return t < e.t;
  }
};

bool rel[2010][2010];
vector<int> G[2010];

class SmilesTheFriendshipUnicorn {
public:

  bool check(Edge e1,Edge e2,bool sp1,bool sp2) {
    int p1 = -1, p2 = -1;
    int rp1 = -1, rp2 = -1;
    if( sp1 ) p1 = e1.t, rp1 = e1.s;
    else      p1 = e1.s, rp1 = e1.t;
    if( sp2 ) p2 = e2.t, rp2 = e2.s;
    else      p2 = e2.s, rp2 = e2.t;
    if( !rel[p1][p2] ) return false;
    
    int deg1 = (int)G[rp1].size();
    int deg2 = (int)G[rp2].size();
    
    //
    --deg1;
    if( rel[rp1][p2] ) --deg1;
    if( rel[rp1][rp2] ) --deg1;
    if( deg1 >= 1 ) return true;
    
    //
    --deg2;
    if( rel[rp2][p1] ) --deg2;
    if( rel[rp2][rp1] ) --deg2;
    if( deg2 >= 1 ) return true;
    
    return false;
  }

   string hasFriendshipChain( int N, vector <int> A, vector <int> B ) {
     N = N;
     vector<Edge> vec;
     rep(i,(int)A.size()) {
       vec.push_back((Edge){A[i],B[i]});
       G[A[i]].push_back(B[i]);
       G[B[i]].push_back(A[i]);
       rel[A[i]][B[i]] = rel[B[i]][A[i]] = true;
     }
     rep(i,(int)vec.size()) {
       Edge &e1 = vec[i];
       rep(j,(int)vec.size()) {
         Edge &e2 = vec[j];
         if( e1.s == e2.s || e1.s == e2.t ) continue;
         if( e1.t == e2.s || e1.t == e2.t ) continue;
         if( check(e1,e2,0,0) ) return kzy;
         if( check(e1,e2,0,1) ) return kzy;
         if( check(e1,e2,1,0) ) return kzy;
         if( check(e1,e2,1,1) ) return kzy;
       }
     }
     
     return KT;
   }
};