7.12 noip2103提高组复赛day2

第一题:积木大赛

法一:模拟。纯模拟过不完。。超暴力的模拟。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int a[100005];
struct node{
int num,qu;
};node pp[100005];
int cmp(const node &a,const node &b)
{
return a.qu>b.qu; 
}
main()
{
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
int n,i,step=0,sum=0,ans=0;
scanf("%d",&n);
int k=n;
for(i=1;i<=n;i++)
{
  scanf("%d",&a[i]);
i f(a[i]!=0)
sum++;
if(a[i]==0&&sum==0)k--;
if(a[i]==0&&sum!=0)
{
k--;
pp[++step].num=i-1;pp[step].qu=sum;sum=0;
}
if(i==n&&sum!=0)
{
pp[++step].num=n;pp[step].qu=sum;
}
}
n=k;
while(n)
{
sort(pp+1,pp+step+1,cmp);
for(i=pp[1].num-pp[1].qu+1;i<=pp[1].num;i++)
{
if(pp[1].qu==1&&a[pp[1].num]==1)
{
n--;
a[pp[1].num]--;
pp[1].qu--;
break;
}
a[i]--;
if(i!=pp[1].num-pp[1].qu+1||i!=pp[1].num)
{
if(a[i-1]!=0&&a[i]==0)
{
step++;
pp[step].num=i-1;
pp[step].qu=i-(pp[1].num-pp[1].qu+1);
pp[1].qu=pp[1].num-i;
n--;continue;
}
}
if(i==pp[1].num-pp[1].qu+1&&a[i]==0)
{
pp[1].qu--;n--;continue;
}
if(i==pp[1].num&&a[i]==0)
{
pp[1].qu--;pp[1].num--;n--;continue;
}
}
ans++;
sort(pp+1,pp+step+1,cmp);
}
cout<<ans;
}

码了70多行,超时最后四组。

法二:按数学方法仔细想想,处理相邻的两个数。如果前一个数大于后一个数,可以不用管,因为处理前一个数的时候可以顺带处理后一个数。前一个数小于后一个数时,只需加上后一个数减去前一个数的差(前一个数在前面已经处理了)。于是O(n)。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
int a[100005];
main()
{
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
int n,i,ans=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
cin>>a[i];
if(a[i]>=a[i-1])
{
ans+=(a[i]-a[i-1]);
}
}
cout<<ans;
}

干净多了。。。。


法三:二分。

在一个区间里面找最小的高度,即使为0也无所谓。整个区间减去最小值,然后将这个点的左右两边分开继续查找。数据较水,可以Accept。


代码:

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100000+5;
int h[maxn];
int n;
int solve(int L,int R)
{
int minn=maxn,pos;
if (L>R) return 0;
for (int i=L;i<=R;i++)
 if (minn>h[i])
 {
  minn=h[i];
  pos=i;
 }
for (int i=L;i<=R;i++)
 h[i]-=minn;
return minn+solve(L,pos-1)+solve(pos+1,R);
}
int main()
{
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
cin>>n;
for (int i=1;i<=n;i++)
 scanf("%d",&h[i]);
cout<<solve(1,n);
return 0;
}



第二题:花匠

法一:动归(O(n)),fup为最后两个数为上升的序列,fdown为最后两个数为下降的序列,则可写出方程:

if(h[i]>h[i-1])                 fdown[i]=fdown[i-1],fup[i]=max(fup[i-1],fdown[i-1]+1)

if(h[i]<h[i-1])                 fup[i]=fup[i-1],fdown[i]=max(fdpwn[i-1],fup[i-1]+1)


代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int fup[100005],h[1000005],fdown[1000005];
int main()
{
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
int n,i;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&h[i]);
}
fdown[1]=1;fup[1]=1;
for(i=2;i<=n;i++)
{
if(h[i]==h[i-1])
{
fup[i]=fup[i-1];fdown[i]=fdown[i-1];
}                                                                //这步必须要

else if(h[i-1]<h[i])
{
fdown[i]=fdown[i-1];
fup[i]=max(fdown[i-1]+1,fup[i-1]);
}
else
{
fup[i]=fup[i-1];
fdown[i]=max(fdown[i-1],fup[i-1]+1);
}
}
cout<<max(fup[n],fdown[n]);
}


法二:找拐点(O(n))

定义一个数表示当前数列的最后是上升还是下降,循环每个数与前一个数比较。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int a[1000005];
int main()
{
int n,i,flag=0,ans=0;                      //flag=0是因为最开始第一、二个数既可上升也可下降 。1上升,-1下降 
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
cin>>n>>a[1];
for(i=2;i<=n;i++)
{
cin>>a[i]; 
if(a[i]<a[i-1])
{
if(flag==0||flag==1)
{
flag=-1;
ans++;
}
}
if(a[i]>a[i-1])
{
if(flag==-1||flag==0)
{
flag=1;
ans++;
}
}
}
cout<<ans+1;                                                                 //拐点表示的边数,点数等于边数+1
}


第三题:华容道。

用广搜dfs爆搜,过得了80%。

用结构体保存空格的位置和可移动点的位置,用增量数组移动空格。当搜到可移动点时,交换两个位置,直到可移动点的位置是目标位置为止。判断数组可用四维数组b[][][][]表示,表示空格和可移动点的位置是否出现过。(因为1≤n,m≤30,所以此处不会超内存)


代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int move[2][4]={{0,0,1,-1},{-1,1,0,0}};
int i;
int j,n,m,q;
bool b[35][35][35][35];
int map[35][35];
struct mode{
int xxx,yyy,my,mx;
int step;
};
mode node[810005];
bool judge(int a,int e,int c,int d)
{
if(!map[a][e])return false;
if(b[a][e][c][d])return false;
b[a][e][c][d]=true;
return true;
}
void bfs()
{
int gx,gy;
cin>>node[1].xxx>>node[1].yyy>>node[1].mx>>node[1].my>>gx>>gy;
int head=0,tail=1;
if(node[1].mx==gx&&node[1].my==gy){cout<<"0"<<endl;return ;}
b[node[1].xxx][node[1].yyy][node[1].mx][node[1].my]=true;
do
{
head++;
for(i=0;i<=3;i++)
{
int tlx=node[head].mx,tly=node[head].my;
int tbx=node[head].xxx+move[0][i],tby=node[head].yyy+move[1][i];
if(tlx==tbx&&tly==tby){tlx=node[head].xxx;tly=node[head].yyy;}
if(judge(tbx,tby,tlx,tly))

{
tail++;
node[tail].xxx=tbx;node[tail].yyy=tby;
node[tail].mx=tlx;node[tail].my=tly;
node[tail].step=node[head].step+1;
if(tlx==gx&&tly==gy)
{
printf("%d\n",node[tail].step);return;
}
}
}
}
while(head<tail);
printf("-1\n");
}
int main()
{
freopen("puzzle.in","r",stdin);
freopen("puzzle.out","w",stdout);
cin>>n>>m>>q;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
cin>>map[i][j];
while(q--)
{
bfs();
memset(b,0,sizeof b);
}
}


附:正解。(太长了,汗⊙﹏⊙)。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
#define MaxN 35
using namespace std;
const int 
INF=~0U>>2,
dx[]={0,0,-1,1},
dy[]={-1,1,0,0};//注意顺序,才易实现0变1,1变0;2变3,3变2 
int mat[MaxN][MaxN],dis[MaxN][MaxN][4];
bool vis[MaxN][MaxN][4];
int step[MaxN][MaxN][4][4];
int d[MaxN][MaxN];
int n,m,q,test,ex,ey,sx,sy,tx,ty;
struct node
{
       int x,y;
};
struct node2
{
       int x,y,k;
};
bool inside(int x, int y)
{
return (x>=1&&x<=n&&y>=1&&y<=m);
}
int spfa()
{
queue<node2> q;
memset(vis,false,sizeof(vis));
while(!q.empty()) q.pop();//局部队列,可能非空,所以清空 
for(int k=0;k<4;k++)//把初始位置棋子与空格相邻,四个方向组成的四种可能的步数入队,当成四个源点。 
if(dis[sx][sy][k]!=INF)
{
            q.push((node2){sx,sy,k});
            vis[sx][sy][k]=true;
        }
while(!q.empty())
{
int x=q.front().x;
int y=q.front().y;
int k=q.front().k;
q.pop();
vis[x][y][k]=false;
for(int i=0;i<4;i++)
{
int _x=x+dx[i];//棋子(x,y)扩展的点(_x,_y)
int _y=y+dy[i];
if(inside(_x,_y))
if(mat[_x][_y])
if(step[x][y][k][i]!=INF)//棋子(x,y)k方向空格可以移到棋子(x,y)i方向。 
if(dis[_x][_y][i^1]>dis[x][y][k]+step[x][y][k][i]+1) 
{//棋子(x,y)k方向空格移到棋子(x,y)i方向,再把棋子移到空格上,所以+1,空格变成i^1方向。
dis[_x][_y][i^1]=dis[x][y][k]+step[x][y][k][i]+1;
if (not vis[_x][_y][i^1])

                   q.push((node2){_x,_y,i ^ 1 });
  vis[_x][_y][i^1]=true;
                 }
}
}
}
int ans=INF;
for(int i=0;i<4;i++)
if(dis[tx][ty][i]<ans)
ans=dis[tx][ty][i];//找出目标位置与空格相邻四种情况最少步数 
return (ans==INF)? -1:ans;
}
int bfs(int sx,int sy,int tx,int ty)//将空格从(sx,sy)移到(tx,ty)的步数 
{
if(!mat[sx][sy])
return INF;
if(!mat[tx][ty])
return INF;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) 
d[i][j]=INF;//INF可以做为没有到过的标志 
d[sx][sy]=0;
queue<node> q;//局部队列,可能非空,所以清空 
while(!q.empty()) q.pop();
q.push((node){sx,sy});
while(!q.empty())
{
   if(d[tx][ty]!=INF)
     return d[tx][ty];
int x=q.front().x;
int y=q.front().y;
q.pop();
for(int i=0;i<4;i++)
{
int _x=x+dx[i];
int _y=y+dy[i];
if(inside(_x,_y))
if(mat[_x][_y]&&d[_x][_y]==INF)
{
d[_x][_y]=d[x][y]+1;
//if(d[tx][ty]!=INF)
               //   return d[tx][ty];
//与下面不等价,有可能(sx,sy)与(tx,ty)一样,所以最好放在出队前判断 
//if (_x==tx&&_y==ty) return d[tx][ty];????
q.push((node){_x,_y});
}
}
}
return INF;
}
void init()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int v=mat[i][j];
mat[i][j]=0;//此位置可能是0或1,均改成0,因为移动空格不能移动棋子(i,j) 
for (int k=0;k<4;k++)
for (int l=0;l<4;l++) 
step[i][j][k][l]=bfs(i+dx[k],j+dy[k],i+dx[l],j+dy[l]);
//step[i][j][k][l] 棋子(i,j)k方向相邻的空格移动到相邻的l方向的步数,
    mat[i][j]=v;//还原
}
}
int getAns()
{
scanf("%d%d%d%d%d%d",&ex,&ey,&sx,&sy,&tx,&ty);
if(sx==tx&&sy==ty)//初始位置与目标位置同,0步
return 0;
if(sx==ex&&sy==ey)//初始位置与空格位置同,无解
return -1;
if(!inside(ex,ey)||!inside(sx,sy)||!inside(tx,ty))//三个位置至少有一个在棋盘外
return -1;
if(!mat[ex][ey]||!mat[sx][sy]||!mat[tx][ty])三个位置至少有一个是固定的
return -1;
for(int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
for (int k=0;k<4;k++)
dis[i][j][k]=INF;
mat[sx][sy]=0;//此时一定是1,改成0,因为前面已经判断过 
for(int k=0;k<4;k++)
dis[sx][sy][k]=bfs(ex,ey,sx+dx[k],sy+dy[k]);
    //dis[sx][sy][k]表示将空格移到(sx,sy)相邻并在k方向上的步骤 
mat[sx][sy]=1; //还原 
return spfa();
}
int main()
{
freopen("puzzle.in","r",stdin);
freopen("puzzle.out","w",stdout);
    scanf("%d%d%d",&n,&m,&test);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&mat[i][j]);
    init();
    while(test--)
printf("%d\n",getAns());
    return 0;
}


总结:考试时容易出意料之外的差错,如做第二题花匠时读错了题,没有看到条件中的:“对任意的i”。做第一题第二题这种略简单的题时,若爆搜要码的字太长(做第一题用爆搜,花了太多时间去模拟过程),最好先想想其他办法。最后一题若实在没有办法想出正解(如这个157行的代码),用暴力方法骗分。(也不叫骗吧。。)另外,参考数据一般比较水,没有办法概括完所有的情况,自己出几组数据在草稿本上模拟,然后检查对不对。时间充裕可以动态差错(当然还得花时间写暴力算法)。╮(╯▽╰)╭

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值