手铐-牛客(tarjan+dfs)

时间:2020-08-08
本文章向大家介绍手铐-牛客(tarjan+dfs),主要包括手铐-牛客(tarjan+dfs)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

手铐-牛客(tarjan+dfs)

题意:

给你一个连通无向图,保证每个点最多属于一个简单环,每个点度数最多为3,求这个图有多少“手铐图形个数”

其中“手铐图形个数”,定义为三元组(x,y,S),其中x和y表示图上的两个点,S表示一条x到y的简单路径,而且必须满足:

1.x和y分别在两个不同的简单环上

2.x所在的简单环与路径S的所有交点仅有x,y所在的简单环与路径S的所有交点仅有y。

(x,y,S)与(y,x,S)算同一个手铐;原图:

 

缩点之后:

缩点之后变成方点:

由1.4构成的手铐有4个,可以走上边也可以走下边,

假设在两个方点之间有x个方点,则构成的手铐数量为2^x;

tarjan缩点成无向图之后,就变成了树,然后进行树形dp;

数组维护手铐的值:

考虑u的所有子树中点数超过1(方点)的块走到u的方案数dp[u],以此考虑u的儿子,对于第一个儿子v,若u也是点大于1的块,那么第一个儿子中点数大于1的点到u构成的手铐对答案的贡献就是dp[v],对于其他儿子,可以经过u点与之前考虑的儿子中点数大于1的点构成手铐,此时对答案的贡献就是dp[u]⋅dp[v]。

对于dp[u]的维护,如果u点数大于1,那么dp[u]+=2⋅dp[v],否则dp[u]+=dp[v],时间复杂度O(n+m)

将节点当成半手铐看;dp[u]表示以u为父节点的半手铐数量

1,圆形节点 dp[u]+=dp[v];

2,方形节点 dp[u]+=dp[v]*2;

ans[u]表示以u为父节点的手铐数,v是子节点;

ans[u]+=f[u]*f[v]+ans[v];

代码:

#include<bits/stdc++.h>
/*#include<iostream>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<queue>
#include<cstring>*/
using namespace std;
const int maxn=1e6+10;
const int mod=19260817;
const int inf=0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> pii;
const int N=5e5+10;

inline int read()
{
    int x=0,w=0;
    char ch=getchar();
    while (!isdigit(ch))
       w|=ch=='-',ch=getchar();
    while (isdigit(ch))
       x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return w?-x:x;
}

struct node{
    int v,next;
}G[maxn<<2];
int head[maxn<<1],cnt;
inline void add(int x,int y)
{
    G[cnt]=(node){y,head[x]};
    head[x]=cnt++;
}

int dfn[maxn],low[maxn],tot;
stack<int> st;
//int Stack[maxn],top;
int vis[maxn];
int belong[maxn];
int num;
ll f[maxn];
int cot[maxn];

void tarjan(int u,int pre)
{
    dfn[u]=low[u]=++tot;
    st.push(u);
    vis[u]=1;
    for(int i=head[u]; i!=-1; i=G[i].next)
    {
        if(G[i].v==pre) continue;
        int v=G[i].v;
        if(!dfn[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v])
         low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        ++num;
        int t;
        do{
            t=st.top();
            st.pop();//缩点
            ++cot[num];//判断方点还是圆点
            vis[t]=0;
            belong[t]=num;//t属于num所代表的节点
        }while(u!=t);
    }
}

ll dp[maxn];
vector <int> g[maxn];
//ll tmp[maxn];

void dfs(int u,int fa)//当前节点,父节点
{
    dp[u]=0;//初始值
    f[u]=(cot[u]>1);//1.0标记圆方点
    for(int v:g[u])
    {
        //int v=G[i].v;
        if(v==fa) continue;//与u相连的节点是父节点跳过这个点,剪枝
        dfs(v,u);
        dp[u]=(1ll*dp[u]+f[v]*f[u]+dp[v])%mod;//计算手铐数
        f[u]=(1ll*f[u]+f[v]*(cot[u]>1?2:1))%mod;
        //f[u]=f[u]>1;   f[u]=(1ll*f[u]+f[v]*(f[u]==1?2:1))%mod;过不了
        //f[u]的值一直在变化,如果不分开存储,会出现问题。
    }
}

int n,m;
int main()
{
    n=read(),m=read();
    for(int i=0; i<=n; i++)
     head[i]=-1;
    for(int i=1; i<=m; i++)
    {
        int u,v;
        u=read(),v=read();
        add(u,v);
        add(v,u);
    }
    for(int i=1; i<=n; i++)
    {
        if(!dfn[i])
         tarjan(i,0);//缩点
    }

    for(int i=1; i<=n; i++)//构造新图
    {
        for(int j=head[i]; j!=-1; j=G[j].next)
        {
            int u=belong[i],v=belong[G[j].v];
            if(u!=v)
             g[u].push_back(v);
        }
    }
    dfs(1,0);
    cout<<dp[1]<<endl;//答案
    system("pause");
    return 0;
}

原文地址:https://www.cnblogs.com/sweetlittlebaby/p/13460600.html