[NOIP2017模拟]路径统计

2017.11.7 T2 2045

样例数据1
输入

4
2 1
3 2
1 3
4 3

输出

12

样例数据2
输入

2
1 1
2 2

输出

1000000005

分析:考场上看出来这明显是个缩点的题嘛,然后枚举每条边,使用的次数就是边两边的能到达的点的个数。结果发现实现很难,考完发现我没能根据输入格式得出每个点只有一个出度……现在这张图应该是很多个(很多条链指向一个环)的图,得到这个结论会简单许多,但是实现还是比较复杂。
因为每个点只有一个出度,入度却是不确定的,所以把边反着建就可以有唯一性,再来计算每条边的贡献。

代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int N=500005,p=1e9+7;
int n;
int go[N],len[N],t[N];
int tot,first[N],nxt[N],to[N],w[N];
int idx,top,cnt,dfn[N],low[N],stk[N];
int num,id[N],size[N],dis[N];
long long sum[N],val[N],ans;
vector<int>f[N];
bool exist[N];

inline void add(int x,int y,int z)
{
    nxt[++tot]=first[x],first[x]=tot,to[tot]=y,w[tot]=z;
}

inline void tarjan(int u)
{
    dfn[u]=low[u]=++idx;
    stk[++top]=u,exist[u]=true;
    int v=go[u];
    if(!dfn[v])
    {
        tarjan(v);
        low[u]=min(low[u],low[v]);
    }
    else 
        if(exist[v])low[u]=min(low[u],dfn[v]);
    if(low[u]==dfn[u])
    {
        ++num;//强联通编号
        v=stk[top];
        while(v!=u)
        {
            id[v]=num,size[num]++;
            f[num].push_back(v);//放到vector中
            top--,exist[v]=false,v=stk[top];
        }
        id[v]=num,size[num]++;
        f[num].push_back(v);
        top--,exist[v]=false;
    }
}

inline void calc(int i)//预处理从环上每个点走一圈的每一种贡献
{
    int u;
    reverse(f[i].begin(),f[i].end());//把vector反转按dfs序排序
    for(int j=0;j0]]=(val[f[i][0]]+1ll*(size[i]-j)*len[f[i][j]]%p)%p;
    for(int j=1;j1]]+sum[i]-1ll*size[i]%p*len[f[i][j-1]]%p)%p;
    for(int j=0;jinline void dfs(int u,int cnt)
{
    exist[u]=true;
    ans=((ans-1ll*(n-cnt)*size[u])%p+p)%p;//把不能到的点都-1
    for(int e=first[u];e;e=nxt[e])
    {
        int v=to[e];
        if(!exist[v])dfs(v,cnt+size[v]);
        ans=(ans+1ll*size[v]*cnt%p*w[e]%p)%p;//计算链上边的贡献
        size[u]+=size[v];
    }
}

int main()
{
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);

    int i,y,z;
    n=getint();
    for(i=1;i<=n;i++)
        go[i]=getint(),len[i]=getint();
    for(i=1;i<=n;i++)
        if(!dfn[i])tarjan(i);//tarjan缩点,求每个点的size
    for(i=1;i<=n;i++)
        if(id[go[i]]==id[i])sum[id[i]]=(sum[id[i]]+len[i])%p;//计算环的路径总长度
        else add(id[go[i]],id[i],len[i]);//如果不是环就反向建边
    for(i=1;i<=num;i++)
    {
        if(size[i]==1)continue;
        ans=(ans+1ll*size[i]*(size[i]-1)/2%p*sum[i]%p)%p;//计算环自己走自己的贡献
        calc(i);
    }
    memset(exist,0,sizeof(exist));
    for(i=1;i<=num;i++)
        if(!exist[i])dfs(i,size[i]);
    for(i=1;i<=n;i++)
        if(id[go[i]]!=id[i])ans=(ans+1ll*size[id[i]]*val[go[i]]%p)%p;//找到链接环的地方,加上从环那个点走一圈的贡献
    cout<<(ans%p+p)%p;
    return 0;
}

本题结。

你可能感兴趣的:(连通性问题(tarjan等))