没赶上昨天的考试,不过我这种人考不考都没有多少提升吧。
挺服气的一场考试,有生以来参加的最让人想笑的考试。
T1:养花
取模,区间询问最大值,有点套路化的预处理答案…难点也在预处理上。容易想到分块然后依次处理每个块的答案。
然后考虑每个块内怎么处理每个k。发现对于一个模数k,最大值一定是每个k的倍数的前驱,即比k小的最大值、比k*2小的最大值,比k*3小的最大值…这些数取max,然后%k。
那么在每个块内先扫一遍存下所有值,然后扫一遍值域使每个值域上的位置存小于等于它的最大值。接着枚举k进行处理,比较k、2k、3k…处的最大值。取模操作可以替换成减法来节省时间。
常数很大,但开了O2可以跑过去。
然后真的秀到我的一波操作出现了…题目的数据范围是100000,而数据出现了100001。重测一次我就快乐WA。
别问,问就是开大值域是好习惯。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; int a[100010],ans,n,m,siz=1000,blo[100010],num,f[150][100010],sum[100010]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); blo[i]=(i-1)/siz+1; } num=blo[n]; for(int i=1;i<=num;i++){ memset(sum,0,sizeof(sum)); for(int j=(i-1)*siz+1;j<=i*siz;j++){ sum[a[j]]=a[j]; } for(int j=1;j<=100001;j++){ sum[j]=max(sum[j],sum[j-1]); } for(int k=1;k<=100001;k++){ for(int j=0;j<=100001;j+=k){ f[i][k]=max(f[i][k],sum[j+k-1]-j); } f[i][k]=max(f[i][k],sum[100001]%k); } } for(int i=1,l,r,k;i<=m;i++){ scanf("%d%d%d",&l,&r,&k); ans=0; if(blo[l]==blo[r]){ for(int j=l;j<=r;j++){ ans=max(ans,a[j]%k); } } else{ for(int j=l;j<=blo[l]*siz;j++){ ans=max(ans,a[j]%k); } for(int j=(blo[r]-1)*siz+1;j<=r;j++){ ans=max(ans,a[j]%k); } for(int j=blo[l]+1;j<blo[r];j++){ ans=max(ans,f[j][k]); } } printf("%d\n",ans); } return 0; }
T2:折射
很显然是个DP,考虑怎么设计状态。数据范围卡掉了n2,又看到每个装置向左向右这样的方向性,想到了我前不久刚刚学会的关路灯。两道题显然不一样,但我觉得第二维八成就是方向了,然后思考怎么转移。
果然我是从转移开始错的…虽然DP一直都不擅长。
正解是按x排序。设f[i][0]为当前装置为最后一个,把光线向右折射,f[i][1]为向左。按x排序以后,当前扫到的i一定x最大,它的1数组一定只会由后面的i更新,而它的0数组只会由前面的i更新。同时它的0数组会去更新前面的1数组,因为这个,第二层循环要倒序进行。x小于xi的j有可能更新f[i][0],而f[i][0]紧接着可能拿来更新x更小的f[j][1]。更新i的0数组还是用0数组更新j的1数组取决于循环到的yj与yi的大小关系。最后把所有的f[i][0]和f[i][1]加起来,因为每个i只有自己组成一种方案的时候无论向左向右都一样,每个i的总方案再减去1。
有点乱…挺玄学的,完全想不到。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n; long long f[6010][2],ans,mod=1000000007; struct node{ int x,y; }a[6010]; bool cmp(node a,node b){ if(a.x<b.x)return true; else return false; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d%d",&a[i].x,&a[i].y); } sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++){ f[i][0]=f[i][1]=1; for(int j=i-1;j;j--){ if(a[j].y<a[i].y){ f[i][1]=(f[i][1]+f[j][0])%mod; } else f[j][0]=(f[i][1]+f[j][0])%mod; } // printf("%d %d %d\n",i,f[i][1],f[i][0]); } for(int i=1;i<=n;i++){ ans=(ans+f[i][0]+f[i][1]-1+mod)%mod; } printf("%lld",ans); return 0; }
T3:画作
有一个很好想的结论,作画的每一个操作i的范围一定可以被上一个操作的范围包括。染了外面大的一圈,再把中间染回来…这样每一次都可以更新至少一个确定的位置,这个位置以后不会再被染回来造成次数浪费。
那么可以让终态的每个格子向四周连边,异色连1边表示一次操作,同色连0边。每个格子跑一次最短路,找离它最远的黑块的距离,每个这样得出的距离取min,这就是操作数最少的方案。从每个格子出发找最远的黑块等价于从这个黑块出发染到它需要几次操作,因为第一步操作一定是染黑,所以一定是找最远的黑块。同一距离的格子一定可以在同一次操作中被确定。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; int r,c,ans=2147483647,vis[100010]; deque<pair<int,int> >q; int a[51][51],b[100010]; int h[5]={0,0,-1,0,1}; int l[5]={0,-1,0,1,0}; int ver[100010],head[100010],Next[100010],edge[100010],tot; void add(int x,int y,int z){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; edge[tot]=z; } int bfs(int x){ int num=0; q.clear(); q.push_back(make_pair(x,0)); memset(vis,0,sizeof(vis)); vis[x]=1; while(!q.empty()){ int u=q.front().first,dis=q.front().second; if(b[u])num=max(num,dis); q.pop_front(); for(int i=head[u];i;i=Next[i]){ int v=ver[i],z=edge[i]; if(!vis[v]){ vis[v]=1; if(!z){ q.push_front(make_pair(v,dis)); } else{ q.push_back(make_pair(v,dis+1)); } } } } // printf("%d ",num); return num+1; } int main() { scanf("%d%d",&r,&c); for(int i=1;i<=r;i++){ for(int j=1;j<=c;j++){ scanf("%1d",&a[i][j]); b[(i-1)*c+j]=a[i][j]; } } for(int i=1;i<=r;i++){ for(int j=1;j<=c;j++){ for(int k=1;k<=4;k++){ int x=i+h[k],y=j+l[k]; if(x>0&&y>0&&x<=r&&y<=c){ add((i-1)*c+j,(x-1)*c+y,((a[i][j]==a[x][y])^1)); // printf("(%d,%d) (%d,%d) %d\n",i,j,x,y,((a[i][j]==a[x][y])^1)); } } } } for(int i=1;i<=r;i++){ for(int j=1;j<=c;j++){ ans=min(ans,bfs((i-1)*c+j)); } } printf("%d",ans); return 0; }
大约是有史以来考得最好的一次吧,也说明我的极限大约就在这里了。
然而大家能做到的事情一定更多,希望大家能变得越来越好。