暑假集训8.8 C班模拟赛
2023-08-10 17:58:18
前言
这场打下来感觉没有很妙,感觉也是题目有一定的问题吧?比如 A 题看似不正常实则通过了的做法(nth_element
的滥用),B 题的数据范围不清晰,以及 C 题的卡空间导致不敢用二维线段树,不过也还好,订正之后还是有收获的。
这场基本上就是被莫名其妙送了很多分,但是努力打的分却没有多少(比如 B 的超长超复杂暴力),下次加油吧。
A
做法1(考场做法)
这题一开始各种什么速度,然后初始位置给我搞蒙了,我还以为要以
先瞎打个暴力吧,每次
没想拿
然后发现它只是需要求一个位置的值,所以想到了仁蛟之前讲的
但是愚蠢的我连快速排序都不会,更不要说是快速排序的变种了,所以我就想起来之前第 nth_element
,然后就查了一下用法就过了。
struct node{ int v,s,id;ll ans; inline bool operator <(const node& w)const{ if(ans==w.ans)return id<w.id; return ans>w.ans; } }a[7003]; int n,m; int main(){ n=read(); for(int i=1;i<=n;i++){ int v=read(),s=read(); a[i]={v,s,i,0}; } m=read(); for(int ttt=1;ttt<=m;ttt++){ int t=read(),k=read(); for(int i=1;i<=n;i++){ a[i].ans=1ll*a[i].v*t+1ll*a[i].s; } nth_element(a+1,a+k,a+1+n); printf("%d\n",a[k].id); } }
注:这份神奇的代码把 nth_element(a+1,a+k,a+1+n);
改成 sort(a+1,a+1+n)
也能过。
做法2(题解做法)
注意到,一旦一个赛车
int t; struct node{ int v,s,id;//对于 q,v 表示时间,s表示 排名,id表示第几个询问 inline bool operator <(const node &w)const{ return v<w.v; } inline bool operator >(const node &w)const{//若a>b,说明a比b优,要放在前面 if(1ll*v*t+s==1ll*w.v*t+w.s)return id<w.id; return 1ll*v*t+s>1ll*w.v*t+w.s; } }a[7003],q[7003]; int n,m,ans[7003]; int main(){ n=read(); for(int i=1;i<=n;i++){ int v=read(),s=read(); a[i]={v,s,i}; } m=read(); for(int i=1;i<=m;i++){ int w=read(),k=read(); q[i]={w,k,i}; } sort(q+1,q+1+m); for(int o=1;o<=m;o++){ t=q[o].v; int w=n-1,p=0;//p存最后一次交换的位置,因为如果之后不再交换,说明在当前时间下,后面部分已经有序,算是一个小优化吧 for(int i=1;i<=n;i++){ bool flag=1;//判断是否有发生交换 for(int j=1;j<=w;j++){ if(a[j+1]>a[j]){//更优就放前面 swap(a[j+1],a[j]); flag=0;p=j; } } w=p;//记录当前时间状态下,还未有序的终点 if(flag)break;//如果任意两两之间不交换,说明已经有序,直接退出 } ans[q[o].id]=a[q[o].s].id;//记录答案 } for(int i=1;i<=m;i++) printf("%d\n",ans[i]); }
好家伙,说实话我冒泡排序都忘光了,打一个不同的方法算是复习一下冒泡排序吧。
B
暴力
第一眼以为是网络流啥啥我不会的算法,然后仔细看观察到数据范围很小就想着随便搜索乱搞了。
把每一条路都处理出来,然后稍微去掉一些不优的路径(比自己的子集的答案要大),再搜索即可(一开始想当然以为选到一条路要一直走下去,没想到中途可以换线)。
struct DAY{int l,r;}; int n,m,K,e; int f[25][25],cnt,s[300004]; ll F[300005],vis[300005];//2^18=262144 vector<DAY>No[25]; void dfs(int x,int zt,ll res){ if(x==m){ if(~F[zt^(1<<m-2)])F[zt^(1<<m-2)]=min(res,F[zt^(1<<m-2)]); else F[zt^(1<<m-2)]=res; return; } if(~vis[zt]&&vis[zt]<=res)return; vis[zt]=res; for(int i=2;i<=m;i++){ if(i==x||f[x][i]==-1||(zt&(1<<i-2)))continue; dfs(i,(zt|(1<<i-2)),res+f[x][i]); } } ll ans=1e9; void dfs2(int x,int st,ll res){//第 x 种状态路线,从 st 开始 for(int i=1;i<m-1;i++){ if(!(s[x]&(1<<i-1)))continue; for(DAY o:No[i]){ int l=o.l,r=o.r; if(st>=l&&st<=r)return; } } ll now=res+F[s[x]]; if(st==n){ ans=min(ans,now); return; } for(int i=1;i<=cnt;i++){ dfs2(i,st+1,now+(i==x?0:K)); } } int main(){ n=read(),m=read(),K=read(),e=read(); memset(f,-1,sizeof(f)); memset(vis,-1,sizeof(vis)); memset(F,-1,sizeof(F)); for(int i=1;i<=e;i++){ int u=read(),v=read(),w=read(); if(~f[u][v])f[u][v]=f[v][u]=min(f[u][v],w); else f[u][v]=f[v][u]=w; } dfs(1,0,0); for(int i=0;i<(1<<m-2);i++){ if(F[i]==-1)continue; bool flag=0; for(int j=(i-1)&i;j;j=(j-1)&i){ if(F[j]==-1)continue; if(F[i]>=F[j]){ flag=1; break; } } if(!flag)s[++cnt]=i; } int d=read(); for(int i=1;i<=d;i++){ int id=read(),l=read(),r=read(); No[id-1].push_back({l,r});//这里减1强行从1开始 } for(int i=1;i<=cnt;i++){ dfs2(i,1,0); } cout<<ans; }
有一说一我感觉暴力比正解难打。
正解
考场上以及讲之前都一直以为是深搜乱搞,没想到居然是一个很奇怪的最短路 + dp。
我们要先预处理出
令
表示在第
因为点数很小,所以随便怎么求最短路,求多少次,时间复杂度都够的,注意一些细节:
- 在赋值
数组的时候最好不要直接用memset(co,0x3f,sizeof(co))
,因为这样只能保证在相加极大值的时候不会爆,而 在转移是乘上一个数,会导致爆long long
或者int
。 - 注意怎么判断一个时间区间内一个点可不可用,例如
点在 时间段不能用,那么我们现在时间区间为 ,判断为 ,不要像我一样少写一个调半个小时。想必不会有人跟我一样蠢 - 因为这个变量名的命名很乱,所以要注意使用的变量名与其意义对应。
某人又因为写错了一个变量,而样例里两个变量的值是一样的,结果上交直接爆 0。
typedef pair<ll,ll> PII; struct Ban{ int id,l,r; }a[502]; int n,m,d,K;//与题目不一样,分别表示码头数,边数,天数,换线成本 vector<PII>f[25]; bool ban[25],vis[25]; ll co[102][102],dp[102],dis[22]; priority_queue<PII>Q; ll Dijkstra(){ memset(vis,0,sizeof(vis)); for(int i=1;i<=n;i++)dis[i]=1e9;//细节1 Q.push({(dis[1]=0),1}); while(!Q.empty()){ int x=Q.top().second; Q.pop(); if(vis[x]||ban[x])continue; vis[x]=1; for(PII PI:f[x]){ int y=PI.first,w=PI.second; if(dis[y]>dis[x]+w)Q.push({-(dis[y]=dis[x]+w),y}); } } return dis[n]; } int main(){ d=read(),n=read(),K=read(),m=read(); for(int i=1;i<=m;i++){ int u=read(),v=read(),w=read(); f[u].push_back({v,w}); f[v].push_back({u,w}); } int baned=read(); for(int i=1;i<=baned;i++){ a[i].id=read(),a[i].l=read(),a[i].r=read(); } for(int i=1;i<=d;i++)//细节3 for(int j=i;j<=d;j++){ memset(ban,0,sizeof(ban)); for(int o=1;o<=baned;o++){ int l=a[o].l,r=a[o].r; if((i<=l&&j>=l)||(i<=r&&j>=r)||(l<=i&&j<=r))//细节2 ban[a[o].id]=1; } co[i][j]=Dijkstra(); } memset(dp,0x3f,sizeof(dp)); dp[0]=-K; for(int i=1;i<=d;i++){//细节3 for(int j=0;j<i;j++){ dp[i]=min(dp[i],dp[j]+co[j+1][i]*(i-j)+K); } } printf("%lld",dp[d]);//细节3 }
C
考场上因为写爆了只写了一个
第一眼就知道是二维数据结构,然后想到之前看别人写的线段树套线段树(二维线段树),发现似乎这个连 short
的空间是 long long
或者 int
,就没有想到可以写二维线段树拿个
那么这个空间只能写二维树状数组了,前置知识看我博客二维树状数组吧。
然后就是写的过程中发现:
char
爆完了,因为char
的取值范围是 [-128,127] 的。short
的内存爆了,只能拿 (当然如果是 NOIP 估计直接爆炸),算了一下有 。- 只能用
unsigned char
,但是一定要及其小心赋值的问题,其他方法见代码。 - 一定要看清题目的输入顺序啊,我调这个又调了半个小时,还有变量不要打错了啊啊啊。
const int N=8002; #define lb(x) (x&-x) int n,mod,q,lasans; unsigned char d[N][N],xd[N][N],yd[N][N],xyd[N][N]; void add(int x,int y,int v){ for(int i=x;i<=n;i+=lb(i)) for(int j=y;j<=n;j+=lb(j)){ int p1=d[i][j],p2=xd[i][j],p3=yd[i][j],p4=xyd[i][j]; p1=((p1+v)%mod+mod)%mod; p2=((p2+v*x%mod)%mod+mod)%mod; p3=((p3+v*y%mod)%mod+mod)%mod; p4=((p4+v*x%mod*y%mod)%mod+mod)%mod; //先转成int运算完了再赋值,还有小心负数 d[i][j]=p1,xd[i][j]=p2,yd[i][j]=p3,xyd[i][j]=p4; } } int query(int x,int y){ int res=0; for(int i=x;i;i-=lb(i)) for(int j=y;j;j-=lb(j)){ int p1=d[i][j],p2=xd[i][j],p3=yd[i][j],p4=xyd[i][j]; res+=((x+1)%mod)*((y+1)%mod)%mod*p1%mod-((x+1)%mod)*p3%mod-((y+1)%mod)*p2%mod+p4; res=(res%mod+mod)%mod; //小心负数 } return res; } void update(int x1,int y1,int x2,int y2,int v){ add(x1,y1,v); add(x1,y2+1,-v); add(x2+1,y1,-v); add(x2+1,y2+1,v); return; } int query(int x1,int y1,int x2,int y2){ return ((query(x2,y2)+query(x1-1,y1-1)-query(x1-1,y2)-query(x2,y1-1))%mod+mod)%mod; } int main(){ n=read(),q=read(),mod=read(); while(q--){ int opt=read(),x1=read(),x2=read(),y1=read(),y2=read(); x1=(x1%n+lasans%n)%n,x2=(x2%n+lasans%n)%n,y1=(y1%n+lasans%n)%n,y2=(y2%n+lasans%n)%n; x1++,x2++,y1++,y2++; if(x1>x2)x1^=x2^=x1^=x2; if(y1>y2)y1^=y2^=y1^=y2; if(opt&1){ update(x1,y1,x2,y2,1); }else{ int ans=query(x1,y1,x2,y2); lasans=(lasans%n+ans%n)%n; printf("%d\n",ans); } } }
后记
好家伙打个比赛 3 小时,订正了我 5~6 个小时,不过还是学到了很多东西的,比如二维树状数组,一维树状数组的区间操作,冒泡排序的巧妙用法,写 dp 的新思路。
本文作者:NBest
本文链接:https://www.cnblogs.com/NBest/p/17686939.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步