2025--炼石计划-- 11 月 23 日 --NOIP 模拟赛 #23
2025--炼石计划-- 11 月 23 日 --NOIP 模拟赛 #23
\(T1\) A. 排序 \(100pts\)
-
仅考虑临项比较时必要的一位的选择即可。
点击查看代码
ll a[1000010],ans[35][2]; ll ask() { ll x=0; for(ll i=31;i>=0;i--) { if(ans[i][0]!=0&&ans[i][1]!=0) { return -1; } if(ans[i][1]!=0) { x|=(1<<i); } } return x; } void update(ll x,ll y,ll val) { for(ll i=31;i>=0;i--) { ll cx=(a[x]>>i)&1; ll cy=(a[y]>>i)&1; if(cx!=cy) { ans[i][cx]+=val; break; } } } int main() { #define Isaac #ifdef Isaac freopen("sort.in","r",stdin); freopen("sort.out","w",stdout); #endif ll n,m,p,v,x,y,i,j,k; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(i=1;i<=n-1;i++) { update(i,i+1,1); } printf("%lld\n",ask()); scanf("%lld",&m); for(k=1;k<=m;k++) { scanf("%lld%lld",&p,&v); if(p!=1){update(p-1,p,-1);} if(p!=n){update(p,p+1,-1);} a[p]=v; if(p!=1){update(p-1,p,1);} if(p!=n){update(p,p+1,1);} printf("%lld\n",ask()); } return 0; }
\(T2\) B. 交换 \(10pts\)
-
部分分
- \(10pts\) :模拟。
点击查看代码
int a[100010],b[100010],c[100010]; int main() { #define Isaac #ifdef Isaac freopen("swap.in","r",stdin); freopen("swap.out","w",stdout); #endif int n,m,t,i; scanf("%d%d%d",&n,&m,&t); for(i=0;i<n;i++) { scanf("%d",&a[i]); } for(i=0;i<m;i++) { scanf("%d%d",&b[i],&c[i]); } for(i=1;i<=t;i++) { swap(a[(b[i%m]+i)%n],a[(c[i%m]+i)%n]); } for(i=0;i<n;i++) { printf("%d ",a[i]); } return 0; }
-
正解
- 不妨将 \(\{ a \}\) 看做一个排列,并对每 \(m\) 次操作分成一轮然后进行加速,最后再暴力处理剩下的操作。
- 当 \(m\) 是 \(n\) 的倍数,每一轮对排列的操作都是一样的,快速幂加速置换复合即可。
- 当 \(m\) 不是 \(n\) 的倍数时,执行完一轮操作后用于操作的映射置换同时进行了一次向右循环位移 \(m \bmod n\) 位。不妨每一轮操作都先将原排列向左循环位移再执行和第一轮一样的操作,最后执行完之后再向右一次性循环位移完,这样得到的结果和原来是等价的,直接考虑循环节上的对应关系即可。然后进行正常的快速幂加速即可。
点击查看代码
ll n,a[100010],b[100010],c[100010]; struct Permutation { ll pos[100010]; void init() { for(ll i=0;i<n;i++) { pos[i]=i; } } Permutation operator * (const Permutation &another) { Permutation tmp; for(ll i=0;i<n;i++) { tmp.pos[i]=another.pos[pos[i]]; } return tmp; } }per; Permutation qpow(Permutation a,ll b) { Permutation ans; ans.init(); while(b) { if(b&1) { ans=ans*a; } b>>=1; a=a*a; } return ans; } Permutation left(Permutation a,ll d) { Permutation tmp; for(ll i=0;i<n;i++) { tmp.pos[(i-d+n)%n]=a.pos[i]; } return tmp; } Permutation right(Permutation a,ll d) { Permutation tmp; for(ll i=0;i<n;i++) { tmp.pos[(i+d)%n]=a.pos[i]; } return tmp; } int main() { #define Isaac #ifdef Isaac freopen("swap.in","r",stdin); freopen("swap.out","w",stdout); #endif ll m,t,i; scanf("%lld%lld%lld",&n,&m,&t); for(i=0;i<n;i++) { scanf("%lld",&a[i]); } for(i=0;i<m;i++) { scanf("%lld%lld",&b[i],&c[i]); } per.init(); for(i=1;i<=m;i++) { swap(per.pos[(b[i%m]+i)%n],per.pos[(c[i%m]+i)%n]); } per=right(qpow(left(per,m%n),t/m),(t/m)*(m%n)); for(i=t/m*m+1;i<=t;i++) { swap(per.pos[(b[i%m]+i)%n],per.pos[(c[i%m]+i)%n]); } for(i=0;i<n;i++) { printf("%lld ",a[per.pos[i]]); } return 0; }
\(T3\) C. 计算 \(20pts\)
-
考虑将整数部分和小数部分的贡献分开统计。
-
部分分
- \(20pts\)
- 整数部分的贡献为 \(\sum\limits_{i=1}^{n}\sum\limits_{j=l_{i}}^{r_{i}-1}\frac{j}{r_{i}-l_{i}}=\sum\limits_{i=1}^{n}\frac{l_{i}+r_{i}-1}{2}\) 。
- 小数部分的贡献为 \(\sum\limits_{i=1}^{n-1}\frac{i}{n}=\frac{n-1}{2}\) 。
- 当 \(\forall i \in [1,n],x_{i}\) 在 \([0,1)\) 均匀随机时 \(\left\lfloor \sum\limits_{i=1}^{n}x_{i} \right\rfloor=0/1/2/ \dots /n-1\) 的概率相等且均为 \(\frac{1}{n}\) 。详细证明不会。
点击查看代码
const ll p=998244353; ll l[1010],r[1010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { #define Isaac #ifdef Isaac freopen("calc.in","r",stdin); freopen("calc.out","w",stdout); #endif ll n,k,ans=0,inv=qpow(2,p-2,p),i; cin>>n>>k; ans=(n-1)*inv%p; for(i=1;i<=n;i++) { cin>>l[i]>>r[i]; ans=(ans+(l[i]+r[i]-1)%p*inv%p)%p; } cout<<ans<<endl; return 0; }
- \(20pts\)
-
正解
\(T4\) D. 莫队 \(25pts\)
-
部分分
- \(Subtask 1 \sim 3\) :暴力进行 \([L,R]\) 的操作,时间复杂度为 \(O(qm \log n)\) 。
- \(Subtask 4\) :同一个左端点的询问一起处理。
点击查看代码
int l[500010],r[500010],ans[500010]; vector<pair<int,int> >ask[500010]; struct ODT { struct node { int l,r; mutable int col; bool operator < (const node &another) const { return l<another.l; } }; int cnt[500010],ans; set<node>s; void init(int n) { s.clear(); ans=n; for(int i=1;i<=n;i++) { cnt[i]=1; s.insert((node){i,i,i}); } } set<node>::iterator split(int pos) { set<node>::iterator it=s.lower_bound((node){pos,0,0}); if(it!=s.end()&&it->l==pos) { return it; } it--; if(it->r<pos) { return s.end(); } int l=it->l,r=it->r,col=it->col; s.erase(it); s.insert((node){l,pos-1,col}); return s.insert((node){pos,r,col}).first; } void assign(int l,int r,int col) { set<node>::iterator itr=split(r+1),itl=split(l); for(set<node>::iterator it=itl;it!=itr;it++) { cnt[it->col]-=(it->r-it->l+1); ans-=(cnt[it->col]==0); } s.erase(itl,itr); ans+=(cnt[col]==0); cnt[col]+=(r-l+1); s.insert((node){l,r,col}); } int query_col(int pos) { set<node>::iterator it=s.lower_bound((node){pos,0,0}); if(it->l==pos) { return it->col; } it--; return it->col; } }O; int main() { #define Isaac #ifdef Isaac freopen("mo.in","r",stdin); freopen("mo.out","w",stdout); #endif int n,m,q,st,ed,i,j,k; scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { scanf("%d%d",&l[i],&r[i]); } scanf("%d",&q); for(i=1;i<=q;i++) { scanf("%d%d",&st,&ed); ask[st].push_back(make_pair(ed,i)); } for(i=1;i<=m;i++) { if(ask[i].empty()==0) { O.init(n); sort(ask[i].begin(),ask[i].end()); k=i; for(j=0;j<ask[i].size();j++) { while(k<=ask[i][j].first) { O.assign(l[k],r[k],O.query_col(l[k])); k++; } ans[ask[i][j].second]=O.ans; } } } for(i=1;i<=q;i++) { printf("%d\n",ans[i]); } return 0; }
-
正解
- 颜色种类数不可差分,无法直接套用 luogu P8512 [Ynoi Easy Round 2021] TEST_152 维护时间戳上的影响做法。但仍可借鉴其扫描线维护时间戳的做法。
- 考虑将答案拆成未被覆盖的部分和被覆盖的部分中不同的颜色。
- 前者支持差分,珂朵莉树维护被覆盖长度,再用 \(n\) 减去即可得到结果。
- 对于后者,珂朵莉树推平过程中我们先不用管 \(a_{l_{i}}\) 具体等于什么,而是尝试通过维护一些信息判断是否与先前颜色不同。
-
因维护时间戳的需要,当新进行操作 \(i\) ,将时间轴上 \(i\) 的贡献加一, \(l_{i}\) 上次被覆盖的时间处的贡献减一(如果没有则直接不管),分讨询问端点与其两点的大小关系即可得到正确性。
-
而对于删除操作(某个操作覆盖的区间被后续操作完全覆盖),考虑通过一些手段将其影响撤销。一个比较容易想到的方法是记录每个颜色因分裂而导致在珂朵莉树上的段数 \(cnt\) ,通过判断 \(cnt\) 是否等于 \(0\) 来决定是否需要撤销。但这样的话无法处理图中这种情况。
-
而出现这种情况是由于在撤销 \(1\) 操作的影响后 \(2\) 操作仍需要 \(1\) 操作的贡献,但上述处理方式因没有正确判断某个颜色是否真正消失出现了错误没有统计这一贡献。不妨给每个操作再开一个计数器表示有多少后续操作需要其影响,删除时一并删除即可。
-
点击查看代码
int l[500010],r[500010],ans[500010],fa[500010],son[500010],cnt[500010]; vector<pair<int,int> >ask[500010]; struct BIT { int c[500010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { if(x==0) { return; } for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T[2]; struct ODT { struct node { int l,r; mutable int col; bool operator < (const node &another) const { return l<another.l; } }; set<node>s; void init(int n) { s.insert((node){1,n,0}); } set<node>::iterator split(int pos) { set<node>::iterator it=s.lower_bound((node){pos,0,0}); if(it!=s.end()&&it->l==pos) { return it; } it--; if(it->r<pos) { return s.end(); } int l=it->l,r=it->r,col=it->col; cnt[it->col]++; s.erase(it); s.insert((node){l,pos-1,col}); return s.insert((node){pos,r,col}).first; } void del(int col,int m) { if(col==0) { return; } if(cnt[col]==0&&son[col]==0) { T[1].add(m,col,-1); if(col!=fa[col]) { T[1].add(m,fa[col],1); son[fa[col]]--; del(fa[col],m);//尝试判断上一次覆盖是否需要撤销 } } } void assign(int l,int r,int col,int m) { set<node>::iterator itr=split(r+1),itl=split(l); fa[col]=(itl->col==0)?col:itl->col; cnt[col]=1; T[1].add(m,col,1); if(col!=fa[col]) { T[1].add(m,fa[col],-1); son[fa[col]]++; } for(set<node>::iterator it=itl;it!=itr;it++) { T[0].add(m,it->col,-(it->r-it->l+1)); cnt[it->col]--; del(it->col,m); } T[0].add(m,col,r-l+1); s.erase(itl,itr); s.insert((node){l,r,col}); } }O; int main() { #define Isaac #ifdef Isaac freopen("mo.in","r",stdin); freopen("mo.out","w",stdout); #endif int n,m,q,x,y,i,j; scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { scanf("%d%d",&l[i],&r[i]); } scanf("%d",&q); for(i=1;i<=q;i++) { scanf("%d%d",&x,&y); ask[y].push_back(make_pair(x,i)); } O.init(n); for(i=1;i<=m;i++) { O.assign(l[i],r[i],i,m); for(j=0;j<ask[i].size();j++) { ans[ask[i][j].second]=n-(T[0].getsum(i)-T[0].getsum(ask[i][j].first-1))+(T[1].getsum(i)-T[1].getsum(ask[i][j].first-1)); } } for(i=1;i<=q;i++) { printf("%d\n",ans[i]); } return 0; }
- 挂一下官方题解的扫描线做法。
总结
-
\(T1\) 貌似直接把捆绑测试后的全部测试数据和
data.yml
配置文件下发了,因为不会用提供的checker.cpp
,所以在对数据后缀名利用模板重命名后手写了run.cpp
。点击查看代码
int sys(string cmd) { return system(cmd.c_str()); } int main() { system("g++ sort.cpp -std=c++14 -O2 -o sort"); for(int i=10;i<=97;i++)//十以内的手动测试 { sys("./a <" + to_string(i) + ".in >" + to_string(i) +".out"); if(sys("diff " + to_string(i)+".out "+ to_string(i)+".ans -Z")) { cout<<i<<endl; break; } } return 0; }
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18564949,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。