NOIP2015刷题报告

Ezio_0420

2018-04-20 15:13:06

Personal

# Day 1 ## [神奇的幻方](https://www.luogu.org/problemnew/show/P2615) 幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行、每列及两条对角线上的数字之和都相同。 当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,最后输出即可。 ```cpp #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; } ``` ------------ ##[信息传递](https://www.luogu.org/problemnew/show/P2661) 有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一遍更新最小值即可。 ```cpp #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; } ``` ------------ ##[斗地主](https://www.luogu.org/problemnew/show/P2668) 牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。 现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。 需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。 具体规则如下: ![](https://cdn.luogu.com.cn/upload/pic/1827.png) 输入输出格式 输入格式: 第一行包含用空格隔开的2个正整数T和n,表示手牌的组数以及每组手牌的张数。 接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。 输出格式: 共T行,每行一个整数,表示打光第i手牌的最少次数。 ![](https://cdn.luogu.com.cn/upload/pic/1828.png) 解法: 模拟 这题数据纯随机,所以按要求模拟即可,但是这引起了一部分人的不爽,所以有了数据增强版[斗地主增强版](https://www.luogu.org/problemnew/show/P2540) ------------ ------------ # Day 2 ## [跳石头](https://www.luogu.org/problemnew/show/P2678) 一年一度的“跳石头”比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 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 解法: 二分 我们只需二分枚举最短跳跃距离,每次大模拟检查是否成立即可。 ```cpp #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; } ``` ------------