题解
今天开局状态不行..NOIP2013day2的题..模拟赛输入输出打错了….’w’,‘W’在DEVc++下真的看不出来好嘛…然后果断地爆了个零。
第一题——积木大赛(block)
【题目描述】
- 告诉你一串序列,题目当中就是积木。告诉你可以连续的 [L..R] [ L . . R ] (包括 L,R L , R )放上1块积木。至少要多少次从全是0放完。
第一眼看到这道题就想到这个不是区间修改和查询么!首选线段树,次选分块!
看我十分钟给你敲完!!!
但想想不太对..第一题怎么会用这么复杂的数据结构呢!自己举了几个小数据发现了规律:
- 首先要把地基铺好(就是每一块都可以放上最小值,这都是可以一次性做完的。接下来就要去找有多少需要分开做的。
- 手动枚举发现..在连续递增下是需要不断增加次数的,因为区间在减小而高度在增加的时候不断上升,就需要增加次数。
- 而连续递减则不需要,因为在连续的区间内递增时已经把这里的做掉了,就不需要增加了。这样子一来就可以线性地解决。复杂程度 O(n) O ( n ) 。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
void fff(){
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
}
const int MAXN=101000;
int n,ans,minn;
int a[MAXN];
int main(){
fff();
scanf("%d",&n);
ans=0,minn=999999;
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
minn=min(minn,a[i]);
}
ans=minn;
for (int i=1;i<=n;i++){
if(a[i]>minn){
if(i==1){
ans+=a[1]-minn;
continue;
}
if(a[i]>a[i-1]) ans+=a[i]-a[i-1];
}
}
cout<<ans;
return 0;
}
- 但其实这道题真的可以用线段树去做(handsometothepolice大佬就手打线段树),还有很多玄妙的算法比如差分也可以去做。
第二题——花匠(flower)
【题目描述】
- 还是给你一段序列,让你去掉一些数,使得保留下来的新序列符合:
- 在任意偶数位上的比相邻的数都要大
- 在任意偶数位上的比相邻的数都要小
- 以上条件符合任意一个就可以了。输出保留的最大序列的长度
- 这道题最开始贪了一把..其实也不算贪,类似于正解但没处理好..只拿了八十分。
先说我开局的思路。
- 我们可以看得出,如果把第一个条件的第一位数拿去后就变成了第二个条件。那么我们要尽量让这个变得更加长,就先进行第一种情况的模拟,再看看第一位或者最后一位能不能进行添上一位似的这个序列更长,符合第二个条件。
- 而对第一个条件的求得,我们就要让偶数位上的数尽量大,奇数位上的数尽量小就可以达到了。再来枚举头和尾就可以获得答案。
- 但是很不幸啊我打的时候只判断了头没有判断尾orz。八十分草草收场。
这个是正解。
- 可以通过画图看出,所需要的序列是一个驼峰式的,那么就可以进行两种规律的dp。方程如下:
if(a[i]>a[i-1])
sta[i][0]=sta[i-1][1]+1;
else
sta[i][0]=sta[i-1][0];
if(a[i]<a[i-1])
sta[i][1]=sta[i-1][0]+1;
else
sta[i][1]=sta[i-1][1];
突然断电了…只保存到这里……那只好从这里继续打下去。
- 解释下这个方程:
- sta是保存第一和第二种情况的方式的长度。
- 如果当前这个数值比前一个数值要大,那么符合第一种形式,就接着第二种情况的数量下去。
- 对于第二种情况也是同理的。然后最后比较到n的时候的两种长度的大小,
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
void fff(){
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
}
const int MAXN=101000;
int n;
int a[MAXN];
int sta[MAXN][2],ans=0;
bool flag=true;//falg dizeng
int main(){
// fff();
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sta[1][0]=sta[1][1]=1;
for (int i=2;i<=n;i++){
if(a[i]>a[i-1]) sta[i][0]=sta[i-1][1]+1;
else sta[i][0]=sta[i-1][0];
if(a[i]<a[i-1]) sta[i][1]=sta[i-1][0]+1;
else sta[i][1]=sta[i-1][1];
}
cout<<max(sta[n][0],sta[n][1]);
return 0;
}
第三题——华容道 (puzzle)
接下来的内容可能会引起读者的不适,请自行评判自己的承受能力再选择是否接下去看。
【题目描述】
- 这道题就和题目一样啊..但有点不同的情况就是这个的方块没有长度…给你一个 n∗m n ∗ m 的图,其中一部分点是固定的不能移动(墙?)。进行一串查询,给定空格,目标块,目的地。输出最小的步数似的目标块到目的地。
- 这道题在洛谷上的评级是省选…所以难度可想而知了….看不懂的大家努力吧…
- 第一次打了一把暴力深搜…但没有处理好记录状态…打崩了只得了十五分…据说可以暴力广搜打到80…但需要超级强大的剪枝…但性价比真的真的很高了…。
正解有两种:
我学习的正解是预处理所读入的地图…通过广搜(一把清流bfs)获得相邻两点之间的最短距离…然后进行最短路的查询。
- 效率大概是….不可预测…但预处理的效率确实高..再加上图小,spfa的效果很明显。
- 主要有两个问题:
- 1、如何保持状态。因为每一次查询的时候有三个点需要记录,dp至少是6维,果断会爆。那我们就用类似hash的方法进行压缩。比如:
tt=bx*120+by*4+i;//这里的i是指方向,我们下面再讲
- 2、怎么样快速求出距离。由于查询的次数特别多,那么如果每次查询都要进行一次搜索那不是果断就要爆炸了么…那我们就在读入的时候进行一次预处理。将这map转化成一张图。由于一个点会有可能有四个方向进行出入,所以每回都需要进行四个方向进行搜索orz。预处理之后再来进行查询的广搜,获得所需要局部图的边,再根据这些边来spfa获得答案。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
void fff(){
freopen("puzzle.in","r",stdin);
freopen("puzzle.out","w",stdout);
}
const int MAXN=40100;
int n,m,q,tot;
int h[MAXN],to[MAXN],nxt[MAXN],w[MAXN],dist[33][33],qx[1010],qy[1010],d[MAXN];
bool map[35][35];
int mvx[4]={-1,1,0,0};
int mvy[4]={0,0,-1,1};
void add(int x,int y,int z){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
w[tot]=z;
}
void bfs(int sx,int sy,int bx,int by,int fx){
//表示从s出发到b的路径求得
int tail=1,head=1,xx,yy,tx,ty;
memset(dist,0,sizeof(dist));
qx[1]=sx,qy[1]=sy,dist[sx][sy]=1;
while(head<=tail){
xx=qx[head],yy=qy[head];
for (int i=0;i<4;i++){
tx=xx+mvx[i];//获得新拓展
ty=yy+mvy[i];
if(map[tx][ty]&&!dist[tx][ty]&&(tx!=bx||ty!=by)){//1、要图上能走,2、点没有走过 3、不是目标点
dist[tx][ty]=dist[xx][yy]+1;//获得新的距离,也可以说是第几步
qx[++tail]=tx;//进队
qy[tail]=ty;
}
}
++head;//出队
}
if(fx==4) return;//沙雕防错,如果不是预处理就不要下去了!!!
//这里开始 建图存边
for (int i=0;i<4;i++){//情况1:空格不经过目标点
tx=bx+mvx[i],ty=by+mvy[i];
if((tx!=sx||ty!=sy)&&dist[tx][ty])
add(bx*120+by*4+fx,bx*120+by*4+i,dist[tx][ty]-1);//前向星存边,需要压缩点,所以将点变成了x*120+y*4+fx
}
add(bx*120+by*4+fx,sx*120+sy*4+fx^1,1);//情况2:空格和目标点进行交换
}
int inq[MAXN];
void spfa(int bx,int by){
int tx,ty,tt;
queue <int> quq;
for(int i=0;i<=4000;i++)
d[i]=1e7,inq[i]=0;
for (int i=0;i<4;i++){
tx=bx+mvx[i];
ty=by+mvy[i];
tt=bx*120+by*4+i;
if(dist[tx][ty]){
d[tt]=dist[tx][ty]-1;
quq.push(tt);
inq[tt]=1;
}
}
while (!quq.empty()){
int x=quq.front();quq.pop();
inq[x]=0;
for (int i=h[x];i!=-1;i=nxt[i]){
if(d[x]+w[i]<d[to[i]]){
d[to[i]]=d[x]+w[i];
if(!inq[to[i]])inq[to[i]]=1,quq.push(to[i]);
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&q);
memset(h,-1,sizeof(h));
for (int i=1;i<=n;i++){
for (int j=1;j<=m;j++){
scanf("%d",&map[i][j]);
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=m;j++){
if(!map[i][j]) continue;
if(map[i-1][j]) bfs(i-1,j,i,j,0);//这里是相邻的两个点建立边orz
if(map[i+1][j]) bfs(i+1,j,i,j,1);
if(map[i][j-1]) bfs(i,j-1,i,j,2);
if(map[i][j+1]) bfs(i,j+1,i,j,3);
}
}
while(q--){
int sx,sy,bx,by,mx,my;
scanf("%d%d%d%d%d%d",&sx,&sy,&bx,&by,&mx,&my);
if(bx==mx&&by==my){
printf("%d\n",0);
continue;
}
bfs(sx,sy,bx,by,4);
spfa(bx,by);
int ans=1e7;
for (int i=0;i<=3;i++){
ans=min(ans,d[mx*120+my*4+i]);
}
if(ans<1e7) printf("%d\n",ans);else printf("%d\n",-1);
}
}
- 第二种解法叫做A*算法(启发式搜索),是结合了深搜和广搜的较优算法,根据评估值来优先选择搜索方向。但…我真的不会打…orz…..我真的是个蒟蒻……..
- 那只好贴个传送门了…K-XZYdalao的题解