石中练习:强连通分量
在一个有向图中,HXY 可以烧掉一个回路上的情侣。 为了方便统计,可以先缩点,来统计一个强连通分量中花费的点。
并且缩点之后,成了一个 。只需要烧入度为 的强连通分量。最后乘法原理计数就好了。
#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;
}
直接用 。本题主要统计两点之间是否可达。因为 ,直接用 优化。
#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;
}
也是有向图,如果间谍的揭发关系形成了环。就只要揭发一个。由此,缩点后就和第一题大抵相同。但还有特殊情况需要考虑。
- 如果一个罪犯,不能被贿赂,也不能被揭发,则本题无解。
一种简单的实现方式:在找环缩点时,如果不能被贿赂,它所在的环也不确定是否可以被完全贿赂。所以我们找一个可贿赂的点,保证环合法。
再遍历全图,如果一个节点没有被包含在强连通分量内,说明无解。
主要考察了图论建模。本题乍一看,婚姻关系是二人互相的,算不了。我们就先看看过程:
- 假设 将 抛弃,选择了 。那 就会选择 ,。最后 和 复合。
此时有一个问题,只有 选择 。不如转变思想,让 选择 。
即现在的关系用男向女连边,以前的关系用女向男连边。
- 假设 将 抛弃,选择了 。那 就会选择 ,,最后也要复合。
表示以前的男女朋友关系。
这样就形成了“男选女”。如果他们的婚姻关系形成了一个环,为 男,女,男,女,男,女,最开始的男。
任意一条无星号边被去掉,都会被前缘续上,成为“女选男”。
至此,本题结论显然,当在男女处在一个环内,为不安全,反之为安全。
变成强连通分量板子。
#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;
}
一眼看很简单,直接缩点,然后将所有入度为 的强连通分量统计。再算概率。
但是有问题。
3 2
1 3
2 3
选 1,2 固然可以,但模拟一下就发现了问题:
选 1,那 3 被确定。
-
如果 1,3 是杀手,结束。
-
如果不是,那么 2 是杀手,结束。
使用排除法,一次就可以知道凶手。
如果一个入度为 0 的原图点,与之相连的所有强连通分量,入度都 。说明选择其他的点,就可以通过上述方法确定凶手。
在实现中,直接将强连通分量大小为 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;
}

浙公网安备 33010602011771号