传送门:>Here<
题意:给出一张DAG,问最多添加几条边(有向)使其强连通分量个数大于1
解题思路
最少添加几条边使其强连通我们是知道的,非常简单,就是入度为0的点与出度为0的点的较大值
但是最多添加几条边使其依然不强连通,这个问题比较复杂——但这题的解法实在是太妙了
可以倒过来想:最多可以添加几条边?很显然,对于一张$n$个点的有向图,至多$n(n-1) / 2$条边,因此总共可以再添加$[n(n-1) / 2] - M$条边。但是添加满所有的边以后肯定会使整个图成为一个强连通分量,因此我们需要把多余的边减掉。怎么减掉呢?可以贪心地思考:由于要让强连通分量大于1且边数最多,最优的情况一定是两个强连通分量,这样加的边最多。
因此我们可以设最后留下两个强连通分量,其中第一个强连通分量内点的个数为$x$,第二个为$y$。很显然$x+y = n$因此最后的边的数量最多是$x*(x-1) + y*(y-1) + x*y$。最后之所以加上$x*y$,是因为我们可以把其中一个强连通分量统统向第二个强连通分量的每一个点连相同方向的边,总共可以连$x*y$条
我们可以化简刚才的式子:
$$x*(x-1) + y*(y-1) + x*y$$$$= x*x - x + y*y - y + x*y$$$$= (x+y)^2 - n - x*y$$$$= n^2 - n - x*y$$
由于$n^2 - n$肯定是确定的,为使边数最多,要让$x*y$尽量小
已知$x+y$的值是确定的,因此$x$和$y$的差一定是越大越好。也就是其中一个要尽量小。由于$x$和$y$分别都是强连通分量,我们可以先对原图进行缩点,然后找出最小的$x$进行计算
然而并不是每一个强连通分量都可以作为$x$的,由于根据我们前面的假设,只能有两个强连通分量,因此作为x的强连通分量在缩点完成后的DAG中不能既有入度又有出度,这样的话前后都包含了强连通分量,就一定不止两个了——换句话说,如果这样的$x$作为一个来计算的话,他往别人或别人往他那里连边的时候一定会形成环。
Code
注意有多组数据,一定要初始化
/*by DennyQi*/ #include <cstdio> #include <algorithm> #include <cstring> #include <queue> #define r read() #define Max(a,b) (((a)>(b))?(a):(b)) #define Min(a,b) (((a)<(b))?(a):(b)) using namespace std; typedef long long ll; const int MAXN = 100010; const int INF = 0x3f3f3f3f; const int MOD = 998244353; inline int read(){ int x = 0; int w = 1; register unsigned char c = getchar(); for(; c^'-' && (c < '0' || c > '9'); c = getchar()); if(c == '-') w = -1, c = getchar(); for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w; } int T,N,M,x[MAXN],y[MAXN],Case; int first[MAXN],nxt[MAXN],to[MAXN],num_edge; int dfn[MAXN],low[MAXN],scc[MAXN],sta[MAXN],sccno[MAXN],amt[MAXN],rd[MAXN],cd[MAXN],top,scc_cnt,dfs_clock; inline void Init(){ memset(first,0,sizeof(first)); memset(nxt,0,sizeof(nxt)); memset(to,0,sizeof(to)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(sta,0,sizeof(sta)); memset(sccno,0,sizeof(sccno)); memset(amt,0,sizeof(amt)); memset(rd,0,sizeof(rd)); memset(cd,0,sizeof(cd)); num_edge = 0; dfs_clock = 0; scc_cnt = 0; top = 0; } inline void add(int u, int v){ to[++num_edge] = v; nxt[num_edge] = first[u]; first[u] = num_edge; } void tarjan(int u){ dfn[u] = low[u] = ++dfs_clock; sta[++top] = u; int v; for(int i = first[u]; i; i = nxt[i]){ v = to[i]; if(!dfn[v]){ tarjan(v); low[u] = Min(low[u], low[v]); } else if(!sccno[v]){ low[u] = Min(low[u], dfn[v]); } } if(dfn[u] == low[u]){ ++scc_cnt; while(1){ sccno[sta[top]] = scc_cnt; ++amt[scc_cnt]; if(sta[top--] == u) break; } } } int main(){ // freopen(".in","r",stdin); T=r; while(T--){ ++Case; printf("Case %d: ", Case); Init(); N=r,M=r; for(int i = 1; i <= M; ++i){ x[i]=r,y[i]=r; add(x[i], y[i]); } for(int i = 1; i <= N; ++i){ if(!dfn[i]){ tarjan(i); } } /* for(int i = 1; i <= N; ++i){ printf("sccno[%d] = %d ", i, sccno[i]); }*/ if(scc_cnt == 1){ printf("-1 "); continue; } for(int i = 1; i <= M; ++i){ if(sccno[x[i]] != sccno[y[i]]){ ++rd[sccno[y[i]]]; ++cd[sccno[x[i]]]; } } int _min = INF; for(int i = 1; i <= scc_cnt; ++i){ if(rd[i]!=0 && cd[i]!=0) continue; _min = Min(_min, amt[i]); } // printf("_min = %d ", _min); printf("%d ", N*(N-1) - _min*(N-_min) - M); } return 0; }