关闭

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

标签: NOIP2009位运算图论SPFAascii
286人阅读 评论(0) 收藏 举报
分类:

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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:22832次
    • 积分:1858
    • 等级:
    • 排名:千里之外
    • 原创:163篇
    • 转载:4篇
    • 译文:0篇
    • 评论:33条
    文章分类
    最新评论