NOI2008 Day 1 解题报告

这篇博客分享了作者在NOI2008竞赛中的解题报告,详细分析了两个问题:假面舞会和设计路线。假面舞会问题涉及寻找面具类别数量,通过DFS寻找环并计算最大公约数来确定答案。设计路线问题关注最小化交通不便值,通过树形动归策略找到最佳规划。博客提供了样例输入和输出,以及解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 NOI2008 Day 1 解题报告

      第一次做NOI的套题,觉得还是有很多可以进步的地方。

      不多说了,直接上题,代码写的丑,求各位神犇不要D

 

                              假面舞会 

【问题描述】
    一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为k (k3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第类面具的人才能看到戴第i+1 类面具的人的编号,戴第类面具的人能看到戴第类面具的人的编号。参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第2号面具的人看到了第号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了k3,所以你必须将这条信息也考虑进去。

【输入格式】
    输入文件 party.in 第一行包含两个整数n, m,用一个空格分隔,表示主办方总共准备了多少个面具,表示栋栋收集了多少条信息。接下来行,每行为两个用空格分开的整数a, b,表示戴第号面具的人看到了第号面具的编号。相同的数对a, b 在输入文件中可能出现多次。

【输出格式】
    输出文件 party.out 包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。如果无法将所有的面具分为至少类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个-1

【输入样例一】
    6 5
    1 2
    2 3
    3 4
    4 1
    3 5
【输出样例一】
    4 4

【输入样例二】
    3 3
    1 2
    2 1
    2 3

【输出样例二】
    -1 -1


【数据规模和约定】
    50%的数据,满足≤ 300, m ≤ 1000
    100%的数据,满足≤ 100000, m ≤ 1000000

 

思路:

第一个问题,什么是限制这个环长度的因素。当我们按照所给关系进行DFS遍历的时候,我们会发现有可能遍历到曾经访问过的点,那么这个时候,为了保证解的合法性,这个所遍历到的环的长度一定是原环长度的(也就是分K类的K值)的倍数。如果遍历到很多环呢,显然,原环的长度一定是所遍历到环长度的公约数。到此,算法思路就非常明显了。DFS找环,求所有环长的最大公约数T

这个题的情况比较多。首先,所有点不一定在一棵树上,那么我们需要遍历整个森林。如果T小于3,那么不存在合法解,如果T大于3,然后找T最小的大于3的约数(包括T本身)。还有一种特殊情况,当T=0的时候,原图不存在环,那么我们可以随意构造其长度,但是注意,这个长度的最大值一定要大于3,否则也是无解的情况。

题目相对较为简单,不过情况比较多,处理时必须得严谨

 

代码:

 program party;

  type

    point=^node;

    node=record

           data,num:longint;

           next:point;

         end;

  var

    i,j:longint;

    pp:array[1..100010]of boolean;

    n,m:longint;

    nu:array[1..100010]of longint;

    way,last:array[1..100010]of point;

    x,y:longint;

    min,max,leng:longint;

    t:longint;

  function gcd(a,b:longint):longint;

    begin

      if b=0 then exit(a)

             else exit(gcd(b,a mod b));

    end;

  procedure build(x,y,z:longint);

    begin

      if way[x]=nil then begin

                           new(way[x]);

                           way[x]^.data:=y;

                           way[x]^.next:=nil;

                           way[x]^.num:=z;

                           last[x]:=way[x];

                           exit;

                         end;

      new(last[x]^.next);

      last[x]^.next^.data:=y;

      last[x]^.next^.next:=nil;

      last[x]^.next^.num:=z;

      last[x]:=last[x]^.next;

    end;

  procedure dfs(v:longint);

    var

      p:point;

    begin

      pp[v]:=true;

      p:=way[v];

      if max<nu[v] then max:=nu[v];

      if min>nu[v] then min:=nu[v];

      while p<>nil do

        begin

          if not pp[p^.data] then begin

                                    nu[p^.data]:=nu[v]+p^.num;

                                    dfs(p^.data);

                                  end

                             else begin

                                    if t=0 then t:=abs(nu[v]+p^.num-nu[p^.data])

                                           else t:=gcd(t,abs(nu[v]+p^.num-nu[p^.data]));

                                  end;

          p:=p^.next;

        end;

    end;

  begin

    assign(input,'party.in');

    assign(output,'party.out');

    reset(input);

    rewrite(output);

    readln(n,m);

    for i:=1 to n do

      way[i]:=nil;

    for i:=1 to m do

      begin

        readln(x,y);

        build(x,y,1);

        build(y,x,-1);

      end;

    fillchar(pp,sizeof(pp),false);

    leng:=0;

    for i:=1 to n do

      if not pp[i] then begin

                          min:=maxlongint;

                          max:=0;

                          dfs(i);

                          leng:=leng+max-min+1;

                        end;

    if t=0 then begin

                  if leng<3 then writeln(-1,' ',-1)

                           else writeln(leng,' ',3);

                end

           else begin

                  if t<3 then writeln(-1,' ',-1)

                         else begin

                                for i:=3 to t do

                                  if t mod i=0 then break;

                                writeln(t,' ',i);

                              end;

                end;

    close(input);

    close(output);

 end.

 


 

 

                              设计路线 

【问题描述】

 

    Z国坐落于遥远而又神奇的东方半岛上,在小Z的统治时代公路成为这里主要的交通手段。Z国共有n座城市,一些城市之间由双向的公路所连接。非常神奇的是Z国的每个城市所处的经度都不相同,并且最多只和一个位于它东边的城市直接通过公路相连。Z国的首都是Z国政治经济文化旅游的中心,每天都有成千上万的人从Z国的其他城市涌向首都。为了使Z国的交通更加便利顺畅,小Z决定在Z国的公路系统中确定若干条规划路线,将其中的公路全部改建为铁路。 我们定义每条规划路线为一个长度大于1的城市序列,每个城市在该序列中最多出现一次,序列中相邻的城市之间由公路直接相连(待改建为铁路)。并且,每个城市最多只能出现在一条规划路线中,也就是说,任意两条规划路线不能有公共部分。当然在一般情况下是不可能将所有的公路修建为铁路的,因此从有些城市出发去往首都依然需要通过乘坐长途汽车,而长途汽车只往返于公路连接的相邻的城市之间,因此从某个城市出发可能需要不断地换乘长途汽车和火车才能到达首都。

   我们定义一个城市的“不便利值”为从它出发到首都需要乘坐的长途汽车的次数,而Z国的交通系统的“不便利值”为所有城市的不便利值的最大值,很明显首都的“不便利值”

为0。小Z想知道如何确定规划路线修建铁路使得Z国的交通系统的“不便利值”最小,以及有多少种不同的规划路线的选择方案使得“不便利值”达到最小。当然方案总数可能非常大,小Z只关心这个天文数字mod Q后的值。 注意:规划路线1-2-3和规划路线3-2-1是等价的,即将一条规划路线翻转依然认为是等价的。两个方案不同当且仅当其中一个方案中存在一条规划路线不属于另一个方案。

【输入格式】

   第一行包含三个正整数N、M、Q,其中N表示城市个数,M表示公路总数,N个城市从1~N编号,其中编号为1的是首都。Q表示上文提到的设计路线的方法总数的模数。接下来M行,每行两个不同的正数ai、bi (1 ai , bi ≤ N)表示有一条公路连接城市ai和城市bi。

   输入数据保证一条公路只出现一次。

【输出格式】

   应包含两行。第一行为一个整数,表示最小的“不便利值”。

   第二行为一个整数,表示使“不便利值”达到最小时不同的设计路线的方法总数mod Q的值。如果某个城市无法到达首都,则输出两行-1。

【输入样例】

  5 4 100 

  1 2 

  4 5 

  1 3 

  4 1 

【输出样例】

  

  10 

【样例说明】

   以下样例中是10种设计路线的方法:

(1)   4-5 

(2)  1-4-5 

(3)  4-5, 1-2 

(4) 4-5, 1-3 

(5) 4-5, 2-1-3 

(6) 2-1-4-5 

(7) 3-1-4-5 

(8)  1-4 

(9)  2-1-4 

(10) 3-1-4 

【数据规模和约定】

   对于20%的数据,满足N,M ≤ 10。 对于50%的数据,满足N,M ≤200。

   对于60%的数据,满足N,M ≤5000。

   对于100%的数据,满足1 ≤ N,M ≤100000,1 ≤ Q ≤ 120000000。

 

 

思路:第一问很好去做,影响不便利值只有可能是三叉树。第二问显然是树形动归。先说第二问。

      F[i,k,0]表示节点i,不满意度为k,不修铁路的方案数。

      F[i,k,1]表示节点i,不满意度为k,修1条铁路的方案数。

      F[i,k,2]表示节点i,不满意度为k,修2条铁路的方案数。

      ji的子节点

      T1=F[j,k,0]+F[j,k,1]              ->(i,j)修铁路

      T2=F[j,k-1,0]+F[j,k-1,1]+F[j,k-1,2]    ->(i,j)不修铁路

      加法原理

      则

          F[i,k,2]=F[i,k,2]*T2+F[i,k,1]*T1

          F[i,k,1]=F[i,k,1]*T2+F[i,k,0]*T1

          F[i,k,0]=F[i,k,0]*T2

      连乘是因为每个节点不一定只有一个子节点,而每个子节点的情况又是独立的,所以可以用乘法原理来做。

      那么算法也十分明显了。

代码:

program design;

  type

    point=^node;

    node=record

           data:longint;

           next:point;

         end;

  var

    i,j:longint;

    n,m,q:longint;

    way,last:array[1..100100]of point;

    pp:array[1..100100]of boolean;

    ans:longint;

    x,y:longint;

    f:array[1..100100,0..12,0..2]of int64;

  procedure build(x,y:longint);

    begin

      if way[x]=nil then begin

                           new(way[x]);

                           way[x]^.data:=y;

                           way[x]^.next:=nil;

                           last[x]:=way[x];

                           exit;

                         end;

      new(last[x]^.next);

      last[x]^.next^.data:=y;

      last[x]^.next^.next:=nil;

      last[x]:=last[x]^.next;

    end;

  procedure dfs(v:longint);

    var

      p:point;

      now:longint;

      t1,t2:int64;

    begin

      pp[v]:=true;

      for i:=0 to ans do

        begin

          f[v,i,0]:=1;

          f[v,i,1]:=0;

          f[v,i,2]:=0;

        end;

      p:=way[v];

      while p<>nil do

        begin

          now:=p^.data;

          if not pp[now] then begin

                                dfs(now);

                                for i:=0 to ans do

                                  begin

                                    t1:=f[now,i,0]+f[now,i,1];

                                    if i=0 then t2:=0

                                           else t2:=f[now,i-1,0]+f[now,i-1,1]+f[now,i-1,2];

                                    if t1 mod q<>0 then t1:=t1 mod q;

                                    if t2 mod q<>0 then t2:=t2 mod q;

                                    f[v,i,2]:=f[v,i,2]*t2+f[v,i,1]*t1;

                                    f[v,i,1]:=f[v,i,1]*t2+f[v,i,0]*t1;

                                    f[v,i,0]:=f[v,i,0]*t2;

                                    for j:=0 to 2 do

                                      if f[v,i,j] mod q<>0 then f[v,i,j]:=f[v,i,j] mod q;

                                  end;

                              end;

          p:=p^.next;

        end;

    end;

 

  begin

    assign(input,'design.in');

    assign(output,'design.out');

    reset(input);

    rewrite(output);

    readln(n,m,q);

    if m<>n-1 then begin

                     writeln(-1);

                     writeln(-1);

                     close(input);

                     close(output);

                     halt;

                   end;

    for i:=1 to n do

      way[i]:=nil;

    for i:=1 to m do

      begin

        readln(x,y);

        build(x,y);

        build(y,x);

      end;

    ans:=-1;

    repeat

      inc(ans);

      fillchar(pp,sizeof(pp),false);

      dfs(1);

until f[1,ans,0]+f[1,ans,1]+f[1,ans,2]>0;  //用这个方法使得第一问和第二问同时做出。不过这样会带来一些麻 

                                        烦若mod q之后为0,它会误认为没有解,所以需要在每次取模的

                                        时候判断是否为0

    writeln(ans);

    writeln((f[1,ans,0]+f[1,ans,1]+f[1,ans,2])mod q);

    close(input);

    close(output);

 end.


 

 

                                                                                                          志愿者招募

    申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要天才能完成,其中第天至少需要Ai 个人。 布布通过了解得知,一共有类志愿者可以招募。其中第类可以从第Si 天工作到第Ti 天,招募费用是每人Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。

Input

第一行包含两个整数N, M,表示完成项目的天数和可以招募的志愿者的种类。 接下来的一行中包含个非负整数,表示每天至少需要的志愿者人数。 接下来的行中每行包含三个整数Si, Ti, Ci,含义如上文所述。为了方便起见,我们可以认为每类志愿者的数量都是无限多的。

Output

仅包含一个整数,表示你所设计的最优方案的总费用。

Sample Input

 

3 3

2 3 4

1 2 2

2 3 5

3 3 2

Sample Output

14

HINT

   招募第一类志愿者3名,第三类志愿者4名 30%的数据中,≤ N, M ≤ 10≤ Ai ≤ 10; 100%的数据中,≤ ≤ 1000≤ ≤ 10000,题目中其他所涉及的数据均 不超过2^31-1

 

思路:

这道题是D1里最难的一道,当时赛场上只有高逸涵一人AC,题目难度由此可见。

我们先分析样例。

X[i]为第i类志愿者招募个数。P[i]为第i天的志愿者个数,那么显然:

P[1]=X[1]>=2

P[2]=X[1]+X[2]>=3

P[3]=X[2]+X[3]>=4

为了消除不等号,我们引入Y[i]来表示不等式左右之差,那么:

P[1]=X[1]=2+Y[1]

P[2]=X[1]+X[2]=3+Y[2]

P[3]=X[2]+X[3]=4+Y[3]

我们再添两个式子

(1) P[0]=0=0;

(2) P[1]=X[1]=2+Y[1]

(3) P[2]=X[1]+X[2]=3+Y[2]

(4) P[3]=X[2]+X[3]=4+Y[3]

(5) P[4]=0=0;

我们用用25式分别减去前式得

P[1]-P[0]=X[1]=2+Y[1]

P[2]-P[1]=X[2]=1+Y[2]-Y[1]

P[3]-P[2]=-X[1]+X[3]=1+Y[3]-Y[2]

P[4]-P[3]=-X[2]-X[3]=-4-Y[3]

然后去掉左边P的表达式:

X[1]=2+Y[1]

X[2]=1+Y[2]-Y[1]

-X[1]+X[3]=1+Y[3]-Y[2]

-X[2]-X[3]=-4-Y[3]

整理得

X[1]-2-Y[1]=0

X[2]-1-Y[2]+Y[1]=0

-X[1]+X[3]-1-Y[3]+Y[2]=0

-X[2]-X[3]+4+Y[3]=0

看到这个我们想到了什么?没错,流量平衡!

对每个等式作为一个点,所有正值都为流入该点的流量,所有负值都为流出该点的流量。因为每个志愿者所能工作的日期为连续的一段,所以这样相邻相减可以保证每个X[i]只会出现两次,而且一次为负,一次为正,这便是这个题的妙处所在。

首先建立源点S,汇点T。如果i式出现了X[k]j式出现了-X[k],那么从j建一条边指向i,容量为无限大,费用为找第k类志愿者的费用。如果i式出现了Y[k]j式出现了-Y[k],那么从j建一条边指向i,容量为无限大,费用为0。如果出现了正常数,那么从S建一条费用为0,容量为这个正常数的边指向这个等式的点;如果出现了负常数,那么从这个等式的点建一条指向T的费用为0,容量为这个负常数绝对值的边。至此,建图完毕。剩下所要做的就是跑一边费用流就OK了。从构建方式来看,边数应为2n+m对于n+3个顶点来说为稀疏图,所以用普通SPFA来找最小增广路就OKzwk费用流一般跑稠密图。(如果我说的不对,请各位神犇告诉我。。。)

代码:

program employee;

  const

    maxn=100000;

    maxinf=maxlongint div 2;

  type

    point=^node;

    node=record

           data,cost,flow:longint;

           next,back:point;

         end;

  var

    i,j:longint;

    pp:array[0..1010]of boolean;

    quene:array[0..100010]of longint;

    d:array[0..1010]of longint;

    n,m:longint;

    way,fa:array[0..1010]of point;

    a,pre:array[0..1010]of longint;

    l,r,c:array[0..10010]of longint;

    s,t:longint;

    ans:longint;

    ff:longint;

  function spfa:boolean;

    var

      i,head,tail,now:longint;

      p:point;

    begin

      for i:=s to t do

        begin

          d[i]:=maxinf;

          pp[i]:=false;

        end;

      d[s]:=0;

      pp[s]:=true;

      head:=0;

      tail:=1;

      quene[1]:=s;

      repeat

        head:=(head+1)mod maxn;

        now:=quene[head];

        p:=way[now];

        while p<>nil do

          begin

            if (p^.flow>0)and(d[p^.data]>d[now]+p^.cost) then

               begin

                 d[p^.data]:=d[now]+p^.cost;

                 pre[p^.data]:=now;

                 fa[p^.data]:=p;

                 if not pp[p^.data] then

                   begin

                     pp[p^.data]:=true;

                     tail:=(tail+1) mod maxn;

                     quene[tail]:=p^.data;

                   end;

               end;

            p:=p^.next;

          end;

         pp[now]:=false;

       until head>=tail;

       if d[t]=maxinf then exit(false)

                      else exit(true);

    end;

  procedure change;

    var

      i,min:longint;

    begin

      min:=maxinf;

      i:=t;

      repeat

        if fa[i]^.flow<min then min:=fa[i]^.flow;

        i:=pre[i];

      until i=s;

      i:=t;

      repeat

        dec(fa[i]^.flow,min);

        inc(fa[i]^.back^.flow,min);

        i:=pre[i];

      until i=s;

      ans:=ans+d[t]*min;

    end;

  procedure add(x,y,z,d:longint);

    var

      p:point;

    begin

       new(p); p^.data:=y; p^.flow:=d; p^.cost:=z; p^.next:=way[x]; way[x]:=p;

       new(p); p^.data:=x; p^.flow:=0; p^.cost:=-z;p^.next:=way[y]; way[y]:=p;

       way[x]^.back:=way[y];

       way[y]^.back:=way[x];

    end;

  begin

    assign(input,'employee.in');

    assign(output,'employee.out');

    reset(input);

    rewrite(output);

    readln(n,m);

    fillchar(a,sizeof(a),0);

    s:=0;

    t:=n+2;

    for i:=1 to n+1 do

      way[i]:=nil;

    for i:=1 to n do

      begin

        read(a[i]);

        add(i,i+1,0,maxinf);

      end;

    for i:=1 to n+1 do

      begin

        ff:=a[i-1]-a[i];

        if ff<0 then add(i,t,0,-ff)

                else add(s,i,0,ff);

      end;

    for i:=1 to m do

      begin

        readln(l[i],r[i],c[i]);

        add(r[i]+1,l[i],c[i],maxinf);

      end;

    ans:=0;

    while spfa do

      change;

    writeln(ans);

    close(input);

    close(output);

  end.


 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值