习题:codevs 2822 爱在心中 解题报告

这次的解题报告是有关tarjan算法的一道思维量比较大的题目(真的是原创文章,希望管理员不要再把文章移出首页)。

这道题蒟蒻以前做过,但是今天由于要复习tarjan算法,于是就看到codevs分类强联通分量里面只有这一道题。

题目是这样的:

“每个人都拥有一个梦,即使彼此不相同,能够与你分享,无论失败成功都会感动。爱因为在心中,平凡而不平庸,世界就像迷宫,却又让我们此刻相逢Our Home。”

在爱的国度里有N个人,在他们的心中都有着一个爱的名单,上面记载着他所爱的人(不会出现自爱的情况)。爱是具有传递性的,即如果A爱B,B爱C,则A也爱C。
如果有这样一部分人,他们彼此都相爱,则他们就超越了一切的限制,用集体的爱化身成为一个爱心天使。
现在,我们想知道在这个爱的国度里会出现多少爱心天使。而且,如果某个爱心天使被其他所有人或爱心天使所爱则请输出这个爱心天使是由哪些人构成的,否则输出-。

这是一个有向图上的问题,这道题很容易看出来一个爱心天使就是一群人互相爱然后组成的有向环。既然是有向的图求强联通分量,套上tarjan就行了。不过因为后面要输出爱心天使是由哪些人构成的,所以,我们需要记录每个人在哪个爱心天使里面,具体tarjan标程就是下面这样:

 void dfs(int u){
low[u] = dfn[u] = ++dfs_clock;
s.push(u);
for(int i = ;i < g[u].size();++i){
int v = g[u][i];
if(!dfn[v]){
dfs(v);
low[u] = min(low[u],low[v]);
}
else if(!ind[v]){
low[u] = min(low[u],dfn[v]);
}
}
if(low[u] == dfn[u]){
scc_cnt++;
while(){
int x = s.top();
s.pop();
ind[x] = scc_cnt;
sccno[scc_cnt]++;
if(x == u)break;
}
}
}
void find_scc(int n){
dfs_clock = scc_cnt = ;
for(int i = ;i <= n;++i){
if(!dfn[i])dfs(i);
}
}

如果有不懂的地方,就说明是tarjan算法还是没搞懂,请先搞定tarjan算法的基础再来看这道题。

然后就是求每个爱心天使被多少个人爱着(爱心天使也被属于它自己的人们爱着)。由于爱有传递性,我们很容易能想到传递闭包的问题。具体做法就是:

1.先求出来所有的爱心天使(在这里一个人也当作是一个爱心天使,但是输出爱心天使个数的时候,一个人组成的爱心天使是不算的)和这个爱心天使由多少个人组成。

2.给每个爱心天使编号(缩点),然后建立一个以“被爱”为方向的新的有向图。

3.在新的有向图中跑SPFA(为什么不用Floyd,这个时间复杂度太高了,所以我们还是选择scc_cnt遍的SPFA,时间复杂度最坏也就是O(nke),保证是超不了时间的。SPFA在这里起到的作用就是计算每个爱心天使的爱能否传递到某个天使)。

4.统计计数,如果size[某个爱心天使] == n的话,那么就按编号从小到大遍历每个人输出这个爱心天使由哪些人组成。如果没有任何一个爱心天使的size为n的话,(用一个数记录,如果b == 0)就输出-1.

然后呈现整体代码,还是不是很长,才刚139行。

 #include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <set>
#include <queue>
using namespace std;
const int maxn = ;
int dfn[maxn],low[maxn],dfs_clock,n,ind[maxn],m,scc_cnt,a,b,size[maxn],sccno[maxn],tim = ;
struct edge{
int u,v;
edge(int a,int b):u(a),v(b){};
};
struct edges{
int to,next,cost;
}qmap[maxn<<];
vector<int>g[maxn];
vector<int>gk[maxn];
vector<edge>ga;
int d[maxn],h[maxn];
const int INF = ;
stack<int> s;
void add(int u,int v){
qmap[tim].to = v;qmap[tim].cost = ;qmap[tim].next = h[u];h[u] = tim++;
}
void init(int n){
memset(dfn,,sizeof(dfn));
memset(low,,sizeof(low));
memset(ind,,sizeof(ind));
memset(size,,sizeof(size));
memset(sccno,,sizeof(sccno));
memset(h,-,sizeof(h));
for(int i = ;i <= n;++i){
g[i].clear();
gk[i].clear();
}
}
void dfs(int u){
low[u] = dfn[u] = ++dfs_clock;
s.push(u);
for(int i = ;i < g[u].size();++i){
int v = g[u][i];
if(!dfn[v]){
dfs(v);
low[u] = min(low[u],low[v]);
}
else if(!ind[v]){
low[u] = min(low[u],dfn[v]);
}
}
if(low[u] == dfn[u]){
scc_cnt++;
while(){
int x = s.top();
s.pop();
ind[x] = scc_cnt;
sccno[scc_cnt]++;
if(x == u)break;
}
}
}
void find_scc(int n){
dfs_clock = scc_cnt = ;
for(int i = ;i <= n;++i){
if(!dfn[i])dfs(i);
}
}
void spfa(int x){
for(int i = ;i <= scc_cnt;++i){
d[i] = INF;
}
bool visit[maxn];
memset(visit,false,sizeof(visit));
d[x] = ;
queue<int>q;
q.push(x);
visit[x] = true;
while(!q.empty()){
int y = q.front();
q.pop();visit[y] = false;
for(int i = h[y];i != -;i = qmap[i].next){
edges e = qmap[i];
if(d[y] + e.cost < d[e.to]){
d[e.to] = d[y] + e.cost;
if(!visit[e.to]){
q.push(e.to);
visit[e.to] = true;
}
}
}
}
}
void work(int i){
spfa(i);
for(int k = ;k <= scc_cnt;++k){
if(k == i)continue;
if(d[k] == INF)continue;
size[i] += sccno[k];
}
if(size[i] == n){
b = ;
for(int j = ;j <= n;++j){
if(ind[j] == i)printf("%d ",j);
}
printf("\n");
}
return;
}
int main(){
scanf("%d%d",&n,&m);
init(n);
for(int i = ;i <= m;++i){
scanf("%d%d",&a,&b);
g[a].push_back(b);
gk[b].push_back(a);
ga.push_back(edge(b,a));
}
find_scc(n);
int ans = scc_cnt;
for(int i = ;i <= scc_cnt;++i){
if(sccno[i] == )ans--;
}
printf("%d\n",ans);
for(int i = ;i < ga.size();++i){
add(ind[ga[i].u],ind[ga[i].v]);
}
for(int i = ;i <= scc_cnt;++i){
size[i] = sccno[i];
}
b = ;
for(int i = ;i <= scc_cnt;++i){
if(sccno[i] != )work(i);
}
if(b == )printf("-1\n");
return ;
}

这次的解题报告就到这里,蒟蒻继续去刷题了。还有如果有问题的话,请联系我的邮箱PC-worker@outlook.com向我留言。

上一篇:hdu4597 区间dp


下一篇:BZOJ 1051 最受欢迎的牛 解题报告