题意:给出一个n和一个m,n表示n个点(1~n),随后给出m行,每行2个数u和v,表示u是v的子集.(定义:若是u是v的子集,且v是u的子集,则u==v,需要注意的是子集关系可以传递)要求输出一个数值(最少还需要多少个子集关系,使得这n个数之间互等)
题解:对于这样一个题目,很容易就可以把子集关系转换成有向图的一条有向边,那么题目就转换为求最少增加多少条有向边使得该图成为一个强连通图.对于一个图来说,他内部可能存在许多强连通分量,对于这些强连通分量之间,他们是不需要再进行建边了,我们可以将一个个强连通分量缩成一个个点,那么我们只需要让这些缩点后的图(有向无环图)是一个强连通图即可.
这里用到一点贪心的思想:对于一个有向无环图,若有zin个入度为0的点,我们最多只需要增加zin条有向边,若是有zout个出度为0的点,我只需要增加zout条有向边,那么对于一个有向无环图要成为一个强连通图,我们最少只需要增加max(zin,zout)条有向边即可.
代码如下:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<map>
#include<vector>
using namespace std;
#define ll long long
const int maxn = 20005;
struct Tarjan {
int n; //点的个数
vector<int>e[maxn]; //邻接表存图
int DFN[maxn], LOW[maxn];
int index; //编辑计数器
int stk[maxn]; //栈
bool ins[maxn]; //记录点是否在栈中
int top;
vector<vector<int> >ans;
void init(int N) { //初始化
n = N;
ans.clear();
for (int i = 1; i <= n; i++)
e[i].clear();
top = 0;
index = 0;
memset(DFN, -1, sizeof(DFN));
memset(ins, 0, sizeof(ins));
}
void add_edge(int u, int v) {//添加边
e[u].push_back(v);
}
void dfs(int u) { //从u点开始搜索
DFN[u] = LOW[u] = ++index;
stk[top++] = u; //为了记录这个连通分量中的节点
ins[u] = true;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if (DFN[v] == -1) {
dfs(v);
LOW[u] = min(LOW[u], LOW[v]);
}
else if (ins[v])
LOW[u] = min(LOW[u], DFN[v]);
}
if (DFN[u] == LOW[u]) { //当DFN==LOW时说明stk中点可以形成一个强连通分量
vector<int>q;
int v = stk[top - 1];
while (u != v) {
q.push_back(v);
ins[v] = false;
top--;
v = stk[top - 1];
}
q.push_back(u);
ins[u] = false;
top--;
ans.push_back(q);
}
}
void solve() { //运行该函数后产生答案
for (int i = 1; i <= n; i++)
if (DFN[i] == -1) dfs(i);
}
}T;
int num[maxn];
bool vis[maxn];
vector<int> e[maxn]; //存强连通缩点后的得到的新图
int n, m;
int main(){
while (~scanf("%d%d",&n,&m)){
int u, v;
T.init(n); //初始化
while(m--){ //建有向图
scanf("%d%d", &u, &v);
T.add_edge(v, u); //这里是u->v
}
T.solve(); //获得该有向图的所有强连通分量
for (int i = 1; i<=n; i++) //清空新图
e[i].clear();
//缩点
for (int i = 0; i< T.ans.size(); i++)
for (int j = 0; j< T.ans[i].size(); j++)
num[T.ans[i][j]] = i + 1;
/*
for(int i=1;i<=n;i++) //输出缩点情况
printf("%d:%d\n",i,num[i]);
*/
for (int i = 1; i<=n; i++) //建新有向无环图
for (int j = 0; j < T.e[i].size(); j++) {
if (num[i] == num[T.e[i][j]]) continue;
e[num[i]].push_back(num[T.e[i][j]]);
//printf("%d->%d\n",num[i],num[T.e[i][j]]);输出图
}
n = T.ans.size(); //新图的点数
//以上为Tarjan算法的强连通缩点操作,得到新的有向无环图为e[],点数为n
memset(vis, 0, sizeof(vis));
int zin = 0, zout = 0; //分别为入度为0的点数和出度为0的点数
for (int i = 1; i <= n; i++){
if (e[i].size() == 0)zout++;
for (int j = 0; j < e[i].size(); j++)
vis[e[i][j]] = true;
}
for(int i=1;i<=n;i++)
if (!vis[i])zin++;
if (n != 1)printf("%d\n", max(zin, zout));
else printf("0\n"); //若是只有一点点需要特判结果为0,否则输出1
}
return 0;
}