【五校联考6day2】san

题意

给你一个无向图,若两点之间的最短路为奇数,那么这叫做不和谐最短路。若一条不和谐最短路上包含地点C,则称它为“经过C 的不和谐最短路”经过它的不同的不和谐最短路数量。两条最短路不同,当且仅当它们途径的地点的序列不同。求每个点所经过的不和谐最短路的不同的条数。(注意最短路的两端点也算)

数据范围

对于50% 的数据,N ≤ 100;
对于全部数据,N ≤ 1000;M ≤ 3000,每条路的长度不超过1000。
保证图连通,无自环重边。

分析

50%做法

其实这道题的50分是很好做的,可以用 Floyd 去实现,附czy代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int INF=6000000;
const int N=100;

long long sum[N+1][N+1];
int dist[N+1][N+1];
long long f[N+1];
int n,m;

void floyd()
{
    for (int k=1;k<=n;k++)
        for (int i=1;i<=n;i++)
            if (i!=k)
                for (int j=1;j<=n;j++)
                    if (j!=k&&i!=j)
                        if (dist[i][j]>dist[i][k]+dist[k][j])
                        {
                            dist[i][j]=dist[i][k]+dist[k][j];
                            sum[i][j]=sum[i][k]*sum[k][j];
                        }
                        else
                            if (dist[i][j]==dist[i][k]+dist[k][j])
                                sum[i][j]+=sum[i][k]*sum[k][j];
}

int main()
{
    //freopen("san.in","r",stdin);
    //freopen("san.out","w",stdout);
    scanf("%d %d",&n,&m);
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
            dist[i][j]=INF;
        dist[i][i]=0;
        sum[i][i]=1;
    }
    for (int i=1,a,b,l;i<=m;i++)
    {
        scanf("%d %d %d",&a,&b,&l);
        dist[a][b]=dist[b][a]=l;
        sum[a][b]=sum[b][a]=1;
    }
    floyd();
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            for (int k=1;k<=n;k++)
                if (dist[j][i]+dist[i][k]==dist[j][k]&&dist[j][k]&1)
                    f[i]+=sum[j][i]*sum[i][k];
    for (int i=1;i<=n;i++)
        printf("%lld\n",f[i]);-
    return 0;
}

很好理解,就是求两点的最短路的方案数,然后再把其经过的点的答案加上即可。

100%做法

其实我们可以发现这题求的是最短路中点的个数,那么是否不是最短路的边可以删除呢?答案是肯定的,我们可以先从每个点去做一次最短路树,那么这棵树里面的每条边都是有“价值”的。这时我们再去想 DP 方程,我们设从枚举的起点到第i个点的最短路的方案( g[i,0..1],0..1 表示奇或偶),再统计从第i个点到他下面的点的最短路的方案( f[i,0..1]0..1 表示奇或偶)那么第i个点可以加的答案便是 f[i,1]g[i,0]+f[i,0]g[j,1] 。(最后不要忘记了还有两端点的方案统计)

易错点

在方程转移时我们若不打一次拓扑排序那么我们将会把一些转移转移错,比如说a->b,c->b,b->d时,我们会发现统计f数组时,若我们先走a->b,b->d,c->b,那么我们便把错误的信息b传给d。所以这便是排拓扑序的原因了(之前我就因为没有排,导致我调试了很久还是没有发现错误。。。)


procedure dfs(x:longint);
var i:longint;
begin
    i:=last[x];bd[x]:=true;
    while i<>0 do begin
        if not bd[d[i]] then
            dfs(d[i]);i:=next[i];
    end;
    pe[pe[0]]:=x;dec(pe[0]);//拓扑排序
end;

代码:

var
    bz,bd:array[1..1000] of boolean; oi:boolean;
    dis,pe:array[0..1000] of longint;
    an:array[1..1000] of int64;
    nex,las,b,o,d,next,last,r,v,a:array[1..6000] of longint;
    f:array[0..1,1..1000] of longint;
    g:array[1..1000,0..1] of longint;
    pre:array[1..1000,0..100] of longint;
    num,nu,n,m,x,y,z,i,j,p,h,k:longint;
procedure insert(x,y,z:longint);
begin
    inc(nu);
    b[nu]:=y;
    nex[nu]:=las[x];
    las[x]:=nu;v[nu]:=z;o[nu]:=x;
end;
procedure add(x,y,z:longint);
begin
    inc(num);
    d[num]:=y;
    next[num]:=last[x];
    last[x]:=num;r[num]:=z;
end;
procedure spfa(x:longint);
var l,r,p,now,i:longint;
begin
    fillchar(dis,sizeof(dis),$7f);
    fillchar(bz,sizeof(bz),false);
    l:=0;r:=1;dis[x]:=0;a[1]:=x;
    while l<r do begin
        inc(l);
        now:=a[l];
        p:=las[now];
        while p<>0 do begin
            if dis[b[p]]>dis[now]+v[p] then begin
               dis[b[p]]:=dis[now]+v[p];
               pre[b[p],0]:=0;
               inc(pre[b[p],0]);pre[b[p],pre[b[p],0]]:=p;
               if not bz[b[p]] then begin
                  bz[b[p]]:=true;inc(r);a[r]:=b[p];
               end;
            end else if (dis[b[p]]=dis[now]+v[p]) then begin
                 inc(pre[b[p],0]);pre[b[p],pre[b[p],0]]:=p;
           end;
            p:=nex[p];
        end;
        bz[now]:=false;
    end;
end;
procedure dfs(x:longint);
var i:longint;
begin
    i:=last[x];bd[x]:=true;
    while i<>0 do begin
        if not bd[d[i]] then
            dfs(d[i]);i:=next[i];
    end;
    pe[pe[0]]:=x;dec(pe[0]);
end;
begin
    readln(n,m);
    for i:=1 to m do begin
        readln(x,y,z);
        insert(x,y,z);
        insert(y,x,z);
    end;
    for i:=1 to n do begin
        num:=0;
        fillchar(last,sizeof(last),0);
        fillchar(next,sizeof(next),0);
        fillchar(f,sizeof(f),0);
        fillchar(g,sizeof(g),0);
        for j:=1 to n do f[0,j]:=1;
        spfa(i);
        for j:=1 to n do begin
            for k:=1 to pre[j,0] do add(o[pre[j,k]],b[pre[j,k]],v[pre[j,k]]);
            pre[j,0]:=0;
        end;
        fillchar(bd,sizeof(bd),false);pe[0]:=n;
        dfs(i);
        for j:=n downto 1 do begin
            h:=pe[j];
            p:=last[h];
            while p<>0 do begin
               inc(f[1,h],f[1-(r[p]mod 2),d[p]]);
               inc(f[0,h],f[r[p]mod 2,d[p]]);p:=next[p];
            end;
        end;
        g[i,0]:=1;
        for j:=1 to n do begin
            h:=pe[j];
            p:=last[h];
            while p<>0 do begin
                inc(g[d[p],1],g[h,1-(r[p]mod 2)]);
                inc(g[d[p],0],g[h,r[p] mod 2]);p:=next[p];
            end;
            if h<>i then begin
            inc(an[h],f[1,h]*g[h,0]+f[0,h]*g[h,1]);
            if dis[h] mod 2<>0 then inc(an[i],g[h,1]);
            end;
        end;
        x:=x;
    end;
    for i:=1 to n do writeln(an[i]);
end.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值