【题目链接】
【思路要点】
- 第一问本质上是问图中最大的强连通分量的大小,Tarjan算法即可。
- 对于第二问,我们先将图缩点,显然答案有下界\(max\{cntin,cntout\}\),其中\(cntin\)为入度为0的点的个数,\(cntout\)为出度为0的点的个数。
- 题目中的两个样例均满足答案恰好为下界,因此我们猜测答案始终为下界,并试图构造一组解。
- 我们将所有入度为0的点排在一边,出度为0的点排在另一边(如果一个点入度、出度均为0,我们将其同时排在两边),若一个入度为0的点\(A\)可以到达一个出度为零的点\(B\),连边\(A\Rightarrow B\),这将构成一张二分图。
- 显然如果我们连接若干条边使得该二分图强联通,那么一定对应了一种使原图联通的方案。
- 由于保证了答案在1000以内,根据答案的下界,这张二分图两边的点数一定在1000以内。
- 这张二分图满足性质:每个点的度数均非0。
- 我们找到这张二分图的一个极大的匹配(不一定要最大,只需要极大即可),将匹配上的\(x\)个点对按顺序连接,形成一个环,这将需要增加\(x\)条边。
- 考虑剩余的没有被连接入环的点,由于这是这张二分图的一个极大的匹配,并且这张二分图上每个点的度数均非0,每个不在环上的入度为0的点一定能够到达环上,每一个不在环上的出度为0的点一定能够从环上到达,否则我们显然能够加一条边,构造出更大的匹配。
- 因此,我们每次找到一对不在环上的入/出度为0的点\(x\)、\(y\),连边\(y\Rightarrow x\),即可使得\(x\)、\(y\)与环强联通。还可能会剩下一些不在环上的入/出度为0的点中的一种,将每个点用一条边与环相接即可。
- 至此,我们共用了\(max\{cntin,cntout\}\)条边。
- BZOJ没有SPJ,无法正确评测本题,以下代码由笔者本人测试通过。
- 时间复杂度\(O(\frac{NM}{w}+Ans^2)\)(其中\(w=64\))。
- 另附上本题的SPJ,运行前请保证同一目录下存在“scg.in”、“scg.out”、“scg.ans”三个文件。
【附本题的SPJ】
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<string> #include<algorithm> #include<fstream> #include<sstream> #include<vector> #include<map> #include<set> #include<queue> #include<deque> #include<cassert> #include<complex> using namespace std; const int MaxN = 10000 + 10; const char* inFile = "scg.in"; const char* outFile = "scg.out"; const char* ansFile = "scg.ans"; const char* logFile = "check.txt"; int grade(int s = 0, string msg = string("Wrong Answer!")) { FILE * file = fopen(logFile, "w"); if(s == 0) { fprintf(file, "%s\n0\n", msg.c_str()); return 0; } fprintf(file, "FC:\n%d\n%s\n",s,msg.c_str()); return 0; } vector<int> adj[MaxN], rev[MaxN]; int N, M; bool vis[MaxN]; int Q[MaxN], n; void dfs1(int u) { vis[u] = true; for(int i=0;i<adj[u].size();++i) if(!vis[adj[u][i]]) dfs1(adj[u][i]); Q[n ++] = u; } void dfs2(int u) { vis[u] = true; for(int i=0;i<rev[u].size();++i) if(!vis[rev[u][i]]) dfs2(rev[u][i]); } bool check() { fill(vis, vis + 1 + N, 0); for(int i=1;i<=N;++i) if(!vis[i]) dfs1(i); fill(vis, vis + 1 + N, 0); dfs2(Q[n - 1]); for(int i=1;i<=N;++i) if(!vis[i]) return false; return true; } int A, C, user_A, user_C; int loadInput(FILE* inf) { if(fscanf(inf, "%d %d", &N, &M) != 2 || N < 3 || N > 10000 || M < 0 || M > 200000) return 0; for(int i=0;i<M;++i) { int x, y; if(fscanf(inf, "%d %d", &x, &y) != 2 || min(x,y) < 1 || max(x,y)>N) { cerr << "error on line #"<<i+2<<" < "<<x<<" , "<<y<<">"<<endl; return 0; } adj[x].push_back(y); rev[y].push_back(x); } return 1; } int loadAnswer(FILE *inf) { if(fscanf(inf, "%d", &C) != 1 || C < 1 || C > N) return 0; if(fscanf(inf, "%d", &A) != 1 || A < 0 || A > N || A > 1000) return 0; return 1; } const int FullScore = 10; const int LoScore = 2; const int MidScore = 4; void loadOutput(FILE* inf) { if(fscanf(inf, "%d", &user_C) != 1) { fclose(inf); exit(grade(0, "Format Error!")); } if(user_C != C) { fclose(inf); exit(grade(0, "Wrong Answer!")); } if(fscanf(inf, "%d", &user_A) != 1) { fclose(inf); exit(grade(LoScore, "<C> Correct but <A> not found! ")); } if(user_A != A) { fclose(inf); exit(grade(LoScore, "<C> Correct but <A> Wrong!")); } int x, y; for(int i=0;i<A;++i) { if(fscanf(inf, "%d %d", &x, &y) != 2 || min(x, y) < 1 || max(x, y) > N) { fclose(inf); exit(grade(MidScore, "<C> and <A> Correct! Solution Format Error!")); } adj[x].push_back(y); rev[y].push_back(x); } fclose(inf); if(check()) grade(10, "Correct!"); else grade(MidScore, "<C> and <A> Correct but Wrong Solution!"); exit(0); } int main() { grade(0, "Internal Error!"); FILE *file = fopen(inFile, "r"); if(file == NULL) exit(grade(0, "No Input File!")); if(!loadInput(file)) { fclose(file); exit(grade(0, "Input File Format Error!")); } fclose(file); file = fopen(ansFile, "r"); if(file == NULL) exit(grade(0, "No Answer File!")); if(!loadAnswer(file)){ fclose(file); exit(grade(0, "Answer File Format Error!")); } fclose(file); file = fopen(outFile, "r"); if(file == NULL) exit(grade(0, "No Output File!")); loadOutput(file); }
【本题的Lemon版SPJ】
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<string> #include<algorithm> #include<fstream> #include<sstream> #include<vector> #include<map> #include<set> #include<queue> #include<deque> #include<cassert> #include<complex> using namespace std; const int MaxN = 10000 + 10; char* inFile; char* outFile; char* ansFile; char* logFile; char* scoreFile; int grade(int s = 0, string msg = string("Wrong Answer!")) { FILE * file = fopen(logFile, "w"); FILE * score = fopen(scoreFile, "w"); fprintf(score, "%d\n", s); if(s == 0) { fprintf(file, "%s\n0\n", msg.c_str()); return 0; } fprintf(file, "FC:\n%d\n%s\n",s,msg.c_str()); return 0; } vector<int> adj[MaxN], rev[MaxN]; int N, M; bool vis[MaxN]; int Q[MaxN], n; void dfs1(int u) { vis[u] = true; for(int i=0;i<adj[u].size();++i) if(!vis[adj[u][i]]) dfs1(adj[u][i]); Q[n ++] = u; } void dfs2(int u) { vis[u] = true; for(int i=0;i<rev[u].size();++i) if(!vis[rev[u][i]]) dfs2(rev[u][i]); } bool check() { fill(vis, vis + 1 + N, 0); for(int i=1;i<=N;++i) if(!vis[i]) dfs1(i); fill(vis, vis + 1 + N, 0); dfs2(Q[n - 1]); for(int i=1;i<=N;++i) if(!vis[i]) return false; return true; } int A, C, user_A, user_C; int loadInput(FILE* inf) { if(fscanf(inf, "%d %d", &N, &M) != 2 || N < 3 || N > 10000 || M < 0 || M > 200000) return 0; for(int i=0;i<M;++i) { int x, y; if(fscanf(inf, "%d %d", &x, &y) != 2 || min(x,y) < 1 || max(x,y)>N) { cerr << "error on line #"<<i+2<<" < "<<x<<" , "<<y<<">"<<endl; return 0; } adj[x].push_back(y); rev[y].push_back(x); } return 1; } int loadAnswer(FILE *inf) { if(fscanf(inf, "%d", &C) != 1 || C < 1 || C > N) return 0; if(fscanf(inf, "%d", &A) != 1 || A < 0 || A > N || A > 1000) return 0; return 1; } const int FullScore = 10; const int LoScore = 2; const int MidScore = 4; void loadOutput(FILE* inf) { if(fscanf(inf, "%d", &user_C) != 1) { fclose(inf); exit(grade(0, "Format Error!")); } if(user_C != C) { fclose(inf); exit(grade(0, "Wrong Answer!")); } if(fscanf(inf, "%d", &user_A) != 1) { fclose(inf); exit(grade(LoScore, "<C> Correct but <A> not found! ")); } if(user_A != A) { fclose(inf); exit(grade(LoScore, "<C> Correct but <A> Wrong!")); } int x, y; for(int i=0;i<A;++i) { if(fscanf(inf, "%d %d", &x, &y) != 2 || min(x, y) < 1 || max(x, y) > N) { fclose(inf); exit(grade(MidScore, "<C> and <A> Correct! Solution Format Error!")); } adj[x].push_back(y); rev[y].push_back(x); } fclose(inf); if(check()) grade(10, "Correct!"); else grade(MidScore, "<C> and <A> Correct but Wrong Solution!"); exit(0); } int main(int argc,char *argv[]) { inFile = argv[1]; outFile = argv[2]; ansFile = argv[3]; scoreFile = argv[5]; logFile = argv[6]; grade(0, "Internal Error!"); FILE *file = fopen(inFile, "r"); if(file == NULL) exit(grade(0, "No Input File!")); if(!loadInput(file)) { fclose(file); exit(grade(0, "Input File Format Error!")); } fclose(file); file = fopen(ansFile, "r"); if(file == NULL) exit(grade(0, "No Answer File!")); if(!loadAnswer(file)){ fclose(file); exit(grade(0, "Answer File Format Error!")); } fclose(file); file = fopen(outFile, "r"); if(file == NULL) exit(grade(0, "No Output File!")); loadOutput(file); }
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 10005; const int MAXM = 200005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } bitset <MAXN> arr[MAXN]; vector <int> a[MAXN], sub[MAXN]; int n, m, top, Max, Stack[MAXN]; int timer, low[MAXN], dfn[MAXN]; int tot, belong[MAXN]; int x[MAXM], y[MAXM]; bool instack[MAXN]; vector <int> inpos, outpos; bool inused[MAXN], outused[MAXN]; bool inp[MAXN], outp[MAXN]; void getarr(int pos) { arr[pos].set(pos); for (unsigned i = 0; i < sub[pos].size(); i++) { int point = sub[pos][i]; for (unsigned j = 0; j < a[point].size(); j++) { int tmp = belong[a[point][j]]; if (arr[tmp][tmp] == true) arr[pos] |= arr[tmp]; else { getarr(tmp); arr[pos] |= arr[tmp]; } } } } void work(int pos) { dfn[pos] = low[pos] = ++timer; instack[pos] = true; Stack[++top] = pos; for (unsigned i = 0; i < a[pos].size(); i++) if (dfn[a[pos][i]] == 0) { work(a[pos][i]); chkmin(low[pos], low[a[pos][i]]); } else if (instack[a[pos][i]]) chkmin(low[pos], dfn[a[pos][i]]); if (low[pos] == dfn[pos]) { tot++; int cnt = 0, tmp = 0; while (tmp != pos) { cnt++; tmp = Stack[top--]; belong[tmp] = tot; sub[tot].push_back(tmp); instack[tmp] = false; } chkmax(Max, cnt); } } int main() { read(n), read(m); for (int i = 1; i <= m; i++) { read(x[i]), read(y[i]); a[x[i]].push_back(y[i]); } for (int i = 1; i <= n; i++) if (dfn[i] == 0) work(i); writeln(Max); for (int i = 1; i <= m; i++) if (belong[x[i]] != belong[y[i]]) { outp[belong[x[i]]] = true; inp[belong[y[i]]] = true; } int cntin = 0, cntout = 0; for (int i = 1; i <= tot; i++) { if (outp[i] == false) { cntout++; outpos.push_back(i); } if (inp[i] == false) { cntin++; inpos.push_back(i); } arr[i].reset(); } writeln(max(cntin, cntout)); if (max(cntin, cntout) == 0) return 0; for (int i = 1; i <= tot; i++) if (arr[i][i] == false) getarr(i); int last = -1; for (unsigned i = 0; i < inpos.size(); i++) for (unsigned j = 0; j < outpos.size(); j++) { if (!inused[i] && !outused[j] && arr[inpos[i]][outpos[j]]) { inused[i] = outused[j] = true; if (last != -1) printf("%d %d\n", sub[outpos[last]][0], sub[inpos[i]][0]); last = j; } } printf("%d %d\n", sub[outpos[last]][0], sub[inpos[0]][0]); for (unsigned i = 0; i < inpos.size(); i++) for (unsigned j = 0; j < outpos.size(); j++) { if (!inused[i] && !outused[j]) { inused[i] = outused[j] = true; printf("%d %d\n", sub[outpos[j]][0], sub[inpos[i]][0]); } } int PointOnCircle = sub[inpos[0]][0]; for (unsigned i = 0; i < inpos.size(); i++) if (!inused[i]) printf("%d %d\n", PointOnCircle, sub[inpos[i]][0]); for (unsigned i = 0; i < outpos.size(); i++) if (!outused[i]) printf("%d %d\n", sub[outpos[i]][0], PointOnCircle); return 0; }