多校A层冲刺NOIP2024模拟赛19
多校A层冲刺NOIP2024模拟赛19
\(T1\) A. 图书管理 (book) \(90pts/90pts\)
-
部分分
- \(90pts\) :平衡树/线段树、主席树上二分/对顶堆暴力维护中位数,同 luogu P3871 [TJOI2010] 中位数 | luogu P1168 中位数 ,时间复杂度为 \(O(n^{2} \log n)\) ,需要适当卡常。
点击查看代码
int p[10010]; int main() { freopen("book.in","r",stdin); freopen("book.out","w",stdout); int n,i,l,r; ll ans=0; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&p[i]); } for(l=1;l<=n;l++) { priority_queue<int,vector<int>,greater<int> >q1; priority_queue<int,vector<int>,less<int> >q2; for(r=l;r<=n;r+=2) { if(q2.empty()==0&&p[r]>q2.top()) { q1.push(p[r]); } else { q2.push(p[r]); } if(abs((int)q1.size()-(int)q2.size())>=2) { if(q1.size()>q2.size()) { while(q1.size()-q2.size()>=2) { q2.push(q1.top()); q1.pop(); } } else { while(q2.size()-q1.size()>=2) { q1.push(q2.top()); q2.pop(); } } } ans+=1ll*l*r*(q1.size()>q2.size()?q1.top():q2.top()); if(r+2<=n) { if(q2.empty()==0&&p[r+1]>q2.top()) { q1.push(p[r+1]); } else { q2.push(p[r+1]); } if(abs((int)q1.size()-(int)q2.size())>=2) { if(q1.size()>q2.size()) { while(q1.size()-q2.size()>=2) { q2.push(q1.top()); q1.pop(); } } else { while(q2.size()-q1.size()>=2) { q1.push(q2.top()); q2.pop(); } } } } } } printf("%lld\n",ans); fclose(stdin); fclose(stdout); return 0; }
-
正解
- 考虑枚举中位数 \(p_{i}\) ,计算区间个数。
- 将 \([l,r]\) 内 \(>p_{i}\) 的数看做 \(1\) , \(<p_{i}\) 的数看做 \(-1\) ,中位数的约束条件等价于 \([l,r]\) 内数的和等于 \(0\) ,开个桶一并维护左右端点即可。
点击查看代码
ll p[10010],suml[20010],sumr[20010]; int main() { freopen("book.in","r",stdin); freopen("book.out","w",stdout); ll n,num,ans=0,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>p[i]; } for(i=1;i<=n;i++) { memset(suml,0,sizeof(suml)); memset(sumr,0,sizeof(sumr)); num=0; suml[n]=sumr[n]=i; for(j=i-1;j>=1;j--) { num+=(p[j]>p[i])?1:-1; suml[num+n]+=j; } num=0; for(j=i+1;j<=n;j++) { num+=(p[j]>p[i])?-1:1; sumr[num+n]+=j; } for(j=-n;j<=n;j++) { ans+=suml[j+n]*sumr[j+n]*p[i]; } } cout<<ans<<endl; fclose(stdin); fclose(stdout); return 0; }
\(T2\) B. 两棵树 (tree) \(30pts/30pts\)
-
部分分
- \(30pts\) :爆搜出每种情况挨个计算。
点击查看代码
const ll p=998244353; ll ans=0; 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; } struct Graph { struct node { ll nxt,to; }e[400010]; ll head[200010],vis[200010],check[200010],cnt=0,tot=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(ll x) { vis[x]=tot; for(ll i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]!=tot&&check[e[i].to]==0) { dfs(e[i].to); } } } ll ask(ll n) { tot++; ll ans=0; for(ll i=1;i<=n;i++) { if(vis[i]!=tot&&check[i]==0) { dfs(i); ans++; } } return ans; } }T,U; void dfs(ll pos,ll n) { if(pos==n+1) { ans=(ans+T.ask(n)*U.ask(n))%p; } else { T.check[pos]=1; U.check[pos]=0; dfs(pos+1,n); T.check[pos]=0; U.check[pos]=1; dfs(pos+1,n); } } int main() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); ll n,u,v,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; T.add(u,v); T.add(v,u); } for(i=1;i<=n-1;i++) { cin>>u>>v; U.add(u,v); U.add(v,u); } dfs(1,n); cout<<ans*qpow(qpow(2,n,p),p-2,p)%p<<endl; fclose(stdin); fclose(stdout); return 0; }
-
正解
- 对于一个森林,连通块数(树数)等于点数 \(V\) 减边数 \(E\) 。此时等价于求 \((V_{T}-E_{T}) \times (V_{U}-E_{U})=V_{T}V_{U}-E_{T}V_{U}-V_{T}E_{U}+E_{T}E_{U}\) 的期望值,考虑分别统计这几项的贡献。
- \(V_{T}V_{U}\)
- 暴力推式子,有 \(\begin{aligned} &E(V_{T}V_{U}) \\ &=\frac{1}{2^{n}} \times \sum\limits_{i=0}^{n}\dbinom{n}{i}(n-i)i \\ &=\frac{1}{2^{n}} \times \sum\limits_{i=1}^{n}\frac{n!}{(i-1)!(n-i-1)!} \\ &=\frac{1}{2^{n}} \times \sum\limits_{i=0}^{n-2}\dbinom{n-2}{i}(n-1)n \\ &=\frac{n(n-1)}{4} \end{aligned}\) 。
- 考虑组合意义,对于 \(x \in T,y \in U\) ,当 \(x \ne y\) 时均被保留的概率为 \(\frac{1}{4}\) ,否则均被保留的概率为 \(0\) ,总期望为 \(\frac{n(n-1)}{2}\) 。
- \(E_{T}V_{U}\)
- 考虑组合意义,对于 \((x,y) \in T,u \in U\) ,当 \(x,y,u\) 互不相同时均被保留的概率为 \(\frac{1}{8}\) ,否则均被保留的概率为 \(0\) ,总期望为 \(\frac{(n-1)(n-2)}{8}\) 。
- \(V_{T}E_{U}\)
- 同 \(E_{T}V_{U}\) ,总期望为 \(\frac{(n-1)(n-2)}{8}\) 。
- \(E_{T}E_{U}\)
- 考虑组合意义,对于 \((x,y) \in T,(u,v) \in U\) ,当 \(x,y,u,v\) 互不相同时均被保留的概率为 \(\frac{1}{16}\) ,否则均被保留的概率为 \(0\) 。
- 枚举 \((x,y) \in T\) ,那么 \((u,v) \in U\) 满足 \(x,y,u,v\) 互不相同的数量等于 \(n-1-du_{U,x}-du_{U,y}+[(x,y) \in U]\) 。
- 总期望为 \(\sum\limits_{(x,y) \in T}\frac{n-1-du_{U,x}-du_{U,y}+[(x,y) \in U]}{16}\) 。
- 最终,有 \(\frac{n-1}{2}+\sum\limits_{(x,y) \in T}\frac{n-1-du_{U,x}-du_{U,y}+[(x,y) \in U]}{16}\) 即为所求。
点击查看代码
const ll p=998244353; map<pair<ll,ll>,ll>e; ll du[200010],u[400010],v[400010]; 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() { freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); ll n,ans,inv=qpow(16,p-2,p),i; cin>>n; ans=(n-1)*qpow(2,p-2,p)%p; for(i=1;i<=n-1;i++) { cin>>u[i]>>v[i]; if(u[i]>v[i]) { swap(u[i],v[i]); } } for(i=1;i<=n-1;i++) { cin>>u[i+n]>>v[i+n]; du[u[i+n]]++; du[v[i+n]]++; if(u[i+n]>v[i+n]) { swap(u[i+n],v[i+n]); } e[make_pair(u[i+n],v[i+n])]=1; } for(i=1;i<=n-1;i++) { ans=(ans+(n-1-du[u[i]]-du[v[i]]+(e.find(make_pair(u[i],v[i]))!=e.end()))*inv%p)%p; } cout<<ans<<endl; fclose(stdin); fclose(stdout); return 0; }
\(T3\) C. 函数(fun) \(60pts/60pts\)
-
部分分
-
\(30 \%\) :模拟。
点击查看代码
ll x[1000010]; int main() { freopen("fun.in","r",stdin); freopen("fun.out","w",stdout); ll n,q,a,b,ans,i,j; scanf("%lld%lld",&n,&q); for(i=1;i<=n;i++) { scanf("%lld",&x[i]); } for(j=1;j<=q;j++) { scanf("%lld%lld",&a,&b); ans=-1; for(i=1;i<=n-1;i++) { if(((x[i]^a)-b)*((x[i+1]^a)-b)<=0) { ans=i; break; } } cout<<ans<<endl; } fclose(stdin); fclose(stdout); return 0; }
-
-
正解
- 考虑手动建出 \(f(x)\) 的函数图象,设左右端点分别为 \(l,r\) ,使用 \(01Trie\) 可以轻松维护。
- 若 \(f(l)f(r)>0\) 则一定无解,否则一定存在 \(f(l)f(mid) \le 0 \lor f(mid)f(r) \le 0\) ,其中 \(mid=\frac{l+r}{2}\) 。二分处理即可。
- 正确性由二分法求函数零点可知。
- 把二分改成递归在 accoders NOI 上大数据平均快了 \(300ms\) 左右,但在学校 \(OJ\) 上变慢了,不是很理解为啥。
点击查看代码
int x[1000010]; struct Trie { int son[32000010][2],ed[32000010],rt_sum=0; void insert(int s,int id) { int x=0; for(int i=30;i>=0;i--) { if(son[x][(s>>i)&1]==0) { rt_sum++; son[x][(s>>i)&1]=rt_sum; } x=son[x][(s>>i)&1]; } ed[x]=id; } int query_min(int s) { int x=0; for(int i=30;i>=0;i--) { if(son[x][(s>>i)&1]!=0) { x=son[x][(s>>i)&1]; } else { x=son[x][((s>>i)&1)^1]; } } return ed[x]; } int query_max(int s) { int x=0; for(int i=30;i>=0;i--) { if(son[x][((s>>i)&1)^1]!=0) { x=son[x][((s>>i)&1)^1]; } else { x=son[x][(s>>i)&1]; } } return ed[x]; } }T; int f(int x,int a,int b) { return (x^a)-b; } int main() { freopen("fun.in","r",stdin); freopen("fun.out","w",stdout); int n,q,a,b,l,r,ans,mid,i; scanf("%d%d",&n,&q); for(i=1;i<=n;i++) { scanf("%d",&x[i]); T.insert(x[i],i); } for(i=1;i<=q;i++) { scanf("%d%d",&a,&b); l=T.query_min(a); r=T.query_max(a); if(l>r) { swap(l,r); } ans=-1; if(1ll*f(x[l],a,b)*f(x[r],a,b)<=0) { while(r-l>1) { mid=(l+r)/2; if(1ll*f(x[l],a,b)*f(x[mid],a,b)<=0) { r=mid; } else { l=mid; } } ans=l; } printf("%d\n",ans); } fclose(stdin); fclose(stdout); return 0; }
\(T4\) D. 编辑(edit) \(60pts/60pts\)
-
部分分
- \(30pts\) :枚举左右端点,然后 \(O(n^{2})\) 计算编辑距离,同 luogu P2758 编辑距离 ,时间复杂度为 \(O(kn^{3})\) 。
- \(60pts\) :考虑固定一个端点,然后让另一个端点单调,时间复杂度为 \(O(n^{3})\) 。
点击查看代码
ll ans[50],f[2][50010]; char s[50010],t[50010]; int main() { freopen("edit.in","r",stdin); freopen("edit.out","w",stdout); ll k,lens,lent,l,r,i; cin>>k>>(s+1)>>(t+1); lens=strlen(s+1); lent=strlen(t+1); for(l=1;l<=lent;l++) { for(i=0;i<=lens;i++) { f[(l-1)&1][i]=i; } for(r=l;r<=lent;r++) { f[r&1][0]=r-l+1; for(i=1;i<=lens;i++) { f[r&1][i]=min(f[(r-1)&1][i-1]+(s[i]!=t[r]),min(f[r&1][i-1]+1,f[(r-1)&1][i]+1)); } if(f[r&1][lens]<=k) { ans[f[r&1][lens]]++; } } } for(i=0;i<=k;i++) { cout<<ans[i]<<endl; } fclose(stdin); fclose(stdout); return 0; }
-
正解
- 观察在编辑距离的存在下,长度差至多为 \(k\) 。
- 考虑设 \(f_{i,j}\) 表示最大的 \(x\) 使得 \(s_{1 \sim x}\) 和 \(t_{1 \sim x+j}\) 可以在 \(i\) 次编辑内得到,即从 \(1\) 开始操作 \(i\) 次且长度增加 \(j\) 的最大匹配位置。
- 显然 \(x,i\) 具有单调性。
- 转移时考虑截取一段 \(t\) 的后缀进行转移。
- 假设已经操作完了,那么 \(f_{i,j}\) 能向外延伸的就是 \(s_{f_{i,j}+1,|s|}\) 和 \(t_{f_{i,j}+j+1,|t|}\) 的公共前缀,查询 \(\operatorname{LCP}\) 进行更新即可。
- \(\operatorname{LCP}\) 可以用二分哈希单次 \(O(\log n)\) 查询,也可以后缀数组 \(O(n \log n)\) 预处理完 \(O(1)\) 查询。
- 转移时考虑刷表,分别对 \(f_{i+1,j-1},f_{i+1,j},f_{i+1,j+1}\) 进行转移(三者分别对应删除、替换、添加)。
- 注意下标为负数时的移位。
- 统计贡献时在遇到第一个合法的 \(i\) 加入贡献即可。
- 如果用后缀数组的话时间复杂度为 \(O(nk^{2}+n \log n)\) 。
点击查看代码
int f[40][70],ans[40]; char s[50010],t[50010],tmp[100010]; struct SA { struct ST { int fminn[100010][25]; void init(int n,int a[]) { memset(fminn,0x3f,sizeof(fminn)); for(int i=1;i<=n;i++) { fminn[i][0]=a[i]; } for(int j=1;j<=log2(n);j++) { for(int i=1;i+(1<<j)-1<=n;i++) { fminn[i][j]=min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]); } } } int query(int l,int r) { if(l>r) { swap(l,r); } l++; int t=log2(r-l+1); return min(fminn[l][t],fminn[r-(1<<t)+1][t]); } }T; int sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010],height[100010]; int val(char x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init(char s[],int len) { int m=127,tot=0,num=0; for(int i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(int w=1;tot!=len;w<<=1,m=tot) { num=0; for(int i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(int i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(int i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(int i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(int i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(int i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } T.init(len,height); } int lcp(int x,int y) { return T.query(rk[x],rk[y]); } }S; int main() { freopen("edit.in","r",stdin); freopen("edit.out","w",stdout); int k,lens,lent,i,j,h; cin>>k>>(s+1)>>(t+1); lens=strlen(s+1); lent=strlen(t+1); for(i=1;i<=lens;i++) { tmp[i]=s[i]; } tmp[lens+1]='&'; for(i=1;i<=lent;i++) { tmp[lens+1+i]=t[i]; } S.init(tmp,lens+1+lent); for(h=1;h<=lent;h++) { memset(f,-0x3f,sizeof(f)); f[0][k]=0; for(i=0;i<=k;i++) { for(j=-k;j<=k;j++) { if(f[i][j+k]>=0&&f[i][j+k]+j>=0&&f[i][j+k]+j+h-1<=lent) { if(f[i][j+k]+1<=lens&&f[i][j+k]+j+1+h-1+lens+1<=lens+1+lent) { f[i][j+k]+=S.lcp(f[i][j+k]+1,f[i][j+k]+j+1+h-1+lens+1); } if(j-1+k>=0) { f[i+1][j-1+k]=max(f[i+1][j-1+k],f[i][j+k]+(f[i][j+k]!=lens)); } f[i+1][j+k]=max(f[i+1][j+k],f[i][j+k]+(f[i][j+k]!=lens)); f[i+1][j+1+k]=max(f[i+1][j+1+k],f[i][j+k]); } } } for(j=-k;j<=k;j++) { for(i=0;i<=k;i++) { if(f[i][j+k]==lens&&lens+j>0&&lens+j+h-1<=lent)//要求非空 { ans[i]++; break; } } } } for(i=0;i<=k;i++) { cout<<ans[i]<<endl; } fclose(stdin); fclose(stdout); return 0; }
总结
- \(T1\) 直接对着 \(2s\) 的时限冲了个 \(O(n^{2} \log n)\) 的做法,大样例在本地跑了 \(1.4s \sim 1.6s\) ,遂直接交了。
- \(T2\) 的 \(Trick\) 好像在之前见过,但想不起来是哪道题了。
- \(T4\) 想了一个小时的怎么求编辑距离(先会写 \(O(3^{n})\) 的刷表爆搜后才会写 \(O(n^{2})\) 的转移),属于去年初赛白考了。
后记
-
\(T1\)
- 下发的 \(PDF,markdown\) 题面都给了 \(2s\) 的时限,且在一段时间内题目界面也给了 \(2s\) 的时限,但在比赛结束时发现改成了 \(1s\) 。
- 第一次下发的大样例假了,暴力都跑不过去。
-
\(T2\) 临结束时在学校 \(OJ\) 补充了额外的大样例。
-
\(T3\) 中途更换了大样例,并下发了
testlib
和checker.cpp
,但默认我们已经会使用checker.cpp
了, \(huge\) 还让我们别卡checker.cpp
。点击查看代码
#include"testlib.h" #include<bits/stdc++.h> const int N=1e6+7; int f[N]; int main(int argc,char *argv[]){ registerTestlibCmd(argc,argv); int n=inf.readInt(),q=inf.readInt(); for(int i=1;i<=n;i++) f[i]=inf.readInt(); while(q--){ int x=inf.readInt(),y=inf.readInt(); int a=ouf.readInt(),b=ans.readInt(); if((a==-1)^(b==-1)) quitf(_wa,"Wrong Answer."); if(a==-1) continue; if(1ll*((f[a]^x)-y)*((f[a+1]^x)-y)>0) quitf(_wa,"Wrong Answer."); std::swap(a,b); if(1ll*((f[a]^x)-y)*((f[a+1]^x)-y)>0) quitf(_wa,"Wrong Answer."); } quitf(_ok,"Correct. Participant found a valid solution."); return 0; }
-
\(T4\) 中途更换了大样例。
-
\(T3,T4\) 因为时间复杂度较大,考虑到各学校 \(OJ\) 评测机,所以
后两题没有时间,设置题目的时候用标程跑一下就行了
。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18533667,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。