NOIP2015刷题报告
Ezio_0420
2018-04-20 15:13:06
# 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;
}
```
------------