hdu 5739 点双连通分量+乘法逆元超详细讲解

题目戳这里http://acm.hdu.edu.cn/showproblem.php?pid=5739
题意大致如下:题目大概说给一张无向点带有权无向图。定义连通图的权值为图中各点权的乘积,图的权值为其包含的各连通图的权值之和,设zi 为删掉i点后图的权值,求S=(1*z1 + 2*z2 + 3*z3 + …….. + n*zn);
这道题简直毒性,感觉打了一道大模拟,这道题的细节巨多,要十分注意!!!。
首先,给出的图可能本就不连通,这是要注意的,也就是可能包含多个连通子图,所以我们要分开处理。
大致的操作步骤是:
1、先求出图的每个连通子图的权值(这个步骤可以在步骤2中顺便实现),也就是在不考虑删点的情况下每个连通子图图的权值大小,每个子图权值大小加起来就是在不删点的情况下原图的权值,我们令它为Sum在下面我直接引用。
2、然后我们需要跑一遍tarjan求割点,如果不会割点请参考http://blog.csdn.net/Kamisama123/article/details/75007415(强行刷访问量/阴险)
3、然后我们枚举每个点,先把Sum也就是整个图的权值和减去这个点所在的连通子图的权值我们令这个为ss,然后我们再求删掉这个点后的子图权值加上去就是Zi了。
4、求子图权值的时候,我们把点分为两类(割点与非割点)
(1)当点是非割点的时候,我们可以清楚地知道,删掉这个点对原来的连通子图,没有什么影响,所以答案就是子图权值除以这个点,在膜意义下我们乘它的逆元。如果不会逆元请参考http://blog.csdn.net/Kamisama123/article/details/72823082
(2)当该点为割点的时候,就要稍微麻烦一下了,因为原来的子图会裂成多个更小的图,因为tarjan跑图的dfs序,更可以说是把图转成了一棵树,只不过这棵树可能有边连向了祖先或者其它子树或者它的孙子们,但遍历的顺序其实就是一棵树。(这里需要深刻理解一下tarjan算法)所以我们对每个点u求一个val[u]表示u这颗子树
的权值乘积,那么这个点u的所有v,如果有low[v]>=dfn[u]或者(pre[u])就表示断开这个点后v会分离,所以把所有的这些裂开的val[v]统计加起来作为ans1,然后我们算剩下的权值,也就是这个点的父亲,用总的就是这个连通子图的权值大小除以所有裂开的val[v](用逆元)然后除以这个u点本身作为ans2,然后把ans1和ans2和ss加起来就好了。别忘了最后乘上一个i。
注意!!!
当然这个题还有几个坑点,当这个点u是根节点的时候,ans2要变为0,因为根节点上面没有点了,但是算下来ans2会是1。
其次就是当连通子图大小为1只有一个点的时候,去掉这个点就为0了,也要特判。

下面是我AC的代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#define N 200005
#define dnt long long
#define mod 1000000007
#define del(a,b) memset(a,b,sizeof(a))
using namespace std;
vector<int>son[N];
int T,n,m,num,head[N],a,b,belong[N],root[N];
int dfn[N],low[N],idc,cut[N],treecnt;
dnt aa[N],treeval[N],val[N];
struct Edge{
    int v,next;
};
Edge e[2*N];
void adde(int i,int j){
    e[++num].v=j;
    e[num].next=head[i];
    head[i]=num;
}
dnt ipow(dnt a,dnt b){
    dnt rt=1;
    for(dnt i=b,uni=a;i;uni=(uni*uni)%mod,i>>=1)
    if(i&1) rt=(rt*uni)%mod;
    return rt;
}
dnt inva(dnt a){
    return ipow(a,mod-2);
}
void init(){
    num=0;del(head,0);
    del(dfn,0);idc=0;
    del(cut,0);treecnt=0;
    del(root,0);
}
void tarjan(int u,int fa){
    dfn[u]=low[u]=++idc;
    belong[u]=treecnt;
    dnt an=aa[u];
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].v;
        if(v==fa)continue;
        if(!dfn[v]){
            tarjan(v,u);
            son[u].push_back(v);
            an=(an*val[v])%mod;
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])cut[u]++;
        }else low[u]=min(low[u],dfn[v]);
    }
    val[u]=an;
}
int main(){
//  freopen("date.in","r",stdin);
 // freopen("myans.out","w",stdout);
    scanf("%d",&T);
    while(T--){
        init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)son[i].clear();
        for(int i=1;i<=n;i++)
        scanf("%I64d",&aa[i]);
        for(int i=1;i<=m;i++){
            scanf("%d%d",&a,&b);
            adde(a,b);
            adde(b,a);
        }
        dnt tot=0,S=0;
        for(int i=1;i<=n;i++)
        if(!dfn[i])root[i]=1,cut[i]=-1,treecnt++,tarjan(i,-1),treeval[treecnt]=val[i];
        for(int i=1;i<=treecnt;i++)tot=(tot+treeval[i])%mod;
        for(int i=1;i<=n;i++){
            dnt temp=(tot-treeval[belong[i]]+mod)%mod;
            if(cut[i]<=0&&treeval[belong[i]]!=aa[i])temp=(temp+treeval[belong[i]]*inva(aa[i]))%mod;
            else{
                dnt ans1=0,ans2=1;
                for(int j=0;j<son[i].size();j++){
                    int v=son[i][j];
                    if(low[v]>=dfn[i]){
                        ans1=(ans1+val[v])%mod;
                        ans2=(ans2*val[v])%mod;
                    }
                }
                dnt t=(treeval[belong[i]]*inva(ans2))%mod;
                t=(t*inva(aa[i]))%mod;
                if(root[i])t--;
                temp=(temp+ans1+t)%mod;
            }
            temp=(temp*i)%mod;
            S=(S+temp)%mod;
        }
        printf("%I64d\n",S);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值