noip模拟22
今天是用 2020 年的 NOIP 模拟的。T2 被换掉了,因为之前专题做过,然后换了个更好做的?
也是有了自己的 200+。
ps:没有按难度排序。
A [NOIP2020] 微信步数
B [NOIP2020] 移球游戏
神秘构造题,但其实思路很简单。
考虑一种接口化,或者说模块化的解法,使得我能利用一些柱子实现某一个数只出现在某一个柱子上。
打个比方,这就像魔方一样,可以构造一个特定的公式,使得一些数交换位置而其他数不受影响。
Step 1 制造全 列
首先,假设当前要处理的元素是 now,那我们就把所有 now 标记成 ,其他标记成 。
然后考虑一个事情,如何把一个序列的 和 分开?
记第 根柱子上的 的个数为 ,我们可以先把第 个柱子上的前 个移动到 (空柱子)上
然后将第一列的 挪到第 列,其他 挪到第 列。
将第 列的前 个 挪回第
然后,将第 列的 分裂到第 列和第 列。
然后就会发现第 列没有数了,第 列成了全 列。
注意到原本定义的列数发生错位了,我们可以用 标记 列的原始编号。这样在进行完上述操作之后就需要交换 和 的编号和 和 的编号。
Step 2 构造全 列
我们在上述操作中已经构造出来一个全 的序列,并已经使得 号柱子上的全部 都到了 号柱子的最上方。重复以上过程就会使全部的 出现在所有序列上方,把它放到那个空序列就行。
处理 的情况
其实很简单,和上述思路类似,把前 的放在 号,把 号柱子分裂在 号和 号上,再把分裂后的回填回 ,这样 柱子就上下分层了,把 填到 上,就好了。
点击查看代码
#include<bits/stdc++.h> using namespace std; int n; const int N=401; int stk[51][N],top[51]; int tot[51]; vector<pair<int,int> >ans; int p[51]; // //void calc(int col,int a,int b,int c,int d) //{ // for(int i=1;i<=tot[p[a]];i++) // { // // } //} inline void move(int a,int b) { ans.push_back({a,b}); stk[b][++top[b]]=stk[a][top[a]--]; } int m; inline int tp(int a) { return stk[a][top[a]]; } signed main() { freopen("ball.in","r",stdin); freopen("ball.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m; for(int i=1;i<=n;i++) { p[i]=i; for(int j=1;j<=m;j++) cin>>stk[i][j]; top[i]=m; } p[n+1]=n+1; top[n+1]=0; for(int now=n;now>=3;now--) { int tmp=0; for(int k=1;k<=m;k++)tmp+=(stk[p[1]][k]==now); for(int i=1;i<=tmp;i++)move(p[now],p[now+1]); for(int i=1;i<=m;i++) { if(tp(p[1])==now) move(p[1],p[now]); else move(p[1],p[now+1]); } for(int i=1;i<=m-tmp;i++) move(p[now+1],p[1]); for(int i=1;i<=m;i++) { if(tp(p[2])==now||top[p[1]]==m) move(p[2],p[now+1]); else move(p[2],p[1]); } swap(p[1],p[now]),swap(p[2],p[now+1]); for(int k=1;k<now;k++) { tmp=0; for(int j=1;j<=m;j++)tmp+=(stk[p[k]][j]==now); for(int i=1;i<=tmp;i++)move(p[now],p[now+1]); for(int i=1;i<=m;i++) { if(tp(p[k])==now) move(p[k],p[now]); else move(p[k],p[now+1]); } swap(p[k],p[now+1]); swap(p[k],p[now]); } for(int i=1;i<now;i++) while(tp(p[i])==now) move(p[i],p[now+1]); for(int i=1;i<now;i++) while(top[p[i]]<m) move(p[now],p[i]); } // cerr<<ans.size()<<"\n"; int tmp=0; for(int k=1;k<=m;k++)tmp+=(stk[p[1]][k]==1); for(int i=1;i<=tmp;i++)move(p[2],p[3]); for(int i=1;i<=m;i++) { if(tp(p[1])==1) move(p[1],p[2]); else move(p[1],p[3]); } for(int i=1;i<=tmp;i++) move(p[2],p[1]); for(int i=1;i<=m-tmp;i++) move(p[3],p[1]); while(top[p[3]]) move(p[3],p[2]); while(tp(p[1])==2) move(p[1],p[3]); for(int i=1;i<=m;i++) { if(tp(p[2])==1) move(p[2],p[1]); else move(p[2],p[3]); } cout<<ans.size()<<"\n"; for(auto i:ans) cout<<i.first<<" "<<i.second<<"\n"; }
C [NOIP2020] 排水系统
这才是真正的 T1。开考半个小时切了。
按题意模拟即可。但是会爆 long long,这给我提了个醒,当大样例答案特别大,但是没超 long long 的时候,一定要自己再搓一个更大的样例,看看会不会超范围。如果超了果断换 int128 或者写高精。其实有个更冒险的做法是用 long double,虽然存储范围很大,但是越大精度越低。
所以还是用 int128 比较好。
后记:题目没卡深搜,其实最正确的做法是拓扑排序,遇到入度为 的点就放水,直到最后。
点击查看代码
#include<bits/stdc++.h> using namespace std; #define int __int128 int n,m; const int N=1e5+5; vector<int>e[N]; int indeg[N],outdeg[N]; int ansa[N],ansb[N]; int lcm(int x,int y) { if(x==0||y==0) return 1; return x/__gcd(x,y)*y; } inline void add(int u,int a,int b) { int lc=lcm(ansb[u],b); ansa[u]=ansa[u]*(lc/ansb[u]); ansb[u]=lc; a=a*(lc/b); ansa[u]+=a; int gc=__gcd(ansa[u],ansb[u]); ansa[u]/=gc,ansb[u]/=gc; } void dfs(int u,int a,int b) { for(int v:e[u]) { if(!outdeg[v]) add(v,a,b*outdeg[u]); dfs(v,a,b*outdeg[u]); } } inline int read() { register int s=0; register char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') s=(s<<1)+(s<<3)+(c^48),c=getchar(); return s; } void write(int x) { if(x>9) write(x/10); putchar(x%10+'0'); } signed main() { freopen("water.in","r",stdin); freopen("water.out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) { ansb[i]=1; int d=read(); for(int j=1;j<=d;j++) { int v=read(); outdeg[i]++,indeg[v]++; e[i].push_back(v); } } for(int i=1;i<=n;i++) { if(indeg[i]==0) dfs(i,1,1); } for(int i=1;i<=n;i++) { if(!outdeg[i]) write(ansa[i]),putchar(' '),write(ansb[i]),putchar(10); } }
D 三元组
的暴力是好想的, 的性质是好做的。
merge 起来就有 分。
设 表示到第 个数时 (其中 )余数为 的数量。因为每新加入一个 就相当于新加入点对 ,也就是所有 到 模 余数的个数加一。,其中 表示新加入点对中余数为 的个数。答案就是 。
可以滚一下,降低空间复杂度。
然后发现可以拿树状数组维护值域区间 ,就是每次在上述区间上区间加 ,并给重复的整块加上 ,用区修单查树状数组就实现了。
点击查看代码
#include<bits/stdc++.h> using namespace std; #define int long long const int N=1e5+5; int n,k; int *c; struct SegTree{ inline int lowbit(int x) { return x&-x; } void update(int x,int K) { x++; for(;x<=k;x+=lowbit(x)) c[x]+=K; } void Update(int l,int r,int K) { update(l,K),update(r+1,-K); } int query(int x) { x++;int res=0; for(;x;x-=lowbit(x)) res+=c[x]; return res; } }st; void q() { cin>>n>>k; c=new int [k+10]; for(int i=0;i<=k+9;i++) c[i]=0; int ans=0,tmp=0; for(int i=1;i<=n;i++) { int l=(i*i+1),r=i*(i+1); l%=k,r%=k; tmp+=i/k; if(i%k) { if(l>r) st.Update(0,r,1),st.Update(l,k-1,1); else st.Update(l,r,1); } ans+=st.query((i*i%k*i)%k)+tmp; } cout<<ans<<"\n"; } signed main() { freopen("exclaim.in","r",stdin); freopen("exclaim.out","w",stdout); ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); int T;cin>>T; for(int i=1;i<=T;i++) cout<<"Case "<<i<<": ",q(); }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析