NOIP2009解题报告(C/C++)(潜伏者)(Hankson的趣味题)(最优贸易)(靶形数独)

来源:互联网 发布:linux mint 18.2 安装 编辑:程序博客网 时间:2024/06/04 23:21

2017.3.4的校内赛

这一次我们进行了NOIP2009的真题测试,算是我这几次以来最好的一次,这归功于第三题的思路较为简单和第二题的暴力能够过1/2的点。但是这不意味着这一套题很简单,与之想法,这套题稍有不慎就会有过失性失分,比如第一题容易看漏条件。下面我们来看看:

1.潜伏者

解题报告:

这道题利用筒的思路,搞一个“字典”,将一个字母的序号(ASCII码减去’a’)作为下标,将与之相对应的字母的序号存储到数组中就可以了,需要注意的是,除了不能A数组中的对应关系必须是一对一的以外,B数组也是这样。
下面来看看代码:

#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;const int N=100;const int M=26;char s1[N+5],s2[N+5],s3[N+5];int len1,len2,len3;int map[M+1];int main(){    freopen("spy.in","r",stdin);    freopen("spy.out","w",stdout);    memset(map,-1,sizeof(map));    scanf("%s",s1);    scanf("%s",s2);    scanf("%s",s3);    len1=strlen(s1),len2=strlen(s2),len3=strlen(s3);    for(int i=0;i<=len1-1;i++)    {        if(map[s1[i]-'A']!=-1&&map[s1[i]-'A']!=s2[i]-'A')//没有一个下标对应两个字母的情况         {            printf("Failed");            return 0;        }        map[s1[i]-'A']=s2[i]-'A';    }    for(int i=0;i<=24;i++)    for(int j=i+1;j<=25;j++)    if(map[i]==map[j])//没有两个字母对应一个下标的情况     {        printf("Failed");        return 0;    }    for(int i=0;i<=25;i++)//26个字母要集齐     if(map[i]==-1)    {        printf("Failed");        return 0;    }    for(int i=0;i<=len3-1;i++)    printf("%c",map[s3[i]-'A']+'A');    return 0;}

2.Hankson的趣味题

解题报告:

这道题我先是想到了用暴力的方法,因为他的数据范围设这样的:

可见,我们通过最小公倍数(gcd)和最大公因数(lcm)的求解方法,可以通过枚举(枚举到b1)的方法没举出所有可能的值。

这样的暴力法不难想出,我就不细讲了。这种算法可以得50分。
那么正解是怎么样呢?我们通过观察gcd和lcm的性质,可以发现,我们只需要枚举到sqrt(b1)就可以利用其性质推出其余可能的解,而这样就不会超时。代码如下:

#include<stdio.h>#include<math.h>#include<string.h>#include<algorithm>using namespace std;const int N=10000;int t,a0,a1,b0,b1;int num,tot;int gcd(int a,int b){    return b==0?a:gcd(b,a%b);}int lcm(int a,int b){    return (a/gcd(a,b))*b;}bool judge(int i){    return(i%a1!=0)?0:(gcd(i/a1,a0/a1)==1&&gcd(b1/i,b1/b0)==1);//保证满足题意 }int main(){    freopen("son.in","r",stdin);    freopen("son.out","w",stdout);    scanf("%d",&t);    while(t--)    {        tot=0;        scanf("%d%d%d%d",&a0,&a1,&b0,&b1);        for(int i=1;i*i<=b1;i++)//不能用sqrt,会超时         {            if(b1%i==0)            {                if(judge(i))tot++;                if(i*i!=b1&&judge(b1/i))tot++;//推出其余可能的解             }        }        printf("%d\n",tot);    }    return 0;}

(此外,这道题也可以用整数的唯一分解定理来做,想来也不难理解)

3.最优贸易


解题报告:

这道题是我为数不多的能够AC得图论的题,其思路非常简洁明了:就是用两次广搜(或者说是SPFA)。第一次我们从头到尾更新出最小的点权,第二次我们从尾到头更新出最大的点权。再将路过每个点时所经过的最大最小点权的两个数组一一枚举,找到最大差值就可以了。

#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;const int M=500000;const int N=100000;int n,m;int head1[N+5],head2[N+5],num=0;int w[N];int minn[N+5],maxx[N+5];struct edge{    int next1,next2,u,v;    edge(){next1=-1;next2=-1;   }};struct edge ed[2*M+5];void build(int u,int v){    num++;    ed[num].u=u;    ed[num].v=v;    ed[num].next1=head1[u];    head1[u]=num;    ed[num].next2=head2[v];//为了满足从未到头找的性质,我们必须建一个反过来的图     head2[v]=num;}void bfs1()//从头到尾找最小 {    int front=0,rear=1;    int state[N],flag[N];    memset(flag,0,sizeof(flag));    memset(minn,0x7f,sizeof(minn));    state[rear]=1,minn[1]=w[1],flag[1]=1;    do    {        front++;        int u=state[front];        flag[u]=0;        for(int i=head1[u];i!=-1;i=ed[i].next1)        {            int v=ed[i].v;            if(minn[v]>minn[u]||w[v]<minn[v])            {                minn[v]=min(w[v],minn[u]);                if(flag[v]==0)state[++rear]=v;                flag[v]=1;            }        }    }while(front<rear);}void bfs2()//从尾到头找最大 {    int front=0,rear=1;    int state[N],flag[N];    memset(flag,0,sizeof(flag));    memset(maxx,-1,sizeof(maxx));    state[rear]=n,maxx[n]=w[n],flag[n]=1;    do    {        front++;        int u=state[front];             for(int i=head2[u];i!=-1;i=ed[i].next2)        {            int v=ed[i].u;            if(maxx[u]>maxx[v]||w[v]>maxx[v])            {                maxx[v]=max(w[v],maxx[u]);                if(flag[v]==0)state[++rear]=v;                flag[v]=1;            }        }        flag[u]=0;    }while(front<rear);}int main(){    freopen("trade.in","r",stdin);    freopen("trade.out","w",stdout);    memset(head1,-1,sizeof(head1));    memset(head2,-1,sizeof(head2));    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++)    scanf("%d",&w[i]);    for(int i=1;i<=m;i++)    {        int u,v,k;        scanf("%d%d%d",&u,&v,&k);        if(k==1)build(u,v);        if(k==2)        {            build(u,v);            build(v,u);        }    }    bfs1();    bfs2();    int ans=-1;    for(int i=1;i<=n;i++)//一一枚举找最大值     ans=max(ans,maxx[i]-minn[i]);    printf("%d",ans);    return 0;}

此外,据说这道题也可以用Tarjan+缩点的方法做出来,反正我是不会的~

4.靶形数独



解题报告:

这道题我刚开始想用DP来做,但是很快我认识到了自己的幼稚。由于这道题数据较小(宫格数较小),可以直接用搜索来解决。但只用搜索的话注定会超时,所以我们还需要位运算。下面我们看一看大神的代码:

#include <iostream>#include <cstdlib>#include <cstdio>using namespace std;const int mlen = 10;int a[mlen][mlen],b[mlen][mlen];int row[mlen],lie[mlen],line[mlen],ma[mlen];int f[512],ans,node[mlen],cnt[mlen];void init() {    for(int i = 0; i < 9; i++) b[i][0] = b[0][i] = b[8][i] = b[i][8] = 6;    for(int i = 1; i < 8; i++) b[i][1] = b[1][i] = b[7][i] = b[i][7] = 7;    for(int i = 2; i < 7; i++) b[i][2] = b[2][i] = b[6][i] = b[i][6] = 8;    for(int i = 3; i < 6; i++) b[i][3] = b[3][i] = b[5][i] = b[i][5] = 9;    b[4][4] = 10; int t = 0;    for(int i = 1, j = 0; i <= 511; i <<= 1, j++) f[i] = j;    for(int i = 0; i < 9; i++)        for(int j = 0; j < 9; j++) {            scanf("%d",&a[i][j]);            if(a[i][j] != 0){                row[i] |= 1<<j;                t = 1<<(a[i][j]-1);                if((lie[i]&t) || (line[j]&t) || (ma[i/3*3+j/3]&t)) { printf("-1\n"); exit(0); }                lie[i] |= t; line[j] |= t;                ma[i/3*3+j/3] |= t;            }else cnt[i]++;        }}inline void sore() {    int nowans = 0;    for(int i = 0; i < 9; i++) for(int j = 0; j < 9; j++) nowans += a[i][j]*b[i][j];    if(ans < nowans) ans = nowans;}void dfs(int t) {    int pos,k;    if(t == 9) { sore(); return; }    int i = node[t];    if(!cnt[i]) { dfs(t+1); return; }    cnt[i]--;    int p = (511^row[i])&(-(511^row[i]));    row[i] |= p;    int j = f[p];    pos = 511^(lie[i]|line[j]|ma[i/3*3+j/3]);    while(pos > 0) {        k = pos&(-pos); pos ^= k;        lie[i] |= k; line[j] |= k;        ma[i/3*3+j/3] |= k; a[i][j] = f[k]+1;        dfs(t);        lie[i] ^= k; line[j] ^= k;        ma[i/3*3+j/3] ^= k;    }    cnt[i]++; row[i] ^= p;}int main() {    freopen("sudoku.in","r",stdin);    freopen("sudoku.out","w",stdout);    init();    for(int i = 0; i < 9; i++) node[i] = i;    for(int i = 0; i < 9; i++)        for(int j = i+1; j < 9; j++)            if(cnt[node[i]] > cnt[node[j]]) node[i] ^= node[j], node[j] ^= node[i], node[i] ^= node[j];    int tot = 0;    while(!cnt[node[tot]]) tot++;    dfs(tot);    if(!ans) { printf("-1\n"); return 0; }    printf("%d\n",ans);    return 0;}

以上
2017.3.9

0 0
原创粉丝点击