巨额奖金 Award

巨额奖金
Award/.IN/.OUT/.PAS/.EXE
问题描述
NJ 市的快速发展得益于其便捷的交通。可是,随着经济的发展,大量的人进入NJ 市,
NJ 市的交通也承受着巨大的压力。现在,NJ 市正在筹划建设一个新型的交通枢纽,从而
减轻交通的压力。
NJ 市包含n 个区,有些区之间有双向的干道存在。新型交通枢纽建设在这些干道的基
础上,将其中的部分干道改进为新型干道。改进后,干道能承受的压力可以比原来增加几十
倍。为了和谐发展,在新型的交通枢纽建成后,要求任何两个区之间都可以只通过新型干道
(直接或间接地)连接。政府已经预测出每条干道改进为新型干道的费用。政府希望建设新
型交通枢纽的总费用最小,并以巨额奖金向市民征集方案。政府很快发现费用最小的方案不
一定唯一,所以决定将奖金平分给每一种方案的第一个设计者,即如果一个人设计的费用是
最小的而且前面没人和他设计出一模一样的方案,则他可获奖。

栋栋被奖金深深的吸引,准备设计一种方案。可是,他发现方案可能会很多,如果最后
获奖者太多,巨额的资金分到每个人头上的也不会太多。所以他决定先算一下可行的方案数
是多少。
输入
输入的第一行包含两个数n (1 ≤ n ≤ 100),m (1 ≤ m ≤ 1,000),分别表示该市有
多少个区和有多少条干道。接下来m 行,每行三个数ai、bi、ci (1 ≤ ai, bi ≤ n, 1 ≤ ci ≤
1,000,000,000),表示ai 区和bi 区之间有一条干道,如果改进需要ci 的费用。
输入保证任何两个区之间至多有一条干道。对于任何一个费用c,不会有超过10 条干
道的费用都是c。
输出
输出费用最小的方案有多少种。由于答案可能很大,你只要输出方案数除以31011 的
模即可。


具体来说,就是求图的最小生成树个数

做法是先生成一遍最小生成树,记录树中各种权值的边有多少个

然后得出考虑这些边的生成树个数

某论文上有讲基尔霍夫矩阵求生成树个数的方法,十分高端

对于这道题,同一费用边不超过10个

则所以重新做一遍prim,

对于每种权值不同的边进行一遍搜索看有多少种可行解

乘起来以后随意找一种方案合并,继续寻找下一种权值不同的边

证明虽觉屌,但不明

证明:

我们用反证法来证明:假如对于图G的某一个最小生成树,其子图Gi不是一棵树,而是若干个连通块,那么由于图G是连通的,所以我们可以确定Gi中的连通块必然通过若干条其他边连起来。那么此时我们将子图中某条可以将两个连通块连接起来的边加入图中(这条边必定可以找到,因为图G’中,Gi为连通分量),这样图G的最小生成树就会形成一个环,并且这个环上必定有权值比c大的边(因为必定有边将Gi与其他子图连接起来),这时我们去掉权值比c大的边,可以得到一颗新的生成树,而且这颗生成树比最小生成树还要小,这样就产生了矛盾。



program award;
type
  lin=record
        u,v,c:longint;
      end;
const
  maxn=31011;
var
  can:boolean;
  now,ans,count,s,tot,n,m,i,j,k,x,y:longint;
  line:array [0..1001] of lin;
  cop,father,dl:array [0..101] of longint;

procedure swap (var a,b:lin);
var
  i:lin;
begin
  i:=a;
  a:=b;
  b:=i;
end;

procedure qsort (s,e:longint);
var
  i,j,k:longint;
begin
  if s>=e then exit;
  i:=s;
  j:=e;
  k:=line[(s+e) div 2].c;
  while i<=j do
    begin
      while line[i].c<k do inc(i);
      while line[j].c>k do dec(j);
      if i>j then break;
      swap(line[i],line[j]);
      inc(i);
      dec(j);
    end;
  qsort(s,j);
  qsort(i,e);
end;

function root (now:longint):longint;
begin
  if father[now]=0 then exit(now);
  father[now]:=root(father[now]);
  exit(father[now]);
end;

function mother (now:longint):longint;
begin
  if father[now]=0 then exit(now);
  exit(mother(father[now]));
end;

procedure search (k,s,e,left:longint);
var
  i,x,y:longint;
begin
  if k>e then
    begin
      if not can then
        begin
          cop:=father;
          can:=true;
        end;
      inc(now);
      exit;
    end;
  if e-k+1>left then search(k+1,s,e,left);
  if left>0 then
    begin
      x:=mother(line[k].u);
      y:=mother(line[k].v);
      if x=y then exit;
      father[y]:=x;
      search(k+1,s,e,left-1);
      father[y]:=0;
    end;
end;

begin
  assign(input,'award.in');
  reset(input);
  assign(output,'award.out');
  rewrite(output);
  read(n,m);
  if m<n-1 then
    begin
      writeln(0);
      close(input);
      close(output);
      halt;
    end;
  if m=n-1 then
    begin
      writeln(1);
      close(input);
      close(output);
      halt;
    end;
  for i:=1 to m do
    read(line[i].u,line[i].v,line[i].c);
  qsort(1,m);
  tot:=0;
  for i:=1 to m do
    begin
      x:=root(line[i].u);
      y:=root(line[i].v);
      if x<>y then
        begin
          inc(tot);
          dl[tot]:=line[i].c;
          father[x]:=y;
        end;
      if tot=n-1 then break;
    end;
  if tot<n-1 then
    begin
      writeln(0);
      close(input);
      close(output);
      halt;
    end;
  fillchar(father,sizeof(father),0);
  ans:=1;
  i:=1;
  k:=1;
  while i<=m do
    begin
      while line[i].c<>dl[k] do inc(i);
      s:=i;
      while (line[i].c=dl[k]) do inc(i);
      count:=1;
      while (k<tot)and(dl[k+1]=dl[k]) do
        begin
          inc(count);
          inc(k);
        end;
      inc(k);
      now:=0;
      can:=false;
      search(s,s,i-1,count);
      father:=cop;
      ans:=(ans*now) mod maxn;
      if k>tot then break;
    end;
  writeln(ans);
  close(input);
  close(output);
end.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值