JZOJ-senior-3899. 【NOIP2014模拟】逻辑的连通性

3899. 【NOIP2014模拟】逻辑的连通性 (Standard IO)

Time Limits: 2000 ms Memory Limits: 262144 KB Detailed Limits
Goto ProblemSet

Description

假如有命题p 一定能推出命题q,则称p 是q 的充分条件,q 是p 的必要条件。
特别的,当p 既是q 的充分条件,又是q 的必要条件时,称p 和q 互为充要条件
现在有n 个命题,其中一些是另一些的充分条件。请问有多少对命题互为充要条件?

Input

第一行三个正整数n,m,分别表示命题数、已知关系数
接下来m 行,每行两个正整数p 和q,表示命题p 是命题q 的充分条件

Output

仅一行,一个整数,表示充要条件的对数

Sample Input

5 5
1 3
3 2
2 1
4 5
5 4

Sample Output

4
样例说明:
4 对充要条件分别是(1, 2)、(2, 3)、(1, 3)、(4, 5)

Data Constraint

对于10% 的数据,n <= 10;m <= 50
对于40% 的数据,n <= 500;m <= 1000
对于另外10% 的数据,数据中保证没有重边且m = n^2
对于100% 的数据,n<= 50000;m <= 600000

题目大意:给定一些命题,求这些命题两两在一个环中的对数。

解题思路:Tarjan模板

dfn[u]表示在深度搜索中遍历到该节点的次序
low[u]表示以u节点为树根,u及u以下树节点所能找到的最小次序号

uses math;
var
    a,b:array[0..600001,1..2] of longint;
    z,dfn,low:array[0..50001] of longint;
    p,bz:array[0..50001] of boolean;
    n,m,t,i:longint;
    ans:int64;
procedure qsort(i,j:longint);
    var
        l,r,mid:longint;
    begin
        l:=i;
        r:=j;
        mid:=a[(l+r) div 2,1];
        repeat
            while a[l,1]mid do dec(r);
            if l<=r then
                begin
                    a[0]:=a[l];
                    a[l]:=a[r];
                    a[r]:=a[0];
                    inc(l);
                    dec(r);
                end;
        until l>r;
        if l0 then
        begin
            if bz[a[i,2]]=false then
                begin
                    tarjan(a[i,2]);//如果节点i未被访问过继续向下找
                    low[x]:=min(low[x],low[a[i,2]]);
                end
            else if p[a[i,2]] then low[x]:=min(low[x],dfn[a[i,2]]);//如果节点i还在栈内
        end;
        if dfn[x]=low[x] then//如果x的最早访问时间等于其实际访问时间,则可把其视作回路的"始点"
            begin
                k:=0;//连通块长度
                while (z[0]>0) and (z[z[0]]<>x) do//将由x直接或间接扩展出的点标记为同一连通块,标记访问后出栈
                    begin
                        p[z[0]]:=false;
                        inc(k);
                        dec(z[0]);
                    end;
                p[x]:=false;
                inc(k);
                dec(z[0]);
                ans:=ans+(k-1)*k div 2;
            end;//如果节点x是强连通分量的根,退栈直到x的前一个数据,记录这个强连通分量的数据
    end;
begin
    readln(n,m);
    for i:=1 to m do readln(a[i,1],a[i,2]);
    qsort(1,m);
    b[a[1,1],1]:=1;
    for i:=2 to m do if a[i,1]<>a[i-1,1] then
        begin
            b[a[i-1,1],2]:=i-1;
            b[a[i,1],1]:=i;
        end;
    b[a[m,1],2]:=m;//前向星
    t:=0;
    ans:=0;
    for i:=1 to n do if bz[i]=false then tarjan(i);
    writeln(ans);
end.

更短更快的模板

#include
#include

#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,a,b) for(int i=a;i>=b;--i)
#define ll long long

using namespace std;

const int N=5e4+5,M=6e5+5;
int n,m,tp,num,time;
int v[N],in[N],z[N],dfn[N],low[N],last[N];
ll ans;

struct edge
{
	int to,next;
}e[2*M];

void link(int x,int y)
{
	e[++num]=(edge){y,last[x]},last[x]=num;
}

void Tarjan(int x)
{
	dfn[x]=low[x]=++time,v[x]=1;
	z[++tp]=x,in[x]=1;
	for(int w=last[x];w;w=e[w].next)
	{
		int y=e[w].to;
		if(!v[y]) Tarjan(y),low[x]=min(low[x],low[y]);
		else if(in[y]) low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		int cnt=0;
		while(tp&&z[tp]!=x) in[z[tp]]=0,++cnt,--tp;
		in[z[tp]]=0,++cnt,--tp;
		ans+=(ll)cnt*(cnt-1)/2;
	}
}

int main()
{
	freopen("tarjan.in","r",stdin);
	freopen("tarjan.out","w",stdout);
	scanf("%d%d",&n,&m);
	fo(i,1,m)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		link(x,y);
	}
	fo(i,1,n) if(!v[i]) Tarjan(i);
	printf("%lld",ans);
}

你可能感兴趣的:(强连通分量,Tarjan)