欢迎使用CSDN-markdown编辑器

noip 2009 提高组题解
这一年据说一等奖要210分(重庆),今天考试被自己水过去了,感觉还是有点难度的,当然第三题spfa是我没想到,不然挺简单的,最后一题没做,因为觉得自己不懂数独,哪知道枚举都可以80分。
题目网上搜,我懒得复制了。
第一题。
潜伏者
这道题相当坑,坑的地方在于太简单引起了我的误会。潜伏在别国的间谍,破译密码,把其中25个字母都破译了,最后一个就一定破译了啊。动一动脑子就能把最后一个字母对应上嘛。所以我就自动帮这位潜伏者补了,哪知道这名潜伏者真的那么傻,不会对应,导致有一个点没过。我们先梳理一下题目中所提的要求,首先如果26个字母中有字母没有被提到,那我们直接输出Failed,第二种,26个字母中的几个被提到了两遍,且两遍对应的的“明文”不一样,那么我们输出Failed。第三种,假设提到了字母“A”,第一次提到的“明文”为“Q”,而当提到“Q”的明文时不是“A”而是其它的什么,可以输出Failed,第四种,即为没有矛盾,可以正常计算。#include<iostream>
using namespace std;
bool a[27];
int b[27];
int i,t,g,j;
int main(){
string m1,m2,m3;
memset(b,-1,sizeof(b));
cin>>m2>>m1>>m3;
t=m1.length();
for(i=0;i<t;i++){
b[m2[i]-64]=m1[i];}
for(i=1;i<27;i++)
if(b[i]==-1){
cout<<"Failed"<<endl;return 0;}
for(i=1;i<27;i++){
g=b[i];
for(j=1;j<27;j++)
if(b[j]==g&&i!=j){cout<<"Failed"<<endl;return 0;}
}
t=m3.length();
for(i=0;i<t;i++)
cout<<char(b[m3[i]-64]);
}

第二题主要考察数学知识。辗转相除法求最大公约数应该是大家都熟悉的,不再多说。
算法一:
在[1,b1]范围内穷举x,按题目要求判断即可,算法效率O(b1),期望得分50。
算法二:
因为gcd(x,a0)=a1,不妨设x=p*a1。
所以lcm(x,b0)=lcm(p*a1,b0)=p*a1*b0/gcd(p*a1,b0)=b1
可以得到p=b1* gcd(p*a1,b0)/a1/b0,枚举gcd(p*a1,b0)后可以求得p,继而求得x,检验gcd(p*a1,a0)是否为a1即可。
因为gcd(p*a1,b0)是b1的约数,所以枚举量为O(sqrt(b1)),最后复杂度为O(N*sqrt(b1)*log(b1)),期望得分100。
算法三:
因为lcm(x,b0)=x*b0/gcd(x,b0)=b1,所以x/gcd(x,b0)=b1/b0。不妨把b1/b0分解质因数,又一位x有约数a1,结合素数表进行搜索也能快速出结果。效率不好分析,期望得分100。

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
int a[500001];
int gcd(int a,int b){
    if(a%b==0) return b;
    return gcd(b,a%b);
}
int main(){
    int n;
    cin>>n;
    for(int k=1;k<=n;k++){
          int a0,a1,b0,b1,ans=0;
          scanf("%d %d %d %d",&a0,&a1,&b0,&b1);
          int p=b1/b0;
          a[0]=0;
          for(int i=1;i*i<=b0;i++)
          if(b0%i==0){
               if(gcd(i,p)==1) a[++a[0]]=i;
               if(b0/i!=i && gcd(b0/i,p)==1) a[++a[0]]=b0/i;
          }
          for(int i=1;i<=a[0];i++){
              int x=b0/a[i];
              if(gcd(x*p,a0)==a1) ans++;
          }
          printf("%d\n",ans);
    }
}

第三题
做这道题,首先要学过SPFA算法
SPFA是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
【算法思想】
初始将起点加入队列,每次从队列中取出一个元素,并对所有的与它相邻的点进行松弛计算,若某个相邻的点松弛成功,则将其入队。直到队列为空时算法结束。
这个算法简单的说就是队列优化的bellman-ford,利用每个点不会更新次数太多的特点发明此算法。
SPFA在形式上和广度优先搜索非常类似,但不同的是广搜中一个点出了队列就不可能重新进入队列,但SPFA中一个点可能在出队列之后再次被松弛,也就是说:一个点修改过其它的点之后,过段时间可能会获得更短的路径,需要再次被松弛。
SPFA算法的时间复杂度是O(kE),E是边数,k是常数,平均值是2.
【算法描述】
dis[i]记录从起点s到i点的最短路径,w[i][j]记录连接i,j的边的长度,pre[v]记录前趋。
SPFA有多种方法,一是深搜,二是广搜。目前多数采用广搜。我们也主要介绍广搜。广搜也有两种方法,一是邻接表解法,二是邻接矩阵解法。
SPFA的优化算法有两种:SLF和LLL:
SLF:Small Label First策略,设要加入的结点是j,队首元素为i,若dis[i]

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
const int MAXN = 500000;
int n,m,e = 1,ans,head[MAXN][2],MAX[MAXN],MIN[MAXN],Data[MAXN];
bool inq[MAXN];
queue<int>q;
struct node{
    int v,next;
}edge[MAXN];
void addedge(int u,int v){
    edge[e].next = head[u][0];edge[e].v = v;head[u][0] = e++;
    edge[e].next = head[v][1];edge[e].v = u;head[v][1] = e++;
}
void init(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++)  scanf("%d",&Data[i]),MIN[i] = 105;
    int u,v,k;
    for(int i = 1;i <= m;i++){
        scanf("%d%d%d",&u,&v,&k);
        addedge(u,v);
        if(k == 2) addedge(v,u);
    }
}

void spfa_1(){
    q.push(1);inq[1] = true;
    MIN[1] = Data[1];
    while(!q.empty()){
        int x = q.front();q.pop();
        inq[x] = false;
        for(int i = head[x][0];i;i = edge[i].next){
            int v = edge[i].v;
            if(MIN[v] > MIN[x] || MIN[v] > Data[v]){
               MIN[v] = min(MIN[x],Data[v]);
               if(!inq[v]) q.push(v),inq[v] = true;
            }
        }
    }

}
void spfa_2(){
    q.push(n);inq[n] = true;
    MAX[n] = Data[n];ans = max(ans,MAX[n] - MIN[n]);
    while(!q.empty()){
        int x = q.front();q.pop();
        inq[x] = false;
        for(int i = head[x][1];i;i = edge[i].next){
            int v = edge[i].v;
            if(MAX[v] < MAX[x] || MAX[v] < Data[v]){
               MAX[v] = max(MAX[x],Data[v]);
               ans = max(MAX[v] - MIN[v],ans);
               if(!inq[v]) q.push(v),inq[v] = true;
            }
        }
    } 
}

int main(){
    init();
    spfa_1();
    memset(inq,0,sizeof(inq));
    spfa_2();
    printf("%d\n",ans);
    return 0;
}

第四题
开始看到的时候,毫无思绪,考试的时候也没做,以为很难,关键是不知道数独,后来看了别人的分析,原来就是搜索啊,但是数据太大,绝对TLE,这个时候就要用一些特殊的技巧.先填可选数字最少的那一格吧。搜索的顺序也是这样,每次都搜能填个数最少的那一格,就可以过了。下面这个是模仿别人写的代码

#include<cstdio>
#include<algorithm>
using namespace std;
int ans=-1,a[10][10],b[10][10],c[10][10];
bool f1[10][10],f2[10][10],f3[10][10];
void dfs(int step){
  int i,j,k,s,xx,yy,zz=10;
  for(i=1;i<=9;i++)
    for(j=1;j<=9;j++)if(a[i][j]==0){
        for(s=0,k=1;k<=9;k++)
          if(!f1[c[i][j]][k] && !f2[i][k] && !f3[j][k])s++;
        if(s<zz)zz=s,xx=i,yy=j; 
      } 
  if(zz==10){
      for(k=0,i=1;i<=9;i++) for(j=1;j<=9;j++)  k+=a[i][j]*b[i][j];
      ans=max(ans,k);
      return;
    }
  if(zz==0)return;
  for(k=1;k<=9;k++)
    if(!f1[c[xx][yy]][k] && !f2[xx][k] && !f3[yy][k]){
        a[xx][yy]=k;
        f1[c[xx][yy]][k]=f2[xx][k]=f3[yy][k]=1;
        dfs(step+1);
        a[xx][yy]=0;
        f1[c[xx][yy]][k]=f2[xx][k]=f3[yy][k]=0; 
      }
}
int main(){
  int i,j,k;
  for(i=1;i<=9;i++)
    for(j=1;j<=9;j++){
        scanf("%d",&a[i][j]);
        b[i][j]=10-max(abs(i-5),abs(j-5));
        c[i][j]=(i-1)/3*3+(j-1)/3+1;
        f1[c[i][j]][a[i][j]]=1;
        f2[i][a[i][j]]=1;
        f3[j][a[i][j]]=1;
      }
  dfs(1);
  printf("%d\n",ans);
  return 0;
}

09年的题对我来说挺有难度的,并且发现了自己图论上的不足和一些畏难情绪。
还有,原来最后一道题不是真的最难。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值