TLE が厳しくて話題になってた
問題概要
頂点数 、辺数 の単純無向グラフ [tex G] が与えられる。また、正の整数 が与えられる。
以下のいずれかの条件を満たすものが存在するかどうかを判定し、存在するならばそれを出力せよ。
- type 1: の部分グラフであって、それに含まれるどの頂点についても、次数が 以上である
- type 2: の部分グラフであって、サイズ のクリークである (頂点数が でどの頂点も次数が であるもの)
制約
考えたこと
まず type 1 に関しては、「サイクル検出をするのに queue を使って葉がある限り除去していく」と、同じ方法が使える! つまり、
- 次数 以下の頂点がある限りそれを削除していく
- 削除することで新たに次数 の頂点が生じる場合も、改めてそれを削除していく
という処理を行なっていく。もし何も残らなければ、type 1 の条件を満たすものは存在しない。残ったならば、それは type1 の条件を満たすものとなる。
サイズ のクリーク
サイズ のクリークとは、どの頂点の次数も であるような頂点数 の部分グラフである。
よって、先ほどの手続きを修正して、次のように探索できる。
- 次数 以下の頂点がある限りそれを削除していく (その過程で新たに次数 以下の頂点が生じる場合も、改めてそれを削除していく)
- それによって次数 以上の頂点集合が残る
- 次数がちょうど であるような頂点 については、それがクリークをなしうるかどうかを次のように判定できる
- 頂点 とそれに隣接する頂点からなるサイズ の頂点集合がクリークをなすかどうかを判定する
- クリークをなさないならば、頂点 は type 1, type 2 のいずれのメンバーにもなり得ないので削除してよい
- この手続きによってクリークが発見されずに、次数 の頂点が消滅したとしても、残る頂点はすべて次数が 以上となるので、type 1 を満たす
以上の手続きの計算量を評価する
- 次数 の頂点が発掘されてクリークかどうかを確かめる回数は となる
- 1 回削除されるごとに辺数が 減少するため
- 1 回のクリーク判定は 程度の計算量を要する
である場合はクリークが存在しえないことに注意すると、計算量は 程度といえる。
コード
#include <bits/stdc++.h> using namespace std; template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; } template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; } /* K-1 未満はガンガン削除していく K-1 が生えたら、それと K-1 個の頂点でできる K 頂点部分グラフを判定 ダメならその K-1 頂点も不要なので削除 もしクリークがあれば、この過程で見つかる クリークがなけれ操作終了時には、どの頂点も次数 K 以上の部分グラフが残るはず */ using Graph = vector<vector<int>>; bool solve(int N, int M, int K, Graph &G) { if ((long long)(K) * (K-1) / 2 > M) return false; for (int v = 0; v < N; ++v) sort(G[v].begin(), G[v].end()); vector<int> deg(N, 0); vector<bool> removed(N, 0); // 1: K, 2: クリーク auto print = [&](int type, const vector<int> &res) -> void { if (type == 1) printf("%d %d\n", type, (int)res.size()); else printf("%d\n", type); for (int i = 0; i < res.size(); ++i) { if (i) printf(" "); printf("%d", res[i]+1); } printf("\n"); }; auto isclique = [&](int v) -> bool { vector<int> res({v}); for (auto u : G[v]) if (!removed[u]) res.push_back(u); if (res.size() != K) return false; for (int i = 0; i < res.size(); ++i) { for (int j = i+1; j < res.size(); ++j) { int s = res[i], t = res[j]; int it = lower_bound(G[s].begin(), G[s].end(), t) - G[s].begin(); if (it == G[s].size() || G[s][it] != t) return false; } } print(2, res); return true; }; queue<int> que; vector<bool> seen(N, false); for (int v = 0; v < N; ++v) { deg[v] = (int)G[v].size(); if (deg[v] <= K-1) que.push(v), seen[v] = true; } while (!que.empty()) { int v = que.front(); que.pop(); if (deg[v] == K-1 && isclique(v)) return true; for (auto u : G[v]) { --deg[u]; if (!seen[u] && deg[u] <= K-1) que.push(u), seen[u] = true; } removed[v] = true; } vector<int> res; for (int v = 0; v < N; ++v) if (!removed[v]) res.push_back(v); if (!res.empty()) { print(1, res); return true; } return false; } int main() { int T; scanf("%d", &T); while (T--) { int N, M, K; scanf("%d %d %d", &N, &M, &K); Graph G(N); for (int i = 0; i < M; ++i) { int u, v; scanf("%d %d", &u, &v); --u, --v; G[u].push_back(v), G[v].push_back(u); } if (!solve(N, M, K, G)) printf("%d\n", -1); } }