HDU 5739 Fantasia

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5739

题目大意:给你一个森林,森林中的每个点有固定的价值,森林中树的价值为树中所有点的乘积,森林的价值为森林中所有树价值的总和,从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]的逆元 就可以了。多个树稍微变变就好了。

AC代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
#define RI(N) scanf("%d",&(N))
#define RII(N,M) scanf("%d %d",&(N),&(M))
#define RIII(N,M,K) scanf("%d %d %d",&(N),&(M),&(K))
#define mem(a) memset((a),0,sizeof(a))
using namespace std;
const int inf=1e9+7;
const int inf1=-1*1e9;
double EPS=1e-10;
typedef long long LL;

int pre[100005];//节点的dfs序
int n,m;
vector<int>G[100005];//图
int weight[100005];//节点权值
int treeNumber=0;
int tree[100005];//每个子节点对应的树的标号
LL val[100005];//每个节点及其下所有子节点的价值成绩
LL treeValue[100005];//每个树的价值
LL ans=0,ans1=0;
vector<int> son[100005];//每个节点的儿子
int low[100005];

/*节点是否为根节点,根节点会乘所有点的逆元,因为乘与所有点的逆元后想要得到的是0,但是为1,所有特殊判断*/

bool root[100005];

int clock=0;

LL dfs(int rt,int fa)
{
    int lowu=pre[rt]=++clock;//dfs序 1开始
    tree[rt]=treeNumber;
    LL an=weight[rt];
    for(int i=0;i<G[rt].size();i++)
    {
        int x=G[rt][i];
        if(!pre[x])
        {
            int lowv=dfs(x,rt);
            lowu=min(lowu,lowv);
            son[rt].push_back(x);//存储子节点
            an=(an*val[x])%inf;
        }
        else if(pre[x]<pre[rt]&&x!=fa)
        {
            lowu=min(lowu,pre[x]);
        }
    }
    val[rt]=an;//存储val值
    return low[rt]=lowu;
}

LL inv(LL aaaa);//求逆元
LL pow_m(LL x,LL nnn);

int main()
{
    int T;
    RI(T);
    while(T--)
    {
        RII(n,m);
        for(int i=1;i<=n;i++)
            RI(weight[i]);
        clock=0;
        memset(pre,0,sizeof(pre));
        memset(root,0,sizeof(root));
        for(int i=0;i<m;i++)
        {
            int u,v;
            RII(u,v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        treeNumber=1;
        for(int i=1;i<=n;i++)
        {
            if(!pre[i])
            {
                dfs(i,0);
                treeValue[treeNumber]=val[i];
                root[i]=true;//i为跟
                treeNumber++;//树的个数
            }
        }
        LL sum=0;
        for(int i=0;i<treeNumber;i++)
                sum=sum+treeValue[i];
        ans=0;
        for(int i=1;i<=n;i++)
        {
            LL ans2=0;//变成连通块的子节点价值和
            ans1=treeValue[tree[i]];//除去ans2和i的价值和
            ans1=ans1*inv(weight[i])%inf;
            LL ss=sum-treeValue[tree[i]];//其余树的价值
            for(int j=0;j<son[i].size();j++)
            {
                int v=son[i][j];
                if(low[v]>=pre[i])
                {
                    ans1=ans1*inv(val[v])%inf;
                    ans2=ans2+val[v];
                }
            }
            if(root[i]) ans1=ans1-1;//根节点要减1
            ans=(ans+(((ans1+ans2+ss)%inf)*i)%inf)%inf;
        }
        printf("%I64d\n",ans);

        for(int i=0;i<=n;i++)
            {
                G[i].clear();
                son[i].clear();
            }

    }
    return 0;
}



LL pow_m(LL x,LL nnn)
{
    LL res=1;
    while(nnn>0){
        if(nnn&1) res=(res*x)%inf;
        x=((x%inf)*(x%inf))%inf;
        nnn>>=1;
    }
    return res;
}
LL inv(LL aaaa)
{
    return pow_m(aaaa,inf-2);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值