The King’s Problem
在一个王国里面, 国王有一个新的问题. 皇城中有N个城市M条单行路,为了让他的王国更加高效,国王想要将他的王国划分成几个州,每个城市必须属于一个州。对于两个城市(u,v),必须满足以下3个条件:
1、如果有一条从u到v的路,也有一条从v到u的路,那么u、v必须属于同一个州;
2、对于每一个州里的任何两个城市u、v,至少要有一方能到达另一方(必须经过同一个州的点到达)。
3、一个城市只能属于一个州。
现在国王想要知道他的王国最少可以划分成多少个州。
Input
第一行是一个数字T,代表测试组数,接下来是T组测试。
每组测试数据的第一行包含两个整数n、m(0 < n <= 5000,0 <= m <= 100000),分别指n个城市,m条单行路,接下来m行,每行包含两个数字a、b,表示有一条从城市a到b的单行路。
Output
输出包含T行。
每组测试数据输出一行。
Sample Input
1
3 2
1 2
1 3
Sample Output
2
题意在寂静的Kingdom,国王遇到了一个新问题。王国里有N个城市,城市之间有许多方向性的道路。这意味着,如果有一条从U到V的道路,你只能从城市到第五城,但不能从第五城到美国城市,为了更有效地统治他的王国,国王想要把他的王国分割成几个州,每个城市必须属于一个州。更重要的是,对于每一对城市(U,V),如果有一种从u到V,从V到u,(u,v)必须属于同一状态的方法。国王必须保证,在每一个州,我们都可以从U到v,或者在每一对城市(u,V)之间从v到u,而不经过任何属于另一个州的城市。
现在国王请求你的帮助,他想知道他必须把王国分成多少个州。
思路:targin缩点+匈牙利最小路径覆盖
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
const int maxn = 5009;
int head[100001], dfn[maxn], low[maxn];//dfn是时间戳low是最小的时间戳head头
int chudu[maxn], rudu[maxn], id[maxn], all[maxn];//id是染色后的点 all是染色的点数
bool instal[maxn];int cnt, tot, gg;//instal是判断在不在栈中
stack<int>s;
struct Edge{
int next, to;
}edge[100009];//链式前向星存图
inline void add(int x, int y) {
cnt++;
edge[cnt].to = y;
edge[cnt].next = head[x];
head[x] = cnt;
}
void init() {
while(!s.empty()) {
s.pop();
}
gg = 0;cnt = 0, tot = 0;
memset(head, 0, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(instal, false, sizeof(instal));
memset(all, 0, sizeof(all));
memset(chudu, 0, sizeof(chudu));
memset(id, 0, sizeof(id));
memset(edge, 0, sizeof(edge));
}
//链式前向星加边
void targin(int x) {
dfn[x] = low[x] = ++cnt;
s.push(x);
instal[x] = true;
for(int i = head[x];i;i = edge[i].next) {
int u = edge[i].to;
if(!dfn[u]) {
targin(u);
low[x] = min(low[x], low[u]);
}
else if(instal[u]) low[x] = min(low[x], dfn[u]);
}
int k;
if(low[x] == dfn[x]) {
++gg;
do{
k = s.top();s.pop();
instal[k] = false;
id[k] = gg;all[gg]++;//id是染色gg是第一次的点把所有的一个联通快的点缩成这个点all是存的缩点的点的数量
}while(x != k);
}
}//强连通
struct hunge{
vector<int>line[maxn];int used[maxn],nxt[maxn];
int n;
void initma(int n) {
this->n = n;
for(int i = 0;i <= n;++i) {
line[i].clear();
}
memset(nxt, -1, sizeof(nxt));
}
void add(int x, int y) {
line[x].push_back(y);
}
bool find(int x)
{
for(int i = 0;i < line[x].size();++i)
{
if(line[x][i] && !used[line[x][i]])
{
int h = line[x][i];
used[h] = 1;
if(nxt[h] == -1 || find(nxt[h]))//如果这个人没匹配或这个人可以和别人匹配就让匹配
{
nxt[h] = x;
return true;
}
}
}
return false;
}//查询匹配的人
int match()
{
int sum = 0;
for(int i = 1;i <= n;++i)
{
memset(used,0,sizeof(used));
if(find(i))
{
sum++;
}
}
return sum;
}//找匹配个数
}gao;//二分匹配板子
int main() {
int t;
scanf("%d", &t);
while(t--) {
init();
int n, m;
scanf("%d%d", &n, &m);
for(register int i = 1;i <= m;++i) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
}
for(register int j = 1;j <= n;++j) {
if(!dfn[j]) {
targin(j);
}
}
gao.initma(n);
for(register int i = 1;i <= n;++i) {
for(register int j = head[i];j ;j = edge[j].next) {
int u = edge[j].to;
if(id[i] != id[u]) {
gao.add(id[i], id[u]);
}
}
}
int ans = gao.match();
printf("%d\n", gg-ans);//gg是缩点后的点数量
}
return 0;
}