tarjan 题目汇总(含解析)

下面容许我偷个懒,洛谷上写过的blog我就不来再抄一遍了

  1. 洛谷P3436 【[POI2006]PRO-Professor Szu】(别称:作死的老教授)
  2. 洛谷P4306 【[JSOI2010]连通数】
  3. 洛谷P4303 【[AHOI2006]基因匹配】(额…这篇好像发错了,emmm…没事大家可以选择忽略)
  4. 洛谷UVA11294 【Wedding】(有点BT了最后我再稍微讲讲吧毕竟这道题是道英文题)

那么我先讲一讲今天晚上的第一道题吧(话说今天晚上好黑教室里还就我一个银_ (:з」∠)_)!
题目如下:

[POI2008]BLO-Blockade

题目描述

There are exactly nn towns in Byteotia.

Some towns are connected by bidirectional roads.

There are no crossroads outside towns, though there may be bridges, tunnels and flyovers. Each pair of towns may be connected by at most one direct road. One can get from any town to any other-directly or indirectly.

Each town has exactly one citizen.

For that reason the citizens suffer from loneliness.

It turns out that each citizen would like to pay a visit to every other citizen (in his host’s hometown), and do it exactly once. So exactly n\cdot (n-1)n⋅(n−1) visits should take place.

That’s right, should.

Unfortunately, a general strike of programmers, who demand an emergency purchase of software, is under way.

As an act of protest, the programmers plan to block one town of Byteotia, preventing entering it, leaving it, and even passing through.

As we speak, they are debating which town to choose so that the consequences are most severe.

Task Write a programme that:

reads the Byteotian road system’s description from the standard input, for each town determines, how many visits could take place if this town were not blocked by programmers, writes out the outcome to the standard output.

给定一张无向图,求每个点被封锁之后有多少个有序点对(x,y)(x!=y,1<=x,y<=n)满足x无法到达y

输入输出格式

(不贴了,反正一堆英文你懂得)

输入输出样例

输入样例#1:
5 5
1 2
2 3
1 3
3 4
4 5
输出样例#1:
8
8
16
14
8

题目分析

话说翻译讲的好模糊啊我看着道题看了半天就是看不出来那个答案是怎么来的。。。
好吧我再好好讲讲:这道题就是说给你n个点和m条无向边,让你分别求:第i个点所连的边都被切掉的时候有多少对点是无法相互到达的(注意1 ,2 和 2 , 1是不同的一对点,也可以理解为最终求出的点对要*2),也就是说第i个点也是要算在无法相互到达的点对之内的。另外注意题目给出的图是个无向连通图。完了我也不想赘述了,题意也就差不多是这样,tarjan缩缩点差不多可以水过去。

此外,本题与割点相关

代码

#include<bits/stdc++.h>
#define M 110000
typedef long long ll;
using namespace std;
inline ll read()  //日常读优(最近tarjan一堆大数据搞得我很焦躁什么数据类型都用longlong了)
{
    ll x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x;
}
ll n,m,tim,pat=1;
ll size[M],f[M];
ll dfn[M],low[M];
ll head[M],ans[M];
struct Edge{ int v,next; }e[M*10];
void add(int u,int v) { e[pat].next=head[u]; e[pat].v=v; head[u]=pat++; }

void tarjan(int u)  //tarjan(相较模板有改动,因为要求割点。PS:本题和割点也有关)
{
    int ape=1,chd=0;
    ++size[u]; ans[u]+=n-1;
    dfn[u]=low[u]=++tim;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].v; if(f[u]==v) continue;
        if(!dfn[v])
        {
            f[v]=u; tarjan(v); size[u]+=size[v]; ++chd; low[u]=min(low[u] , low[v]);
            if(u!=1 && dfn[u]<=low[v]) ape+=size[v], ans[u]+=(n-ape)*size[v];
//是割点,appear加上该子树的size,ans加上未出现的节点数(n-appear)*(该子树的大小)size
        }
        else low[u]=min(low[u] , dfn[v]);
    }
    if(u==1 && chd>=2) //单独判断根节点u是否是割点,是的话就进行累加
        for(int i=head[u];i;i=e[i].next) if(f[e[i].v]==u)
        for(int j=e[i].next;j;j=e[j].next) if(f[e[j].v]==u)
            ans[u]+=size[e[i].v]*size[e[j].v]; //这里是直接子树大小 * 子树大小了
}

int main()
{
    n=read(); m=read();
    for(int i=1;i<=m;++i) //读边不解释
        { int u=read(),v=read(); add(u,v); add(v,u); }

    tarjan(1); //该图本身联通,只需从点1出发即可
    for(int i=1;i<=n;++i)  //tarjan中已经处理完了答案,直接输出即可
        printf("%lld\n",ans[i]<<1);
    return 0;
}

分割线来调皮了~_(:з」∠)_


[HNOI2012]矿场搭建

题目描述

煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。

请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。

输入输出格式

输入格式:
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N<=500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。

输出格式:
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。

输入输出样例

输入样例#1:
9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0
输出样例#1:
Case 1: 2 4
Case 2: 4 1

说明

Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);

Case 2 的一组解为(4,5,6,7)。

题目分析

(这道题是道中文题不用解释好开森~_(:з」∠)_)
咳咳。然后这道题同上也是要求割点的。= =||| 懒癌晚期不想说什么了代码里分析

代码

#include<bits/stdc++.h>
#define M 510
typedef long long ll;
using namespace std;
inline ll read()  //读优你懂的
{
    ll x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
int n,m;
ll tim,pat,top,part,res,ans;
ll dfn[M],low[M];
ll num[M],stk[M];
bool vis[M];
struct Node{ int gd,es;}grp[M];  //强连通分量记录割点数和非割点数你懂的
struct Edge{ int v; Edge *next;}*head[M]; //邻接表存边你懂的(只不过用了指针而已啦)
void add(int u,int v) { Edge *p=new(Edge); p->v=v; p->next=head[u]; head[u]=p; }

void tarjan(int u)  //tarjan缩点你懂的
{
    dfn[u]=low[u]=++tim;
    for(Edge *i=head[u];i;i=i->next)
    {
        int v=i->v;
        if(!dfn[v])
        {
            tarjan(v); low[u]=min(low[u] , low[v]);
            if(dfn[u]<=low[v]) ++num[u];  //子树的数目++(比起模板也就这里变了一下吧)
        }
        else low[u]=min(low[u] , dfn[v]);
    }
}

void dfs(int u)  //dfs上再来求强连通分量
{
    vis[u]=true; stk[++top]=u;
    for(Edge *i=head[u];i;i=i->next)
    {
        int v=i->v;
        if(vis[v]) continue;
        dfs(v);
        if(low[v]>=dfn[u])
        {
            ++part;
            int j=0;
            do{
                j=stk[top--];
                if(num[j]) ++grp[part].gd;
                else ++grp[part].es;
            }while(j!=u);
            ++top;
        }
    }
}

int main()
{
    int T=0;
while(true)
{
    memset(num , 0 , sizeof(num));
    memset(stk , 0 , sizeof(stk));
    memset(vis , 0 , sizeof(vis));
    memset(dfn , 0 , sizeof(dfn));
    memset(low , 0 , sizeof(low));
    memset(head, 0 ,sizeof(head));
    for(int i=0;i<M;++i)grp[i].gd=grp[i].es=0;
    pat=tim=part=top=ans=n=0; res=1; 
    /*         这些可以放到子函数里那样看起来清楚点          */
    m=read();
    if(!m) return 0;
    while(m--)
    {
        int u=read(),v=read();
        n=max(n , max(u , v));  //题目中没说几个矿场我们就找最大值存下来当矿场数
        add(u,v); add(v,u); 
    }

    for(int i=1;i<=n;++i) if(!dfn[i])
    {
        tarjan(i);
        if(num[i]>0)  //根节点的有效子树数量要-1(当然这里没有这个if也没事直接减减就行)
            --num[i];
    }
    for(int i=1;i<=n;++i) //这时候再深搜找强连通分量(已排除根节点有效子树数量的干扰)
        if(!vis[i]) dfs(i);
    for(int i=1;i<=part;++i)
    {
        if(grp[i].gd>=2) continue;  //大于两个割点的就不用建出口了,因为任意一个割点矿场毁了后,
        //该强连通分量矿场里的人还可以通过剩下来的割点矿场逃到其他强连通分量矿场里找出口
        if(grp[i].gd==1) { ++ans; res*=grp[i].es; }
        //只有一个割点就要再建一个出口(方案数*该强连通分量矿场中非割点矿场的数量)
        else if(grp[i].gd==0) { ans+=2; res*=(grp[i].es*(grp[i].es-1))/2; }
//没有割点的话说明该强连通分量与外界完全分离了,要建两个出口以防万一(可能建出口的那个矿场会炸)
    }
    printf("Case %d: %lld %lld\n",++T,ans,res);
}
}

= =||| 怎么说好咧,这个题目自己都还是有点疑问,等会儿再想想。。。


那么接下来我就稍微讲讲【wedding】这道题吧。题意就是给你个n和m,表示有n队夫妇,编号为0~n-1,其中0号夫妇今天办了酒宴,然后这些夫妇中有m对人是不能坐在一列的。(好像说是通奸啥的?)那么这m对人相当于两两排斥,不能同时坐在0号新郎那列(但是可以同时坐在0号新娘那列),并且每对夫妇也不可以坐在同一列(假设3号新郎坐在0号新郎那列,那么3号新娘就只能坐在0号新娘那列,也就是和3号新郎不同的那列),然后的话就是要你求和0号新娘坐在同一列的夫妇的任意一种状况(任意一种状况求出来都能AC,也不知道是数据输入的时候就保证了只有唯一解还是评测的时候是一个一个情况去对的,前者可能性较大)。好了那么这道题讲的也就差不多,其实本质上是一个2-SAT问题,不过是最终的答案变成了无矛盾组的取反而已,我也就不多说了这道题还是有点做的价值滴~~~

OK,这篇专题就结束了,小伙伴们下次见~_(:з」∠)_

ヾ( ̄▽ ̄)Bye~Bye~ (喜欢请点赞不喜欢请评论)

posted @ 2018-04-09 21:23  Jμdge  阅读(386)  评论(0编辑  收藏  举报