石中练习:强连通分量

T1-P2194\operatorname{T1-P2194}

在一个有向图中,HXY 可以烧掉一个回路上的情侣。 为了方便统计,可以先缩点,来统计一个强连通分量中花费的点。

并且缩点之后,成了一个 DAG\operatorname{DAG}。只需要烧入度为 00 的强连通分量。最后乘法原理计数就好了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10,Mod=1e9+7;
int n,m,nm,scc[N],s[N],c,a[N],cnt[N],w[N];
long long ans,anss;
vector<int>e[N],re[N],sn[N];
bool v[N];
void dfs(int from){
    v[from]=1;
    for(int to:e[from])if(!v[to])dfs(to);
    s[c++]=from;
}
void rfs(int from){
    scc[from]=nm;
    if(a[nm]>w[from]) a[nm]=w[from],cnt[nm]=1;
    else
    if(a[nm]==w[from]) cnt[nm]++;
    for(int to:re[from])if(!scc[to])rfs(to);
}
void ko(){
    for(int i=1;i<=n;i++)if(!v[i])dfs(i);
    for(int i=n-1;~i;i--)
        if(!scc[s[i]]) nm++, rfs(s[i]);
}
signed main() {
	memset(a,0x3f,sizeof a);
	ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++) {
    	cin>>w[i];
	}
	cin>>m;
    for(int i=1,x,y;i<=m;i++) {
        cin>>x>>y;
        e[x].push_back(y), re[y].push_back(x);
    }
    ko();
    anss=1;
    for(int i=1;i<=nm;i++) {
    	ans+=a[i];
    	anss*=cnt[i];
    	anss%=Mod;
	}
	cout<<ans<<" "<<anss; 
    return 0;
}

T2-P4306\operatorname{T2-P4306}

直接用 Floyd\operatorname{Floyd}。本题主要统计两点之间是否可达。因为 n2000n\leq2000 ,直接用 bitset\operatorname{bitset} 优化。

#include<bits/stdc++.h>
using namespace std;
int n,ans;
const int N = 2e3+10;
bitset<N>f[N];
char ch;
int main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			cin>>ch;
			f[i][j]=(ch-'0')|i==j;
		}
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			if(f[i][k])f[i]|=f[k];
	for(int i=1;i<=n;i++) ans+=f[i].count();
	cout<<ans;
	return 0;
}

T3-P1262\operatorname{T3-P1262}

也是有向图,如果间谍的揭发关系形成了环。就只要揭发一个。由此,缩点后就和第一题大抵相同。但还有特殊情况需要考虑。

  • 如果一个罪犯,不能被贿赂,也不能被揭发,则本题无解。

一种简单的实现方式:在找环缩点时,如果不能被贿赂,它所在的环也不确定是否可以被完全贿赂。所以我们找一个可贿赂的点,保证环合法。

再遍历全图,如果一个节点没有被包含在强连通分量内,说明无解。

T4-P1407\operatorname{T4-P1407}

主要考察了图论建模。本题乍一看,婚姻关系是二人互相的,算不了。我们就先看看过程:

  • 假设 B1B_1G1G_1 抛弃,选择了 G2G_2。那 B2B_2 就会选择 G2G_2\dots\dots。最后 BNB_NG1G_1 复合。

此时有一个问题,只有 BB 选择 GG。不如转变思想,让 GG 选择 BB

即现在的关系用男向女连边,以前的关系用女向男连边。

  • 假设 B1B_1G1G_1 抛弃,选择了 G2G_2。那 G1G_1 就会选择 B2B_2\dots\dots,最后也要复合。

^* 表示以前的男女朋友关系。

这样就形成了“男选女”。如果他们的婚姻关系形成了一个环,为 男,女,^*男,女,^*\dots男,女,^*最开始的男。

任意一条无星号边被去掉,都会被前缘续上,成为“女选男”。


至此,本题结论显然,当在男女处在一个环内,为不安全,反之为安全。

变成强连通分量板子。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n,m,nm,scc[N],s[N],c,rd[N],cd[N];
//rd,cd 分别为入度,出度
vector<int>re[N],e[N],ne[N];
//ne 为新图
queue<int>q;
bool v[N];
int ix;
void dfs(int from){
    v[from]=1;
    for(auto to:e[from])if(!v[to])dfs(to);
    s[c++]=from;
}
void rfs(int from) {
    scc[from]=nm;
    for(int to:re[from])if(!scc[to])rfs(to);
}
void ko(){
    for(int i=1;i<=ix;i++)if(!v[i])dfs(i);
    for(int i=ix-1;~i;i--)
        if(!scc[s[i]]) nm++, rfs(s[i]);
}
map<string,int>mp; string x,y;
string b[N],g[N];
int main() {
    cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>g[i]>>b[i];
        x=g[i],y=b[i];
        if(mp[x]==0) mp[x]=++ix;
        if(mp[y]==0) mp[y]=++ix;
        e[mp[y]].push_back(mp[x]),re[mp[x]].push_back(mp[y]);
    }
    cin>>m;
    for(int i=1;i<=m;i++) {
    	cin>>x>>y;
    	e[mp[x]].push_back(mp[y]),re[mp[y]].push_back(mp[x]); 
	}
    ko();
    for(int i=1;i<=n;i++) {
    	if(!(scc[mp[g[i]]]==scc[mp[b[i]]])) cout<<"Safe";
    	else cout<<"Unsafe";
    	cout<<"\n";
	}
    return 0;
}

T5-P4819\operatorname{T5-P4819}

一眼看很简单,直接缩点,然后将所有入度为 00 的强连通分量统计。再算概率。


但是有问题。

3 2
1 3
2 3

选 1,2 固然可以,但模拟一下就发现了问题:

选 1,那 3 被确定。

  • 如果 1,3 是杀手,结束。

  • 如果不是,那么 2 是杀手,结束。

使用排除法,一次就可以知道凶手。

如果一个入度为 0 的原图点,与之相连的所有强连通分量,入度都 >1>1。说明选择其他的点,就可以通过上述方法确定凶手。


在实现中,直接将强连通分量大小为 1 的点看作原图点就可以了。需要注意,排除法只能用一次。

还要注意,如果有重边,就会多计算入度。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n,m,nm,scc[N],s[N],c,rd[N],cd[N],num[N];
//rd,cd 分别为入度,出度
vector<int>re[N],e[N],ne[N];
//ne 为新图 
queue<int>q;
bool v[N],vis[N];
void dfs(int from){
    v[from]=1;
    for(auto to:e[from])if(!v[to])dfs(to);
    s[c++]=from;
}
void rfs(int from) {
    scc[from]=nm;
    num[nm]++;
    for(int to:re[from])if(!scc[to])rfs(to);
}
void ko(){
    for(int i=1;i<=n;i++)if(!v[i])dfs(i);
    for(int i=n-1;~i;i--)
        if(!scc[s[i]]) nm++, rfs(s[i]);
}
int main() {
    cin>>n>>m;
    for(int i=1,x,y;i<=m;i++) {
    	cin>>x>>y;
    	e[x].push_back(y),re[y].push_back(x);
	}
    ko();
    for(int i=1;i<=n;i++) {
        for(auto j:e[i]) {//vis:给到达的强连通分量打标记,使只能被加一次边
            if(scc[i]!=scc[j]&&vis[scc[j]]==0)
                ne[scc[i]].push_back(scc[j]),
				rd[scc[j]]++, cd[scc[i]]++,
				vis[scc[j]]=1;
        }
        for(auto j:e[i]) vis[scc[j]]=0;
    }
    double cnt=0;
    int h=0;
    for(int i=1;i<=nm;i++) {
    	if(rd[i]==0) {
			cnt++;
    		if(num[i]==1&&h==0) {
    			int x=1;
    			for(auto j:ne[i]) {
    				if(rd[j]<2) {
    					x=0;break;
					}
				}
				if(x) h=1;
			}
    	}
	}
	printf("%.6lf",(n-cnt+h)/n);
    return 0;
}
posted @ 2023-10-09 15:51  cjrqwq  阅读(11)  评论(0)    收藏  举报  来源