时间:2018年5月31日 8:00~11:00
题目:5
难度:普及+/提高-
T1、T2略
T3:
题目:
此时,Conan 却在一旁玩着 2048。
这是一个 4*4 的矩阵,初始全为 0。每次一个没有数字的格子中会随机出现一个 2 或 4,每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,相同数字的方块在靠拢、相撞时会相加。
Conan 想看看今天自己运气咋样,于是就闭着眼睛,在屏幕上随便滑来滑去。所以这个模拟的任务就交给你了。过了一会,他然后睁开眼睛,如果游戏没有结束(滑动后如果没有空格子,则游戏结束),请输出矩阵(格式参见样例),否则输出“Game over!”(不包含引号)。
1.游戏结束的含义:如果当前位置的数并没有消掉,此位置又新出现数了,游戏结束2.向右滑,比如8 2 2 2会消成8 2 4 向左滑,会消成8 4 2 2 2 2 2向右滑,会是4 4,而不是8
N <=1000。
分析:
题意:满了,还有操作,就game over
以2048为背景的模拟题。需要考虑的情况不多,但是还是漏掉了情况。每次移动,先判断game over 。消除数字的时候,最好先让数字到位,再消除。再合并。
想要一边归位一边消除的时候,容易造成:4 4 2 2 (右滑)-> 4 4 0 4 -> 4 0 0 8 -> 0 0 4 8。因为每一位归位后判断是否消除都是和上个归位的比较是否相同判断。
但是实际上,4 4 2 2 -> 0 0 8 4 因为每个数只能在一轮发生一次合并。
可以改进的是,判断在这一个位置上是否已经发生“碰撞”,没发生才行。
如果先到位再判断的话: 4 4 2 2 -> 4 4 2 2 ( 归位 )-> 4 4 0 4 (j=4&&j=3) -> 4 4 0 4 (j=3&&j=2) -> 0 8 0 4 (j=2&&j=1) -> 0 0 8 4 (归位) 就不会有问题了。
总结:还是有一些方面没有想到位,确实没有什么好方法。只能是根据题目内容信息,仔细把握,抓住细节。数据可以构造地活一点。
#include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int N=1000+10; int n,siz; int mp[6][6]; int a[6][6]; bool vis[6];//注意这个vis bool flag=true; struct node{ int x,y,v; char f; }q[N]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d%d %c",&q[i].x,&q[i].y,&q[i].v,&q[i].f); for(int i=1;i<=n;i++) { siz++; if(siz>16) { flag=false;break; }//warning mp[q[i].x][q[i].y]=q[i].v; memset(a,0,sizeof a); if(q[i].f==‘U‘){ for(int j=1;j<=4;j++) { memset(vis,0,sizeof vis); int k=1; for(int i=1;i<=4;i++) { if(mp[i][j]) { a[k][j]=mp[i][j]; if(k>1&&a[k-1][j]==a[k][j]&&!vis[k-1]) a[k-1][j]=2*a[k-1][j],vis[k-1]=1,a[k][j]=0,k--; k++; } } } } else if(q[i].f==‘D‘){ for(int j=1;j<=4;j++) { memset(vis,0,sizeof vis); int k=4; for(int i=4;i>=1;i--) { if(mp[i][j]) { a[k][j]=mp[i][j]; if(k<4&&a[k+1][j]==a[k][j]&&!vis[k+1]) a[k+1][j]=2*a[k+1][j],vis[k+1]=1,a[k][j]=0,k++; k--; } } } } else if(q[i].f==‘L‘){ for(int i=1;i<=4;i++) { memset(vis,0,sizeof vis); int k=1; for(int j=1;j<=4;j++) { if(mp[i][j]) { a[i][k]=mp[i][j]; if(k>1&&a[i][k-1]==a[i][k]&&!vis[k-1]) a[i][k-1]=2*a[i][k-1],vis[k-1]=1,a[i][k]=0,k--; k++; } } } } else{ for(int i=1;i<=4;i++) { memset(vis,0,sizeof vis); int k=4; for(int j=4;j>=1;j--) { if(mp[i][j]) { a[i][k]=mp[i][j]; if(k<4&&a[i][k+1]==a[i][k]&&!vis[k+1]) a[i][k+1]=2*a[i][k+1],vis[k+1]=1,a[i][k]=0,k++; k--; } } } } memcpy(mp,a,sizeof a); siz=0; for(int i=1;i<=4;i++) for(int j=1;j<=4;j++) if(mp[i][j]) siz++; } if(!flag){ printf("Game over!"); } else{ for(int i=1;i<=4;i++) { for(int j=1;j<=4;j++) { if(j!=4) printf("%d ",mp[i][j]); else printf("%d",mp[i][j]); } puts(""); } } return 0; }
T4:
题目:
豪华礼包:一个 U 盘、一个鼠标和一个机械键盘。
幸运礼包:一个 U 盘、两个鼠标。
普通礼包:两个 U 盘、一个鼠标。
卖店内准备了 a 个 U 盘、b 个鼠标和 c 个机械键盘。为了给顾客带来足够多的惊喜,店长希望相邻两位领礼包的顾客拿到的礼包类型都是不同的。店长想知道这些奖品最多可以发出多少份礼包。
组数T <=100000 ,a,b, c <=1,000,000 。
分析:
看似背包实际范围太大。看数据范围,可以做到 tlogmx
O1 公式不好想。就加一个log , 最直观的想法应该是二分(虽然没有想到)。单调性是显然的。
那么对于每一个二分的答案,我们必须O1判断。首先,发现所有的礼包可以划分成:(a+b)+c,(a+b)+a,(a+b)+b。必有a+b
先判断a+b够不够用。减去这次判断用掉的a+b之后,看剩下的a/b/c能否凑够mid份。剩下的:la=mid-a,lb=mid-b,lc=c;
la+lb+lc<mid 可以返回false
但是有个条件是:相邻两个不一样,也就是说,用掉的任意两个物品的总数和,+1后不能小于剩下一个物品的用掉的数量。
但是,la,lb,lc可能剩的多,怎样判断用掉的有多少呢?
a/b/c最多都再用掉ceil(mid/2)份。所以,每一个和mid-a取min
这样,保证当a、b、c足够的时候,mid是一个合法解。
总结:
二分实在没想到。可以反省的是,开始可以多想几个思路。没有思路的时候,应该根据数据范围先想出来时间复杂度。再套算法。
也许这样,二分就能想到了。
二分其实简化在于:在符合单调性的情况下,用一个log的代价,知道了答案,从而逆向考虑这个答案是否合法。把正向的找答案,变成了逆向的把所有的答案都试一遍。和选择题带入选项的思路类似。
#include<bits/stdc++.h> using namespace std; int t,a,b,c; bool er(int x) { if(a<x||b<x) return false; int la=min((int)ceil(1.0*x/2),a-x),lb=min((int)ceil(1.0*x/2),b-x),lc=min(c,(int)ceil(1.0*x/2)); if(la+lb+lc<x) return false; if(la+lb+1<lc||lb+lc+1<la||lc+la+1<lb) return false; return true; } int ans,l,r; int main() { cin>>t; while(t--){ scanf("%d%d%d",&a,&b,&c); r=a+b+c; l=0; ans=0; while(l<=r){ int mid=(l+r)/2; if(er(mid)) ans=max(ans,mid),l=mid+1; else r=mid-1; } printf("%d\n",ans); } return 0; }
T5:
题目:http://192.168.14.142/problem/2333
大家都说要劳逸结合,Ayumi, Mitsuhiko, Genta 画完方格就出去运动啦!
他们来到了一片空地,画了 N 个连续的方格,每个方格上随机填上了一个数字,大家从第一个格子开始,每次可以向后跳不超过当前格子上的数的步数,大家开始就此比赛,看谁跳到最后一个格子的步数最少。
输出一行,表示跳的最少步数。N <= 10^7,a[i]<=5000
分析:
看复杂度。也许O(n), 好像加一个log也没事。大概确定了循环顺序,估计要O(n)扫一遍。
我们设f[i]表示,到i这个点最小步数。n^2显然可以。
如果我要log 转移呢?
仍然逆向考虑,类似于最长上升子序列,我们考虑,
假如已经知道了,p[j] : 走j步最多到哪个位置,那么,假如循环到i这个位置,(之前能到达i位置的点都已经处理过了,更新了p)我们只要找到能到达i的最少j
发现,这个p[j]一定是单调的。又是二分!!
具体实现方法:
1.初始f[1]=0,p[1]=1+a[1];其他p先设为0x3f3f3f(虽然不合定义,但是方便lower_bound)
2.然后,循环从i=2开始循环,先通过lower_bound 找到第一个>=i的p[j]的j,这就是f[i]的值。因为i之前的点已经尝试更新过p了,那么,现在的p数组,就是目前,每个步数所能到达的最远地方。
3.将p[f[i]+1]=max(p[f[i]+1],i+a[i]) 表示,如果用最小步数先到达i,再多走一步,最多到达a[i]+i,是否可以更新p
因为之前设了p=0x3f3f3f3f,所以特判一下就好。
4.输出f[n]
总结:
不好想。只能说,还是先看数据范围,想复杂度,然后就神仙一般想到了“i步最远能到哪里”。可以说是对平时算法的转化应用吧。
记得单调栈求最长上升子序列的时候,我们也是用了一个数组g,g[i]表示,长度为i的最长上升子序列最后一个数最小是多少。有一点贪心的意思。
平时对于每个题目,每个算法一定要仔细琢磨。尝试串联起来。
#include<bits/stdc++.h> using namespace std; const int N=1e7+10; const int inf=0x3f3f3f3f; int n,x; int p[N],f[N]; int a[N]; inline int gen(int x){ return (1LL*x*(x^(x>>1))+(x-3)*(x|(x<<1)))%min(n,5000)+1; } int main(){ scanf("%d%d",&n,&x); for(int i=1;i<=n;++i) x=gen(x),a[i]=x; memset(p,inf,sizeof p); p[1]=1+a[1]; for(int i=2;i<=n;i++) { f[i]=lower_bound(p+1,p+n+1,i)-p; if(p[f[i]+1]!=inf) p[f[i]+1]=max(p[f[i]+1],i+a[i]); else p[f[i]+1]=i+a[i]; } printf("%d",f[n]);return 0; }
原文地址:https://www.cnblogs.com/Miracevin/p/9117926.html