NOIP2017模拟赛(7) 总结

来源:互联网 发布:ubuntu如何删除软件 编辑:程序博客网 时间:2024/05/17 18:01

前言:第一题想复杂了,第二题犯了一个sb错误,第三题的话。。。


a 斯诺克

题目描述

考虑这样一个斯诺克球台,它只有四个袋口,分别在四个角上(如下图所示)。我们把所有桌子边界上的整数点作为击球点(除了4个袋口),在每个击球点我们可以以45度角击球。
这曾经是张图
每一个击球点你都可以向两个方向击球,例如像下图所示,从S点击球有两种路线。提供桌子的尺寸,你的任务是计算出有多少种不同的击球方式使得球能入袋。球可视为质点,且无任何阻力,反弹时无能量损失。


输入格式

仅一行,两个整数m和n,2 ≤ M,N ≤ 10^5,表示球台的长和宽。


输出格式

符合以上描述的击球路线。


这里写图片描述


解题思路(bfs+模拟)

这题我写了bfs,用了1h+从四个角去搜索,模拟各种情况。可是这样做共有16中恶心的分类讨论,很烦。
其实还可以直接从一个角出发模拟球前进的情况,然后根据四个角的对称关系,乘以4就可以了。这样做是不可能遇到环的。
其实认真看,就可以发现样例都是4的倍数。

最后,做题一定不要把题想得太复杂,写这题浪费了我很多时间,导致后面两题失分惨重。


代码

#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <algorithm>#include <cstring>#define N 100005using namespace std;int m, n;int head, tail;int Ans;bool f[4][N][4];struct Data{    int x, y, dir;    Data() {}    Data(int x, int y, int dir):x(x), y(y), dir(dir) {}}q[4*N*4];bool In(int x, int y){    bool f1 = (x == 0 && y == 0) || (x == 3 && y == 0);    bool f2 = (x == 0 && y == n) || (x == 1 && y == 0);    bool f3 = (x == 1 && y == m) || (x == 2 && y == n);    bool f4 = (x == 2 && y == 0) || (x == 3 && y == m);     return f1 || f2 || f3 || f4;}int main(){    freopen("a.in", "r", stdin);    freopen("a.out", "w", stdout);    scanf("%d%d", &m, &n);    f[0][0][1] = f[1][0][3] = f[2][n][4] = f[3][m][2] = true;    head = tail = 0;    q[tail++] = Data(0, 0, 1);    q[tail++] = Data(1, 0, 3);    q[tail++] = Data(2, n, 4);    q[tail] = Data(3, m, 2);    while(head <= tail){      int x = q[head].x, y = q[head].y, dir = q[head].dir;      head ++;      int nx, ny, ndir;      if(x == 0){        if(dir == 1){          if(y + m <= n)  nx = 2, ny = y + m, ndir = 2;          else  nx = 1, ny = n - y, ndir = 3;        }        else{          if(y >= m)  nx = 2, ny = y - m, ndir = 4;          else  nx = 3, ny = y, ndir = 1;        }      }      else if(x == 1){        if(dir == 3){          if(y + n <= m)  nx = 3, ny = y + n, ndir = 1;          else  nx = 2, ny = n - m + y, ndir = 4;        }        else{          if(y <= n)  nx = 0, ny = n - y, ndir = 3;          else  nx = 3, ny = y - n, ndir = 2;        }      }      else if(x == 2){        if(dir == 4){          if(y >= m)  nx = 0, ny = y - m, ndir = 3;          else  nx = 3, ny = m - y, ndir = 2;        }        else{          if(y + m <= n)  nx = 0, ny = y + m, ndir = 1;          else  nx = 1, ny = m - n + y, ndir = 4;        }      }      else{        if(dir == 1){          if(y + n <= m)  nx = 1, ny = y + n, ndir = 3;          else  nx = 2, ny = m - y, ndir = 2;        }        else{          if(y <= n)  nx = 0, ny = y, ndir = 1;          else  nx = 1, ny = y - n, ndir = 4;        }      }      if(!In(nx, ny) && !f[nx][ny][ndir]){        f[nx][ny][ndir] = true;        q[++tail] = Data(nx, ny, ndir);        Ans ++;      }    }    printf("%d\n", Ans);    return 0;}

b 完美排列

题目描述

排列,相信大家都很熟悉了,给你0 到N-1,共 N个数, 那么在排列中,每个数都要出现,而且只出现一次. 对于一个排列A, 有一个与之对应的孩子序列B, 其中B这样定义的:
1、B[0] = 0
2、B[i] = A[B[i-1]], 1 <= i <= N-1.
对于一个排列X, 如果它的孩子序列也是一个排列, 那么X称为“完美排列”。
看下面的例子,N=3时,有6个排列,其中{1, 2, 0}、{2, 0, 1}这两个排列的孩子序列也是一个排列,所有{1, 2, 0}、{2, 0, 1}是完美排列。
这里写图片描述
给你一个整数N,再给出它的一个排列P,要你找一个完美排列Q,使得Q跟P的差距dif最小,两个排列的差距dif是这样定义的:对于每个下标i,如果P[i] 不等于 Q[i],那么dif加1, 0 <= i < N; 如果Q不唯一,那么请输出孩子序列中字典序最小的Q. 也就是在所有满足题意的完美序列中,看哪个完美序列的孩子序列字典序最小,输出该完美序列。


输入格式

多组测试数据,第一行:一个整数ng,表示有1<=ng<=5组测试数据。
每组测试数据格式如下:
第一行:一个整数N, 表示排列的长度。1 <= N <= 50.
第二行:有N个整数,空格分开,表示给出的一个排列,就是对应的P[ ]数组。


输出格式

ng行,每行对应一组测试数据。 每一行,就是满足题意的Q, 共N个整数, 空格分开。


输入样例

2
3
2 0 1
6
4 0 5 2 1 3


输出样例

2 0 1
2 0 5 4 1 3


解题思路(找环+贪心)

这题算是我这场比赛最可惜的一道题。
首先题面长如狗,将题意转换,i向P[i]连边成环。考场上我已经将一个个环找出来了,然后也知道连边,甚至连贪心都写好了,结果一测试就爆零了。
原因竟然是:连边连多了!导致了字典序更优但dif增加了!n个环连成一个大的,应该连几条边呢?将环画横排在纸上的naive的我,天真的想,当然是(n-1)*2条了。结果,显然的,只需要n条边。
于是我贪心就理所应得的错了。
一开始错误的作法是在环上选最小的,再连其他的最小的,再连。。
实际上,只有第一个环(就是0在的那个环)需要向后判断是否连出去,其他的由于受到dif最小的限制只能直接走完整个环。贪心地按环上的最小值排序后,这就变成非常简单的模拟了。
ps:据说kxy和KsCla看错了题,将孩子序列字典序最小看成了Q的字典序最小。。。那个贪心就比较难写了。。。。


代码

#include <iostream>#include <cstdio>#include <cstdlib>#include <cstring>#include <cmath>#include <algorithm>#define N 60#define oo 0x7fffffffusing namespace std;int nG, Cnt;int n, Ring[N], P[N], Pre[N], Next[N];bool vis[N];int main(){    freopen("b.in", "r", stdin);    freopen("b.out", "w", stdout);    scanf("%d", &nG);    while(nG --){      scanf("%d", &n);      for(int i = 0; i < n; i++)  scanf("%d", &P[i]);      for(int i = 0; i < n; i++){        Next[i] = P[i];        Pre[P[i]] = i;      }      for(int i = 0; i < n; i++)  vis[i] = false;      Cnt = 0;      for(int i = 0; i < n; i++){        if(vis[i])  continue;        int Min = oo, j = i;        do{          vis[j] = true;          Min = min(Min, j);          j = Next[j];        }while(j != i);        Ring[++Cnt] = Min;      }      sort(Ring+1, Ring+Cnt+1);      int now = Ring[1];      while(Next[now] != Ring[1] && Next[now] < Ring[2])  now = Next[now];      now = Next[now];      Ring[1] = now;      for(int i = 1; i <= Cnt; i++){        now = Pre[Ring[i]];        Next[now] = Ring[i%Cnt+1];      }      for(int i = 0; i < n; i++)  printf("%d ", Next[i]);      printf("\n");    }    return 0; }

c 拼图

题目描述

FJ最近很烦恼,因为他正在寻找一些拼图块,这些拼图块其实可以拼成N个有顺序的完整的拼图。每个完整的拼图由若干个拼图块组成。
FJ希望把这些完整的拼图按拼出的顺序划分成M个集合,一个拼图集合由若干个完整的拼图组成,并且每个集合总的拼图块的数目不超过T。
并且,构成集合的拼图是不能交叉的,例如,当拼图1与拼图3被放在拼图集合1中之后,拼图2就只能放进拼图集合1或者不放进任何拼图集合。
FJ要找出划分成M个集合后,M个集合中最多能有多少个完整的拼图。


输入格式

第一行为三个整数,为N,M,T(1 <= N,M,T <= 1000)
第二行有N个数,按照拼出拼图的顺序给出N个拼图分别含有多少个拼图块(拼图块的个数是不超过T的正整数)。


输出格式

输出文件只有一个数字,为M个拼图集合最多包含的完整拼图个数


输入样例

6 2 4
1 1 3 1 2 2


输出样例

5

样例解释:
对于样例数据,1个可行的方案如下:
拼图集合1放拼图1和拼图2和拼图4,
拼图集合2放拼图5和拼图6
于是最多可以放5个拼图
并且显然不存在能够放5个以上拼图的方案

【数据规模】
对于30%的数据,有1<=N,M<=100
对于100%的数据,有1<=N,M<=1000


解题思路(背包dp+二元组)

这题一看就是类似于背包模型的dp。
如果记三维状态,无论是枚举上一个点,还是直接记多一维做背包,时间都是不可接受的O(n3)
于是有优化?
这题直接记二元组。类似于今年KOI Day1 T4那题dp,当时没做出来,后来听讲后又没有认真总结和修改,导致考试时完全没有印象。
所以总结经验,改正错误很重要啊。

然后知道记二元组后的我,一开始是这么想的,记f[i][j]={k,x}表示第到了第i个集合,第j个拼图,最后一个集合剩下k的空间,答案为x。由于我以前没听课,苦思冥想,结果KsCla告诉我这是不行的,因为二元组必须满足第一关键字的优先级大于第二关键字
就是说,我记得两个值k,x没有任何可比性,不能说x大,k小就认为比xk大的状态优。我恍然大悟,我们不能直接记答案。转而记f[i][j]={k,x},表示做到第i个拼图,选了j个(这是很常见的模型),然后用了k个集合,最后一个集合用了x的空间。这就很科学了,因为如果同样的状态,如果k小肯定更优,k一定才有比x的必要。所以这就是二元组的特点。

然后方程就很简单的背包模型,选or不选,然后转移。

f[i][j]=Min(f[i][j],Min(f[i1][j],put(f[i1][j1],a[i])));

put就是将i取走后的转移函数,详见代码。

一开始不取是{k=1,x=0},其他都设为很大,最后集合小于m的答案贡献给Ans就行了。
时间O(n2)

dp真是我的硬伤,遇见过的题目一定要熟练,主要还是时间不够,暴力都没写。


代码

#include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cmath>#include <cstring>#define N 1010using namespace std;int n, m, T, ans;int a[N];struct Data{    int x, y;    Data() {}    Data(int x, int y):x(x), y(y) {}}f[N][N];Data put(Data A, int v){    A.y += v;    if(A.y > T)  A.y = v, A.x ++;    return A;}Data Min(Data A, Data B){    if(A.x == B.x)  return A.y < B.y ? A : B;    return A.x < B.x ? A : B;}int main(){    freopen("c.in", "r", stdin);    freopen("c.out", "w", stdout);    scanf("%d%d%d", &n, &m, &T);    for(int i = 1; i <= n; i++)  scanf("%d", &a[i]);    for(int i = 0; i <= n; i++)     for(int j = 0; j <= n; j++)  f[i][j] = Data(m+1, 0);     for(int i = 0; i <= n; i++)  f[i][0] = Data(1, 0);    for(int i = 1; i <= n; i++)     for(int j = 1; j <= n; j++)       f[i][j] = Min(f[i][j], Min(f[i-1][j], put(f[i-1][j-1], a[i])));    ans = 0;    for(int i = 1; i <= n; i++)  if(f[n][i].x <= m)  ans = i;    printf("%d\n", ans);    return 0;}

总结

蒟蒻的我也要翻身啊,不行啊,还是太菜了。。。。
考试时间安排上很难受,调试程序的能力很弱,导致很多题想到了却调试不出来。
写程序一定要认真,提高码代码的能力实在太重要了。
脑子不要坏啊。


这里写图片描述

为什么天空如此蔚蓝
仿佛一点悲伤也不懂似的

原创粉丝点击