hdu 5739(点双联通分量 )

本文解析了一道关于图论的竞赛题目,涉及无向图、连通性、DFS及双连通分量等核心概念。介绍了如何通过预处理确定每个树的价值,并利用DFS将无根树转换为有根树进行高效计算。

Fantasia

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 375    Accepted Submission(s): 30


Problem Description
Professor Zhang has an undirected graph G with n vertices and m edges. Each vertex is attached with a weight wi. Let Gi be the graph after deleting the i-th vertex from graph G. Professor Zhang wants to find the weight of G1,G2,...,Gn.

The weight of a graph G is defined as follows:

1. If G is connected, then the weight of G is the product of the weight of each vertex in G.
2. Otherwise, the weight of G is the sum of the weight of all the connected components of G.

A connected component of an undirected graph G is a subgraph in which any two vertices are connected to each other by paths, and which is connected to no additional vertices inG.
 

Input
There are multiple test cases. The first line of input contains an integerT, indicating the number of test cases. For each test case:

The first line contains two integers n and m(2n105,1m2×105) -- the number of vertices and the number of edges.

The second line contains n integers w1,w2,...,wn(1wi109), denoting the weight of each vertex.

In the next m lines, each contains two integers xi and yi(1xi,yin,xiyi), denoting an undirected edge.

There are at most 1000 test cases and n,m1.5×106.
 

Output
For each test case, output an integer S=(i=1nizi) mod (109+7), where zi is the weight of Gi.
 

题目大意:给你一个森林,森林中的每个点有固定的价值,森林中树的价值为树中所有点的乘积,森林的价值为森林中所有树价值的总和,从1到n每次删除一个点,设删完点后森林的价值*对应得点的序号为Gi,问Gi(i从1到n)的总和是多少

解题思路: 预处理:预处理出每个树的价值,将每一个无根树通过DFS变为有根树,在DFS的过程中求出每一个点及其下所有子节点的乘积存储到数组val中,求出每一个点的dfs序存储到pre中,求出每一个点的low值,low值就是此点能连接到的dfs序中标号最小的点,存储到low中(不懂low值或pre是什么的请见刘汝佳入门经典-训练指南上的312页或者百度双连通分量,推荐前者)

具体操作:对于某个树,当要删除树中点 i 时,判断其所有子节点low[j]是否大于等于pre[i],如果是则说明删除点 i 后其子节点j和其下的所有点会变成单独的连通块,即刚才预处理出的val[j],用一个值ans2统计所有的val[j] ,再用一个值ans1表示删除点 i 后剩余节点的价值,用树的价值乘以i价值 的逆元和所有val[j]的逆元 就可以了。多个树稍微变变就好了

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<string>
using namespace std;

typedef long long ll;
const ll mod=1e9+7;

int n,m;
int w[100010];//每个节点的权重
vector<int> mapp[100010];//存图
vector<int> son[100010];//每个节点的儿子
int pre[100010];//节点的DFS序
int low[100010];//每个节点能连接到的最小的DFS序标号
ll val[100010];//每个节点下的价值
ll treeVal[100010];//每个树的价值
int tree[100010];//每个节点对应的树的标号
bool root[100010];//节点是否为根节点,根节点会乘所有点的逆元,因为乘以所有点的逆元后想要得到的是0,但是为1,所有特殊判断
ll ans,ans1,ans2;
int treeNumber;
int clock;

void init()
{
    clock=1;
    memset(pre,0,sizeof(pre));
    memset(root,0,sizeof(root));
    treeNumber=1;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=0;i<=n;i++)
    {
        mapp[i].clear();
        son[i].clear();
    }
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        mapp[a].push_back(b);
        mapp[b].push_back(a);
    }
}

ll dfs(int u,int fa)
{
    int lowu=pre[u]=clock++;//DFS序,1开始
    tree[u]=treeNumber;
    ll sum=w[u];
    for(int i=0;i<mapp[u].size();i++)
    {
        int v=mapp[u][i];
        if(!pre[v])
        {
            int lowv=dfs(v,u);
            lowu=min(lowv,lowu);
            son[u].push_back(v);
            sum=(sum*val[v])%mod;
        }
        else
        {
            lowu=min(lowu,pre[v]);
        }
    }
    val[u]=sum;
    return low[u]=lowu;
}

ll qpow(ll a,ll b)
{
    ll ans=1;
    a=a%mod;
    while(b)
    {
        if(b&1)
            ans=(ans*a)%mod;
        b>>=1;
        a=(a*a)%mod;
    }
    return ans;
}

ll inverse(ll a)
{
    return qpow(a,mod-2);
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        init();
        for(int i=1;i<=n;i++)
        {
            if(!pre[i])
            {
                dfs(i,-1);
                treeVal[treeNumber]=val[i];
                root[i]=true;//i为根
                treeNumber++;//树的个数
            }
        }
        ll sum=0;
        for(int i=1;i<treeNumber;i++)
            sum+=treeVal[i];
        ans=0;
        for(int i=1;i<=n;i++)
        {
            ans2=0;//变成联通快的价值和
            ans1=treeVal[tree[i]]; //出去ans2和i的价值和
            ll ss=sum-ans1;//其余树的价值
            ans1=ans1*inverse(w[i])%mod;
            for(int j=0;j<son[i].size();j++)
            {
                int v=son[i][j];
                if(low[v]>=pre[i])
                {
                    ans1=ans1*inverse(val[v])%mod;
                    ans2=(ans2+val[v])%mod;
                }
            }
            if(root[i])
                ans1-=1;//根节点要减1,如果能计算回根,此时ans1一定为1!这时候不要他,因为他给删掉了
           ans=(ans+(ans1+ans2+ss)*i%mod)%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值