D1 HL 图的高级应用 tarjan算法

临时跑去D班取听神仙课,不会的还是好多(我真菜);

1.tarjan算法;

受欢迎的牛

tarjan求强连通分量,进行缩点,求缩点后出度为0的点,如果存在两个及以上的点出度都为零 那么输出无解;

否则输出这个强联通分量的大小;

#include<bits/stdc++.h>
#define N 100500
using namespace std;
template<typename T>inline void read(T &x)
{
    x=0;
    register int f=1;
    register char ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
struct gg
{
    int next,y;
}e[N<<1];
int lin[N],dfn[N],low[N],cnt,tot,hl,n,m;
int du[N],id[N],all[N];
bool v[N];
stack<int>s;
inline void add(int x,int y)
{
    cnt++;
    e[cnt].y=y;
    e[cnt].next=lin[x];
    lin[x]=cnt;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++tot;
    s.push(x);v[x]=true;
    for(int i=lin[x];i;i=e[i].next)
    {
        int u=e[i].y;
        if(!dfn[u])
        {
            tarjan(u);
            low[x]=min(low[x],low[u]);
        }
        else if(v[u])low[x]=min(low[x],dfn[u]);
    }
    int k;
    if(low[x]==dfn[x])
    {
        ++hl;
        do
        {
            k=s.top();s.pop();
            v[k]=false;
            id[k]=hl;all[hl]++;
        }while(x!=k);
    }
}
int main()
{
    read(n);read(m);
    int a,b;
    for(int i=1;i<=m;i++)
    {
        read(a);read(b);
        add(a,b);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])    tarjan(i);
        for(int k=1;k<=n;k++)
        {
            for(int i=lin[k];i;i=e[i].next)
            {
                int u=e[i].y;
                if(id[k]!=id[u])
                    du[id[k]]++;
            }
        }
    int t=0;
    for(int i=1;i<=hl;i++)
        if(!du[i])
        {
            if(t)
            {    
                puts("0");
                return 0;
            }
            t=i;
        }
    printf("%d\n",all[t]);
    return 0;
}
View Code

2.杀人游戏

tarjan求强连通分量,对于一个强联通的分量,知道其中一个人就知道这个块里的其他人;

细节:考虑一种情况,求完tarjan后缩点,存在一个入度为0的点,它的强联通分量大小为1,他所连的点的入度>1,我们是不需要知道这个点的;

因为他一定能被其他n-1个点确定后推出;如果等于1,那么我们是必须知道这个入度为0的点的身份的;

#include<bits/stdc++.h>
#define N 500500
using namespace std;
template<typename T>inline void read(T &x)
{
    x=0;
    register int f=1;
    register char ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
struct pink
{
    int x,y;
}a[N<<1];
struct gg
{
    int next,y;
}e[N<<1];
int lin[N<<1],dfn[N],low[N],cnt,tot,hl,n,m,ans[N];
int in[N],id[N],all[N],flag;
bool v[N];
stack<int>s;
inline void add(int x,int y)
{
    cnt++;
    e[cnt].y=y;
    e[cnt].next=lin[x];
    lin[x]=cnt;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++tot;
    s.push(x);v[x]=true;
    for(int i=lin[x];i;i=e[i].next)
    {
        int u=e[i].y;
        if(!dfn[u])
        {
            tarjan(u);
            low[x]=min(low[x],low[u]);
        }
        else if(v[u])    low[x]=min(low[x],dfn[u]);
    }
    int k;
    if(low[x]==dfn[x])
    {
        ++hl;
        do
        {
            k=s.top();s.pop();
            v[k]=false;
            id[k]=hl;all[hl]++;
        }while(x!=k);
    }
}
int main()
{    
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    read(n);read(m);
    for(int i=1;i<=m;i++)
    {
        read(a[i].x);read(a[i].y);
        add(a[i].x,a[i].y);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])    tarjan(i);
    memset(lin,0,sizeof(lin));
    cnt=0;
    for(int i=1;i<=m;i++)
    {
        if(id[a[i].x]!=id[a[i].y])
        {
            in[id[a[i].y]]++;
            add(id[a[i].x],id[a[i].y]);
        }
    }
    int ans=0;
    for(int i=1;i<=hl;i++)
    {
        if(!flag&&!in[i]&&all[i]==1)
        {
            int p=0;
            for(int j=lin[i];j;j=e[j].next)
            {
                int yy=e[j].y;
                if(in[yy]==1) p=1;
            }
            if(!p) flag=1;
        }
        if(!in[i]) ans++;
    }
    if(flag) ans--;
    printf("%.6f\n",1.0-(double)ans/(double)n);
    return 0;
}
View Code

3.矿场搭建

我们需要tarjan跑出所有的割点,然后dfs每一个联通块及大小,如果这个联通块内部不存在割点,那么我么至少建两个救援所,为了避免其中一个救援所炸掉,

而且我们要明白,救援所是不能建在隔点上的,如果这个联通块中存在两个及以上割点,我们是不需要键救援所的;如果存在一个割点,建两个,设在不是割点的电上;

#include<bits/stdc++.h>
using namespace std;
const int maxn=510;
struct gg
{
    int y,next;
}a[maxn*maxn];
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
long long hl,ans=1;
int lin[maxn],len;
inline void add(int x,int y)
{
    a[++len].y=y;
    a[len].next=lin[x];
    lin[x]=len;
}
int dfn[maxn],low[maxn],num,root;
bool cut[maxn];
inline void tarjan(int x,int fa)
{
    dfn[x]=low[x]=++num;
    int flag=0;
    for (int i=lin[x];i;i=a[i].next)
    {
        int y=a[i].y;
        if (!dfn[y])
        {
            tarjan(y,x);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])
            {
                ++flag;
                if (x!=root||flag>1) cut[x]=1;
            }
        }
        else if (y!=fa)
            low[x]=min(low[x],dfn[y]);
    }
}
int vis[maxn],k,cnt;
inline void dfs(int x)
{
    vis[x]=k;
    ++cnt;
    for(int i=lin[x];i;i=a[i].next)
    {
        int y=a[i].y;
        if(vis[y]!=k&&cut[y]) ++num,vis[y]=k;
        if(!vis[y])    dfs(y);
    }
}
int main()
{
    //freopen("input.in","r",stdin);
    //freopen("output.out","w",stdout);
    for (int ca=1;;++ca)
    {
        int n=0,m;
        read(m);
        if(!m) exit(0);
        memset(dfn,0,sizeof(dfn));
        memset(vis,0,sizeof(vis));
        memset(cut,0,sizeof(cut));
        memset(lin,0,sizeof(lin));
        hl=k=num=len=0;
        ans=1;
        for (int i=1;i<=m;++i)
        {
            int x,y;read(x);read(y);
            n=max(n,max(x,y));
            add(x,y);add(y,x);
        }
        for (int i=1;i<=n;++i)
            if(!dfn[i]) root=i,tarjan(i,i);
        for (int i=1;i<=n;++i)
            if(!vis[i]&&!cut[i])
            {
                ++k,cnt=num=0;
                dfs(i);
                if(!num) hl+=2,ans*=cnt*(cnt-1)/2;
                if(num==1) hl++,ans*=cnt;
            }
        printf("Case %d: %lld %lld\n",ca,hl,ans);
    }
    return 0;
}
View Code

圆方树,听懂了但没写题;

竞赛图,强联通竞赛图;

支配树,被支配;

4.炸弹

大致思路:线段树优化建边,区间连边,求tarjan强连通分量,然后反向拓扑;

我觉得自己对线段树优化建边理解的不是特别深刻,也是第一次写;

但是他可以将边优化成log级别的,节省很多空间和时间;

题解:

首先应该明确的是这道题一定是用图论知识来做,当一个炸弹i被引爆时,对于能够被当前这颗炸弹i引爆的炸弹j,我们肯定是要建一条i-->j的边,但是由于题目中的边数较多,所以不可能这样建图,那我们可以想到,当一个炸弹被引爆,那么最总一共被引爆的炸弹一定是连续的,所以就有引出区间问题了,那么区间问题我们就可以用线段树来做,当i炸弹能将(ID)【L,R】的炸弹全部引爆时,就建一条pos【i】-->ID的边,那么这样就一定会形成环,因为这颗炸弹处于区间中间,那么久tarjan缩点,然会就是求每个点能够最远到达哪个点,那么要么就dfs(有点慢),有么就top排序,如果是top排序的话,就要反向建图,从最远的点一步一步的推回去,从而解决题目。

总结:充分利用线段树的区间和树形性质,当图论问题转换成区间问题时,我们就可以利用线段树的特性来优化时间和空间。
原文:https://blog.csdn.net/shiyongyang/article/details/77984795

#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define LL long long 

template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-')  f=-1;  ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
    x*=f;
}

int n,tot,tc,num,top,cnt,lin[N<<2],linc[N<<2],vis[N<<2],ins[N<<2],c[N<<2],Stack[N<<2],pos[N<<2];
int dfn[N<<2],low[N<<2];
LL X[N],R[N],mn[N<<2],mx[N<<2],vmin[N<<1],vmax[N<<1],rd[N<<1];

struct gg {
    int x,y,next;
}a[N<<2],e[N<<2]; 
queue<int> q;
inline void add(int x,int y) {
    a[++tot].y=y; a[tot].next=lin[x]; lin[x]=tot;
}

inline void build(int l,int r,int p) {
    if(l==r) {
        pos[l]=p;
        return ;
    }
    int mid=(l+r)>>1;
    mn[p]=1ll<<62,mx[p]=-1ll<<62;
    build(l,mid,p<<1);
    build(mid+1,r,p<<1|1);
    add(p,p<<1); add(p,p<<1|1);
}

inline void connect(int b,int e,int x,int l,int r,int y) {
    if(b<=l&&e>=r) {
        add(x,y);
        //cout<<b<<' '<<e<<endl;
        return ;
    }
    int mid=(l+r)>>1;
    if(b<=mid) connect(b,e,x,l,mid,y<<1);
    if(e>mid) connect(b,e,x,mid+1,r,y<<1|1);
}

void tarjan(int x)
{
    dfn[x]=low[x]=++num,ins[x]=1,Stack[++top]=x;
    for(int i=lin[x];i;i=a[i].next)
    {
        int y=a[i].y;
        //cout<<x<<' '<<y<<endl;
        if(!dfn[y]) tarjan(y),low[x]=min(low[x],low[y]);
        else if(ins[y]) low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        int t;
        cnt++,vmin[cnt]=1ll<<62,vmax[cnt]=-1ll<<62;
        do
        {
            t=Stack[top--],ins[t]=0,c[t]=cnt;
            vmin[cnt]=min(vmin[cnt],mn[t]),vmax[cnt]=max(vmax[cnt],mx[t]);
        }while(t!=x);
    }
}
int main() {
    read(n);
    build(1,n,1);
    for(int i=1;i<=n;i++) {
        read(X[i]); read(R[i]);
        mn[pos[i]]=mx[pos[i]]=X[i];
    }
    for(int i=1;i<=n;i++) {
        connect(lower_bound(X+1,X+n+1,X[i]-R[i])-X,upper_bound(X+1,X+n+1,X[i]+R[i])-X-1,pos[i],1,n,1);
        //cout<<pos[i]<<endl;
    }
    for(int i=1;i<=4*n;i++) {
        if(!dfn[i])
        {
            tarjan(i);
            //cout<<i<<endl;
        }
    }
    //cout<<cnt<<endl;
    int cc=0;
    for(int x=1;x<=n*4;x++ )
        for(int i=lin[x];i;i=a[i].next)
                if(c[x]!=c[a[i].y])
                    e[++cc].y=c[x],e[cc].next=linc[c[a[i].y]],linc[c[a[i].y]]=cc,rd[c[x]]++;
    
    for(int i=1;i<=cnt;i++) {
        if(!rd[a[i].y]) q.push(a[i].y);
    }
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int i=linc[x];i;i=e[i].next)
        {
            vmin[e[i].y]=min(vmin[e[i].y],vmin[x]),vmax[e[i].y]=max(vmax[e[i].y],vmax[x]),rd[e[i].y]--;
            if(!rd[e[i].y]) q.push(e[i].y);
        }
    }
    LL ans=0;
    for(int i = 1 ; i <= n ; i ++ )
        ans=(ans+(long long)(upper_bound(X+1,X+n+1,vmax[c[pos[i]]])-lower_bound(X+1,X+n+1,vmin[c[pos[i]]]))*i) % 1000000007;
    printf("%lld\n" , ans%1000000007);
    return 0;
} 
View Code

 

posted @ 2019-07-06 21:14  Tyouchie  阅读(206)  评论(0编辑  收藏  举报