NOIP2015刷题报告

2018-04-20 15:13:06


Day 1

神奇的幻方

幻方是一种很神奇的NN矩阵:它由数字1,2,3,……,NN构成,且每行、每列及两条对角线上的数字之和都相同。

当N为奇数时,我们可以通过以下方法构建一个幻方:

首先将1写在第一行的中间。

之后,按如下方式从小到大依次填写每个数K(K=2,3,…,N*N):

1.若(K−1)在第一行但不在最后一列,则将K填在最后一行,(K−1)所在列的右一列;

2.若(K−1)在最后一列但不在第一行,则将K填在第一列,(K−1)所在行的上一行;

3.若(K−1)在第一行最后一列,则将K填在(K−1)的正下方;

4.若(K−1)既不在第一行,也不在最后一列,如果(K−1)的右上方还未填数,则将K填在(K−1)的右上方,否则将K填在(K−1)的正下方。

现给定N请按上述方法构造N*N的幻方。

1<=N<=39且N为奇数。

输入输出格式 输入格式: 输入文件只有一行,包含一个整数N即幻方的大小。

输出格式: 输出文件包含N行,每行N个整数,即按上述方法构造出的N*N的幻方。相邻两个整数之间用单个空格隔开。

解法:

模拟

x和y分别记录当前填数的格子的横坐标和纵坐标,然后根据题目要求每次变换x和y,最后输出即可。

#include<cstdio>
int a[50][50];
int main(){## 
    int n;
    scanf("%d",&n);
    a[1][(n+1)/2]=1;
    int x=1,y=(n+1)/2;
    for(int i = 2;i<=n*n;i++){
        if(x==1&&y!=n) {
        x=n;y++;a[x][y]=i;continue;}
        if(y==n&&x!=1) {
        y=1;x--;a[x][y]=i;continue;}
        if(x==1&&y==n) {
        x++;a[x][y]=i;continue;}
        if(x!=1&&y!=n){
            if(a[x-1][y+1]==0) {
            a[x-1][y+1]=i;x--;y++;continue;}
            x++;a[x][y]=i;continue; 
        }
    }
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=n;j++) printf("%d ",a[i][j]);
        printf("\n");
        }
        return 0;
}

信息传递

有n个同学(编号为1到n)正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为i的同学的信息传递对象是编号为Ti同学。

游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息,但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?

n ≤ 200000

输入输出格式 输入格式: 输入共2行。

第1行包含1个正整数n表示n个人。

第2行包含n个用空格隔开的正整数T1,T2,……,Tn其中第i个整数Ti示编号为i

的同学的信息传递对象是编号为Ti的同学,Ti≤n且Ti≠i

数据保证游戏一定会结束。

输出格式: 输出共 1 行,包含 1 个整数,表示游戏一共可以进行多少轮。

解法:

tarjan 或 拓扑排序+DFS

这题要求图中最小环的长度,可以当成tarjan的模板题来写,缩点后所有点权不为1的点中的最小值即为答案。

当然因为一个人只能将信息告诉另外一个人,所以图中都是单向边,那么我们也可以用拓扑排序先把所有不在环上的点标记一下,对于剩下的在环上的点,我们只需要每个环DFS一遍更新最小值即可。

#include<cstring>
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<climits>
using namespace std;
const int N=2e5+3;
int ans=INT_MAX;
int n,m,cnt,head[N],in[N],vis[N];
struct tu{
    int ne,to;
}e[N];
void add(int u,int v){
    cnt++;
    e[cnt].to=v;
    e[cnt].ne=head[u];
    head[u]=cnt;
}
queue<int>q;
void topsort(){
    for(int i = 1;i<=n;i++){
        if(!in[i]) q.push(i);
    }
    while(!q.empty()){
        int i = q.front();
        vis[i]=1;   
        q.pop();
        for(int j = head[i];j;j=e[j].ne){
            int v = e[j].to;
            in[v]--;
            if(!in[v]) q.push(v);
        }
    }
}
void dfs(int s,int t,int len){
    if(s==t&&len){
        ans=min(ans,len);
        return;
    }
    for(int j = head[t];j;j=e[j].ne){
        int v = e[j].to;
        if(!vis[v]) dfs(s,v,++len),vis[v]=1;
    }
    return;
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i<=n;i++){
        int u;
        scanf("%d",&u);
        add(i,u);
        in[u]++;
    }
    topsort();
    for(int i = 1;i<=n;i++){
        if(in[i]){
            dfs(i,i,0);
        }
    }
    printf("%d",ans);
    return 0;
}

斗地主

牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。

现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。

需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。

具体规则如下:

输入输出格式 输入格式: 第一行包含用空格隔开的2个正整数T和n,表示手牌的组数以及每组手牌的张数。

接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。

输出格式: 共T行,每行一个整数,表示打光第i手牌的最少次数。

解法:

模拟

这题数据纯随机,所以按要求模拟即可,但是这引起了一部分人的不爽,所以有了数据增强版斗地主增强版



Day 2

跳石头

一年一度的“跳石头”比赛又要开始了!

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终 点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达 终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳 跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能 移走起点和终点的岩石)。

输入输出格式 输入格式: 输入文件名为 stone.in。

输入文件第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终 点之间的岩石数,以及组委会至多移走的岩石数。

接下来 N 行,每行一个整数,第 i 行的整数 Di(0 < Di < L)表示第 i 块岩石与 起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同 一个位置。

输出格式: 输出文件名为 stone.out。 输出文件只包含一个整数,即最短跳跃距离的最大值。

1≤n≤1000,1≤m≤200,1≤k≤m

解法:

二分

我们只需二分枚举最短跳跃距离,每次大模拟检查是否成立即可。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int l,n,m;
int a[50001];
bool f(int mid){
    int dis=mid,sum=0;
     for(int i=1;i<=n+1;i++){
         if(dis>=a[i]-a[i-1]) dis-=a[i]-a[i-1];
         else sum++,dis=mid;
     }
     if(sum>(n-m)) return 1;
     else return 0;
}
int main(){
    scanf("%d%d%d",&l,&n,&m);
    int s=1,e,midd;
    a[n+1]=l,e=l;
    for(int i = 1;i<=n;i++) scanf("%d",&a[i]);
    while(s<e){
        midd=(s+e)/2; 
        if(f(midd)) s=midd+1;
        else e=midd;
    }
    printf("%d",s);
    return 0;
}