2016.08.17【初中部 NOIP提高组 】模拟赛C题解

来源:互联网 发布:java异常中的finally 编辑:程序博客网 时间:2024/05/20 22:41

T1:

第一道题本以为很水,然后,我差点就挂了(注意还没挂)。只是,程序是压着950MS的线过的!数据太水没办法233333.

    我来介绍我的方法,和我的同学神犇们一种10MS水过的方法。

     解1:

             很明显,非正解的暴力方法,就是枚举位置(i,j),然后在用双重循环,枚举最近的。

             做出来我们发现。这样182^4一定是会超时的。让我们分析一下:一开始的二重循环是必须的,是必须枚举每个位置的,于是我们就想办法把后面的二重循环去掉,改成一个更加优化的方法。

             考试的时候,我脑子里冒出了2个想法,队列和前缀和。前缀和撇开,剩下一个一找到就是最优解的队列。所以,我们用队列实现第一题,从为“1”的点到每个点的位置扩四周散开来,就可以AC了,不过不优。

procedure doit;begin        fillchar(dis,sizeof(dis),7);//dis代表当前位置到"1"的最短路        for i:=1 to n do                for j:=1 to m do                        if a[i,j]='1' then                                bfs(i,j);end;
     解2:

              可以直接把1个位置丢进队列,然后再按照bfs做一次就可以了,这样不用bfs多个,同时bfs就可以了。

借鉴一位神犇的代码:

for i:=1 to n do        begin                for j:=1 to m do                begin                        read(ch[i,j]);                        if ch[i,j]='1' then                        begin                                inc(tail);                                d[tail,1]:=i;                                d[tail,2]:=j;//丢进队列                                bz[i,j]:=false;                                f[i,j]:=0;//f跟dis一样                        end;                end;                readln;        end;
    当然,解法1这么做,如果有BT数据几乎全都是1的话是过不了的。所以,我们加个优化,没碰到1才把当前元素丢进队列。

紧急报道:

     第一题还可以用DP来做:

           设f[i,j]表示这个位置离"1"的最近位置。我们对于开始并不是1个位置赋值为一个∞,是1的赋值为0,对于四周进行DP。

for k:=1 to n+m do       for i:=1 to n do        for j:=1 to m do          a[i,j]:=mini(a[i-1][j]+1,a[i,j-1]+1,a[i+1,j]+1,a[i,j+1]+1,a[i,j]);
       对于K,因为最多更新n+m次,至于为什么,我还是请教了一位匿名为波波的大神,原因是最多更新第一行,一行为m,第一列,一列为n次,所以为m+n

T2:

         第二道题很容易想到SPFA,以外星人霸占了的城市进行SPFA,最后模拟判断就可以了。不过如此也是压线过的。这里有个优化,如果当前每个城市都不适合居住,那么以后都是不适合居住的了,请选手们自行更改。虽然说数据水过了,但是,用邻接表存这个数据的时候,如果有BT数据数组还是会爆。所以可以使用另外的数据结构存。A了就不改了23333.

for j:=1 to k do        begin                readln(x);                head:=0;                tail:=1;                d[1]:=x;                fillchar(min,sizeof(min),7);                min[x]:=0;                while head<>tail do                begin                        inc(head);                        now:=d[head];                        for i:=1 to b[now,0] do                                if (min[b[now,i]]>min[now]+a[now,i]) then                                begin                                        min[b[now,i]]:=min[now]+a[now,i];                                        inc(tail);                                        d[tail]:=b[now,i];                                end;                end;                ans:=0;                for i:=1 to n do                        if (min[i]>=k1)and(not bz[i]) then                                inc(ans)                        else                                bz[i]:=true;                writeln(ans);        end;
T3:

       第三题膜拜了大神一次又一次还是不肯教我。幸亏一堆人高呼”克鲁斯卡尔大法“,所以水水的想出了正解,蒟蒻地打错了变量。要是我考试的时候能调试一下就好了。

          对于每2点间的数进行排序

for i:=1 to n-1 do                for j:=i+1 to n do                begin                        inc(k);                        b[k]:=sqrt(sqr(a[i,2]-a[j,2])+sqr(a[i,1]-a[j,1]));//精确的两点距离方法。                        c[k,1]:=i;                        c[k,2]:=j;                end;

按照克鲁斯卡尔做一次,如果连接了m-n条边,就把已经连通的取一个最大值,输出即可。
<span style="font-size:18px;">q(1,k);        for i:=1 to k do        begin                if getfather(c[i,1])<>getfather(c[i,2]) then//没联通就连通                begin                        he(c[i,1],c[i,2]);                        inc(sum);//联通了多少边                        if b[i]>max then max:=b[i];                end;                if sum=n-m then                begin                        writeln(max:0:2);                        halt;                end;        end;</span>
T4:

      60%并查集

      80%暴力枚举用dfs判断就可以了。

         100%其实一般做出80分都应该可以想到100分的算法,我们发现,80分的算法慢的原因的进行了多次dfs,但是细心的人可以发现有很多是不必要的,已经做过的。

          用f[x]表示以x为根节点,往下找他的儿子有多少个(包括自己),于是开始用递归预处理一下f数组(我递归没学好打了1个小时)

function dfs(x:longint):longint;var        i:longint;begin        bz[x]:=true;//到达过        for i:=1 to b[x,0] do//以x为根节点他的儿子个数        begin                if not bz[b[x,i]] then//如果当前点没到达过                        f[x]:=f[x]+dfs(b[x,i]);//递归,b[x,i]为以x根节点第i个儿子。        end;        f[x]:=f[x]+1;//加上自己        exit(f[x]);//路径压缩end;
然后一切就容易了。用样例来说,他的图是:


是这个样子的,可是这样不算是一棵标准的树,所以很难看出怎么递归。我改一下变成这样:

美术差生请别注意。绿色则是f数组,这就是样例的树。

假如我们去掉3这个位置,看他有多少个子节点,4他对应的儿子有2,8对应的儿子有5,如果就是这样结束就容易了。可是TM他还有爸爸,所以,我们就用总数减去当前需要删掉的3他的儿子,10-8=2.然后答案就是2,2,5,每个都符合条件就输出。

当然,怎么判断是不是父亲节点是个问题。

父亲节点的儿子数一定比自己这个儿子大,用这个判断就好。

for i:=1 to n do        begin                bb:=true;                for j:=1 to b[i,0] do//枚举儿子以及父亲,预处理处理过                        if f[b[i,j]]>f[i] then//如果当前点是父亲节点                        begin                                if f[1]-f[i]>n div 2 then//看看是否符合条件                                begin                                        bb:=false;                                        break;                                end;                        end                        else                        begin                                if f[b[i,j]]>n div 2 then//儿子节点就直接判断符合性                                begin                                        bb:=false;                                        break;                                end;                        end;                if bb then                begin                        writeln(i);                        kk:=true;                end;        end;        if not kk then                writeln('NONE');
总结:

     图论是我的弱项,看到这个我就怕了。其实没什么好怕的,多思考,多写肯定能想出来。

     当然这次比赛马虎的地方也很多,导致丢了200分。

     如果有题目不会打代码也要硬着头皮码下代码,说不定能骗出一点分。

      下次继续努力,好吧没下次,明天开始刷普及,不过持续更新C组的题解和题目,欢迎大神指导。

1 0
原创粉丝点击