Noip 2015 senior ,The semi-finals,analysis (复赛题解)

来源:互联网 发布:吕宋岛 台风 知乎 编辑:程序博客网 时间:2024/06/11 11:20

My English teacher says that my English is not enough, so forgive me for working hard to analyse today in English.
Today I finished noip 2015 years problems of senior, quite a feeling.ok,sorry,I have exceeded my top of capacity,so I alter to Chinese.
好的,今天我很earnest很earnest做了这套题,并且很earnest很earnest的写了变量名,我是really想学好English的。
The first 题目,simulation,模拟!我们可以do it with enthusiasm. 我就不说blather了,直接附上代码,你们可以imitate my style of creating the name of variable。

#include<iostream>#include<cstdio>using namespace std;const int extend =40;inline int read(){     int data=0,w=1;     char ch=0;     while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();     if(ch=='-') w=-1,ch=getchar();     while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();     return data*w;}int data_number,x,y,i,judge_standard;int magic_square[extend][extend];void solve(){    while(i<data_number*data_number){          judge_standard = 0;          magic_square [x] [y] = ++ i;          if (x==1&&y!=data_number ) {judge_standard=1;x=data_number,y ++;}          if (y == data_number && x != 1 && judge_standard == 0) { judge_standard=2;y=1;x --; }          if (x == 1 && y == data_number && judge_standard == 0) { judge_standard=3;y=1;x=data_number;}          if (judge_standard == 0 ){x --,y ++;}          if (magic_square [x] [y])                switch ( judge_standard){                  case 0 : x += 2,y --;break;                  case 1 : x = 1; break;                  case 2 : x ++; break;                  case 3 : x=2,y=data_number;break;              }      }  }void lets_go_to_the_export(){    for (int j=1;j<= data_number;j++){          for ( int k = 1 ; k <= data_number ; k ++ )              printf ( "%d " , magic_square[j][k] );          putchar ( '\n' );      }  }int main(){    freopen("magic.in","r",stdin);    freopen("magic.out","w",stdout);    data_number=read();    x=1,y=data_number/2+1,i=0;      solve();    lets_go_to_the_export();    return 0;}

ok,the second 题目,读完题目过后,我们可以轻松distinguish这是一个ring。那么如何处理the smallest ring(草莽翻译),这是一个vital point。将题目translate一下,再形象的概括一下,最小环就相当于贪吃蛇,不断地elongate啊elongate,当chew到自己的body时,就结束。
那我们先一起来讲一讲最小环这个知识点吧。the smallest ring can be diveded 成两种class(类型),一种是有向的一种是无向的。先说有向吧,还是先说朴素算法。
令e(u,v)表示u和v之间的连边,再令min(u,v)表示,删除u和v之间的连边之后,u和v之间的最短路。最小环则是:min(u,v) + e(u,v),时间复杂度是EV2。其实都不用这种simple algorithm的,一般是用dijkstra或者是floyd。I recommend floyd,时间复杂度是O(n^3),不过如果大神会优先队列优化,那dijkstra不无不可。
https://vijos.org/p/1423,可以试试水,我来提供一个模板吧,O(∩_∩)O~。

#include <cstdio>#include <cstring>#include <algorithm>#define maxn 2000+10#define INF 1000000using namespace std;int n,m,a,b,c,dist[maxn][maxn],ans=INF,t[maxn];inline int read(){     int data=0,w=1;     char ch=0;     while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();     if(ch=='-') w=-1,ch=getchar();     while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();     return data*w;}int Min(int a,int b){    if(a>=b) return b;    else return a;}int main(){    n=read();    m=read();    for(int i=1;i<=n;i++) t[i]=read();    for(int i=1;i<=n;i++)        for(int j=i+1;j<=n;j++)            dist[i][j]=dist[j][i]=INF;    for(int i=1;i<=m;i++){        a=read();b=read();c=read();        dist[a][b]=Min(c+t[a],dist[a][b]);    }    for(int i=1;i<=n;i++)        for(int j=1;j<=n;j++)            for(int k=1;k<=n;k++)                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);    for(int i=2;i<=n;i++)     ans=min(ans,dist[1][i]+dist[i][1]);    if(ans<INF) printf("%d",ans);    else puts("-1");    return 0;}

then,无向图的最小环的求法不可能和有向图的求法一样, 因为在有向图中i 到j 和 j 到i 算是一个环,但在无向图中不是一个环,
如果直接用flody算法将会出错, 有向图的环可以为2个顶点,而无向图的环至少要三个顶点; 所以为了求无向图的最小环, 我们采用的principle是: 枚举最大环中的连接点,更新环的权重; 比普通Floyd多出来的部分,主要use到的原理是当处理到k时,所有以1 到k - 1为中间结点的最短路径都已经确定,则这时候的环为(i到j(1 < i, j <= k - 1)的最短路径) + 边(i, k) + 边(k, j)遍历所有的i, j找到上述式子的最小值即位k下的最小代价环 。you can try try。
now,我们来solve the message problem,我就把my thinking写在代码里面了哦。

#include<iostream>#include<cstdio>using namespace std;const int extend =200010;/*不妨设每个点 i 有一条边连向点 t[i], 我们可以枚举从点 origination 出发, 然后一直走下去, 并且每经过一个点就标记这个点. 如果我们走到了一个已经标记了的点,此时有两种情况 1. 这个点在我们从 s 出发走出的这条路径上, 这说明我们找到了一个环, 那么用这个环的大小更新答案即可; 2. 否则, 我们已经处理过这个点了, 直接退出, 枚举下一个起点.*/inline int read(){     int data=0,w=1;     char ch=0;     while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();     if(ch=='-') w=-1,ch=getchar();     while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();     return data*w;}int Min(int a,int b){    if(a>=b) return b;    else return a;}int is_used[extend],times[extend];int data_number,t[extend],ans = extend + 10000;void deep_First_Search(int origination){    int k = origination,auxliary = 0;    while(true){        times[origination] = auxliary++;        is_used[origination] = k;        origination = t[origination];        if(is_used[origination] > 0){            if(is_used[origination] == k){                ans = Min(ans,auxliary - times[origination]);                break;            }            else break;        }    }}void say_good_bye(int x){printf("%d",x);}int main(){    freopen("message.in","r",stdin);    freopen("message.out","w",stdout);    data_number=read();    for(int i=1;i<=data_number;i++) t[i]=read();    for(int i=1;i<=data_number;i++) if(is_used[i] == 0) deep_First_Search(i);    say_good_bye(ans);    return 0;}

大概就这样吧,图论is very essential。
最后一道题,我think:Facing the hopeless situation you should never say never, go ahead cheat for the score instead.
我只能cheat了,╮(╯▽╰)╭。全力去骗30分,就是当牌只有2,3,4张时。永远没有顺子的情况。先看代码吧。

#include<iostream>#include<iostream>#include<cstdio>#include<cstring>using namespace std;int data_number,n,Jack_card[20],receive1,receive2;inline int read(){     int data=0,w=1;     char ch=0;     while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();     if(ch=='-') w=-1,ch=getchar();     while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();     return data*w;}void readIn1(){    data_number=read();    n=read();   }void readIn2(){    for(int i=1;i<=n;i++){        receive1=read();        receive2=read();        if(receive1==0){            if(receive2==1) Jack_card[16]++;            else Jack_card[17]++;        }        else if(receive1==1 || receive1==2) Jack_card[receive1+13]++;        else Jack_card[receive1]++;    }}void Memset(){for(int i=3;i<=17;i++) Jack_card[i]=0;}void print(int x){printf("%d\n",x);}void elaborator_cheat(){    if(n==2){        for(int i=3;i<=17;i++){            if(Jack_card[i]==2 ||(Jack_card[16]==1 && Jack_card[17]==1) ){                print(1);                return ;            }               if(Jack_card[i]==1){                print(2);                return ;            }        }    }    else if(n==3){        for(int i=3;i<=17;i++){            if(Jack_card[i]==3){                print(1);                return ;            }               if(Jack_card[i]==2 ||(Jack_card[16]==1 && Jack_card[17]==1)){                print(2);                return ;            }               }        print(3);        return ;    }    else if(n==4){        int num_of_couple=0;        if(Jack_card[16]==1 && Jack_card[17]==1) num_of_couple++;        for(int i=3;i<=17;i++){            if(Jack_card[i]==4 || Jack_card[i]==3){                print(1);                return ;            }            if(Jack_card[i]==2) num_of_couple++;        }        if(num_of_couple==0){            print(4);            return ;        }         if(num_of_couple==1){            print(3);            return ;        }         if(num_of_couple==2){            print(2);            return ;        }     }}int main(){    freopen("landlords.in","r",stdin);    freopen("landlords.out","w",stdout);    readIn1();    while(data_number--){        Memset();        readIn2();        elaborator_cheat();    }    return 0;}

用尽毕生所学,枚举了2张,3张,4张的所有情况,I am very satisfied。
but now,我要学习一波正解,提高自己的代码及poker能力。
我research了一下big god的代码,都是优先搜索顺子,我思考了一下the reason,大概是有两个原因,一个是因为顺子最长,还有就是,只有顺子,会影响答案,所以搜索一下顺子的输出再剪枝。我的代码也许把你恶心到了,那下面这个代码,不是我写的了。想想吧。

#include<cstdio>#include<cstring>#include<climits>int t,n,x,y,a[5],b[14],maxx;int qiu(){    int tot=0;    memset(a,0,sizeof(a));    for(int i=0;i<=13;i++) a[b[i]]++;    while(a[4] && a[2]>1) a[4]--,a[2]-=2,tot++;    while(a[4] && a[1]>1) a[4]--,a[1]-=2,tot++;    while(a[4] && a[2]) a[4]--,a[2]--,tot++;    while(a[3] && a[2]) a[3]--,a[2]--,tot++;    while(a[3] && a[1]) a[3]--,a[1]--,tot++;    return tot+a[1]+a[2]+a[3]+a[4];}void dfs(int u)  //搜索顺子,二顺子,三顺子 {    if(u>=maxx) return;int kk=qiu();    if(u+kk<maxx) maxx=u+kk;    for(int i=2;i<=13;i++)    {        int j=i;        while(b[j]>=3 && j<=13) j++;        if(j-i>=2)          for(int v=i+1;v<=j-1;v++)          {            for(int vk=i;vk<=v;vk++) b[vk]-=3;            dfs(u+1);            for(int vk=i;vk<=v;vk++) b[vk]+=3;          }    }    for(int i=2;i<=13;i++)    {        int j=i;        while(b[j]>=2 && j<=13) j++;        if(j-i>=3)          for(int v=i+2;v<=j-1;v++)          {            for(int vk=i;vk<=v;vk++) b[vk]-=2;            dfs(u+1);            for(int vk=i;vk<=v;vk++) b[vk]+=2;          }    }    for(int i=2;i<=13;i++)    {        int j=i;        while(b[j]>=1 && j<=13) j++;        if(j-i>=5)          for(int v=i+4;v<=j-1;v++)          {            for(int vk=i;vk<=v;vk++) b[vk]--;            dfs(u+1);            for(int vk=i;vk<=v;vk++) b[vk]++;          }    }}int main(){    scanf("%d%d",&t,&n);    while(t--)    {        maxx=INT_MAX;        memset(b,0,sizeof(b));        for(int i=1;i<=n;i++)        {            scanf("%d%d",&x,&y);            if(x==1) x=13;            else if(x) x--;            b[x]++;        }        dfs(0);        printf("%d\n",maxx);    }    return 0;}


at last,我talk about 我这次考试的情况吧,(还没做day2呢),
the first: expect 100,in fact 100.
the second:expect 100,in fact 100.
the third :expect 30,in fact 30.
我去学习一下dijkstra ,我没有recommend it 就是因为 I have not control it。So。。。。。。。。。
Good Bye!

原创粉丝点击