[解题报告] NOIP 2014 提高组Day2试题

[解题报告] NOIP 2014 提高组Day2试题

1.无线网络发射器选址(equation.cpp/c/pas)

【问题描述】
  随着智能手机的日益普及,人们对无线网的需求日益增大。某城市决定对城市内的公共场所覆盖无线网
  假设该城市的布局为由严格平行的 129 条东西向街道和 129 条南北向街道所形成的网格状,并且相邻的平行街道之间的距离都是恒定值 1 。东西向街道从北到南依次编号为0,1,2128, 南北向街道从西到东依次编号为 0,1,2128
  东西向街道和南北向街道相交形成路口,规定编号为x 的南北向街道和编号为y 的东西向街道形成的路口的坐标是 (x,y) 。在某些路口存在一定数量的公共场所
  由于政府财政问题,只能安装一个大型无线网络发射器。该无线网络发射器的传播范围是一个以该点为中心,边长为 2d 的正方形。传播范围包括正方形边界
  例如下图是一个 d=1 的无线网络发射器的覆盖范围示意图
  wifi
  现在政府有关部门准备安装一个传播参数为d的无线网络发射器,希望你帮助他们在城市内找出合适的安装地点,使得覆盖的公共场所最多
【输入】
  输入文件名为wireless.in
  第一行包含一个整数 d ,表示无线网络发射器的传播距离
  第二行包含一个整数n,表示有公共场所的路口数目
  接下来 n 行,每行给出三个整数x,y,k,中间用一个空格隔开,分别代表路口的坐标 (x,y) 以及该路口公共场所的数量,同一坐标只会给出一次
  
【输出】
  输出文件名为wireless.out
  输出一行,包含两个整数,用一个空格隔开,分别表示能覆盖最多公共场所的安装地点方案数,以及能覆盖的最多公共场所的数量

【数据说明】
  对于100%的数据, 1d20,1n20,0x128,0y128,0<k1,000,000

题目分析:
  水体,模拟即可
  注意:边界还是要判的
  爱怎么写怎么写

Code

#include <bits/stdc++.h>
using namespace std;
int MAP[129][129],sum=0,maxn=0;
int d,n,k;
int work(int x,int y) {
    int ans=0;
    for(int i=max(0,x-d);i<=min(128,x+d);i++) {
        for(int j=max(0,y-d);j<=min(128,y+d);j++) {
            ans+=MAP[i][j];
        }
    }
    return ans;
}
int main() {
    int x,y;
    scanf("%d%d",&d,&n);
    for(int i=1;i<=n;i++) {
        scanf("%d%d%d",&x,&y,&k);
        MAP[x][y]=k;
    }
    for(int i=0;i<=128;i++) {
        for(int j=0;j<=128;j++) {
            int ans=work(i,j);
            if(ans==maxn) {
                sum++;
            }else {
                if(ans>maxn) {
                    maxn=ans;
                    sum=1;
                }
            }
        }
    }
    printf("%d %d",sum,maxn);
}

2.寻找道路(road.cpp/c/pas)

【问题描述】
  在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件:

1.路径上的所有点的出边所指向的点都直接或间接与终点连通
2 .在满足条件1 的情况下使路径最短 

  注意:图G中可能存在重边和自环,题目保证终点没有出边
  请你输出符合条件的路径的长度

【输入】
  输入文件名为road.in
  第一行有两个用一个空格隔开的整数 n m,表示图有 n 个点和m条边
  接下来的 m 行每行2个整数x,y,之间用一个空格隔开,表示有一条边从点 x 指向点y
  最后一行有两个用一个空格隔开的整数 s,t ,表示起点为 s ,终点为t

【输出】
  输出文件名为road.out
  输出只有一行,包含一个整数,表示满足题目描述的最短路径的长度。如果这样的路径不存在,输出-1

【数据说明】
  对于30%的数据, 0<n100<m20
  对于60%的数据, 0<n1000<m2000
  对于100%的数据, 0<n10,0000<m200,0000<xystnxt

题目分析:
  题目一共给出了两个条件,先让我们看看畸形的第一条:

1.路径上的所有点的出边所指向的点都直接或间接与终点连通

  这什么鬼?路径上的所有点的出边所指向的点都直接或间接与终点连通
  也就是说,能够用的点(也就是最后在答案路径中的点)不仅仅要满足自己能到达终点,与自己直接相连的点也要能够到达终点
  那么我们怎么得到能够到达终点的点呢?简单,把边反向,从终点BFS能到达的就是能到达终点的点 ((φ(◎ロ◎;)φ)~晕)
  然后我们将不能到达的点反向边连接到的点都标记为不可用,因为它们不满足条件1
  那么我们就处理的到了所有能有的点!(条件1完成)
  那么条件2我们直接在这些能用的点中跑一边最短路就好了

Code:

#include <bits/stdc++.h> 
using namespace std;
bool go1[10010],go2[10010],dl[10010];
int head[3][10010],num[10010];
int tot=0;
queue <int> q;
struct Edge {
    int to;
    int next;
}edge[400010];
int read() {
    int ans=0,flag=1;
    char ch=getchar();
    while( (ch<'0' || ch>'9') && ch!='-' ) ch=getchar();
    if(ch=='-') flag=-1,ch=getchar();
    while(ch>='0' && ch<='9') ans=ans*10+ch-'0',ch=getchar();
    return ans*flag;
}
void addedge(int x,int y,int pd) {
    edge[++tot].to=y;
    edge[tot].next=head[pd][x];
    head[pd][x]=tot;
    return ;
}
int main() {
    memset(num,127,sizeof(num));
    int n=read(),m=read();
    for(int i=1;i<=m;i++) {
        int x=read(),y=read();
        addedge(x,y,1);
        addedge(y,x,2);
    }
    int s=read(),t=read();
    go1[t]=true;
    q.push(t);
    while(!q.empty()) {
        int now=q.front();
        q.pop();
        for(int i=head[2][now];i;i=edge[i].next) {
            int to=edge[i].to;
            if(!go1[to]) {
                q.push(to);
                go1[to]=true;
            }
        }
    }
    for(int i=1;i<=n;i++) {
        if(!go1[i]) {
            for(int j=head[2][i];j;j=edge[j].next) {
                go2[edge[j].to]=true;
            }
        }
    }
    while(!q.empty()) q.pop();
    q.push(s);num[s]=0;dl[s]=true;
    while(!q.empty()) {
        int now=q.front();
        q.pop();
        for(int i=head[1][now];i;i=edge[i].next) {
            int to=edge[i].to;
            if(!go2[to] && num[now]+1<=num[to]) {
                num[to]=num[now]+1;
                if(!dl[to]) {
                    q.push(to);
                    dl[to]=true;
                }
            }
        }
        dl[now]=false;
    }
    if(num[t]==2139062143) {
        printf("-1");
        return 0;
    }
    printf("%d",num[t]);
    return 0;
}

3.解方程(equation.cpp/c/pas)

【问题描述】
  已知多项式方程:
   a0+a1x+a2x2+...+anxn=0
  求这个方程在 [1,m] 内的整数解( n m均为正整数)

【输入】
  输入文件名为equation.in
  输入共 n+2
  第一行包含2个整数 nm ,每两个整数之间用一个空格隔开
  接下来的 n+1 行每行包含一个整数,依次为 a0,a1,a2,...,an

【输出】
  输出文件名为equation.out
  第一行输出方程在 [1,m] 内的整数解的个数
  接下来每行一个整数,按照从小到大的顺序依次输出方程在 [1,m] 内的一个整数解。

【数据说明】
  对于30%的数据, 0<n2|ai|100an0m100
  对于50%的数据, 0<n100|ai|10100an0m100
  对于70%的数据, 0<n100|ai|1010000an0m10000
  对于100%的数据, 0<n100|ai|1010000an0m1000000

题目分析:
  这一题怎么看起来像一个数学题呢?
  没错,它就是一个数学题
  
  30分做法:
  强行求解?你很强,看看 ai 的范围 |ai|1010000 long long long 都爆了,自求多福吧,不过骗骗分还是可以的,应该能过30%(蚊子腿也是肉啊)
  
  50分做法:
  高精吧,貌似是可以的
  
  70分做法(伪100分做法):
  既然 |ai| 范围这么大,我们就让它变小一点,我们将它 mod p (大素数),再将其求解,若modp后的算式通过枚举 m 使得结果为0,我们就将其看做正确答案
  但是这样的做法有一些问题:结果有可能错误!并且时间复杂度为O(nm),常数大一点,嘿嘿,看脸吧,也是有可能AC的
  
  100分做法:
  既然一个素数容易错,我们就开一堆!(也就5~6个吧)
  发现事实上在 mod p 意义下将x x+p 代入方程左侧得到答案是相同的,那么我们枚举答案时只用枚举区间 [1,k] 即可,又因为我们取了多个素数,正确性大大提高,所以我们可以将素数取小来减小 k (大概在1e4左右),所以我们的总复杂度就是O(ni=1primek[i]),除非你的代码真的丑的惨不忍睹,否则怎么样都可以过啦!

Code:

#include <bits/stdc++.h>
using namespace std;
int mod[5] ={7457,7829,8623,8971,9781},num[110][5],total,res[50000];
bool ans[50000][5],pd[110];
int number[10010];
bool read() {
    total=0;
    memset(number,0,sizeof(number));
    bool flag=1;
    char ch=getchar();
    while(ch>'9' || ch<'0' && ch!='-') ch=getchar();
    if(ch=='-') flag=0,ch=getchar();
    while(ch>='0' && ch<='9') number[++total]=ch-'0',ch=getchar();
    return flag;
}
int ksm(int a,int b,int c) {//快速幂
    int r=1;
    while (b) {
        if (b&1) r=r*a%mod[c];
        b>>=1;
        a=a*a%mod[c];
    }
    return r;
}
int main() {
    int n,m;
    long long sum;
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++) {//这是一个不忍直视的极其复杂的处理方式
        if(!read())
            pd[i]=1;
        for(int k=0;k<=4;k++)
            for(int j=1;j<=total;j++)
                num[i][k]=((num[i][k]*10%mod[k])+number[j])%mod[k];
    }
    for(int k=0;k<=4;k++) {
        for(int i=0;i<=min(mod[k],m);i++) {
            sum=0;
            for(int j=0;j<=n;j++) {
                if(pd[j])
                    sum=(long long)(sum-num[j][k]*ksm(i,j,k))%mod[k];
                else
                    sum=(long long)(sum+num[j][k]*ksm(i,j,k))%mod[k];
            } 
            if(sum<0) sum+=mod[k];
            if(sum==0) {
                ans[i][k]=1;
            } 
        }
    }
    for(int i=1;i<=m;i++) {
        res[++res[0]]=i;
        for(int k=0;k<=4;k++) {
            if(!ans[i%mod[k]][k]) {
                res[res[0]--]=0;
                break;
                //如果5个素数解都满足,我们就认定这是方程的一个解
            }
        }
    }
    for(int i=0;i<=res[0];i++) {
        printf("%d\n",res[i]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值