2025--炼石计划-- 11 月 13 日 --NOIP 模拟赛 #20
2025--炼石计划-- 11 月 13 日 --NOIP 模拟赛 #20
\(T1\) A. 邻间的骰子之舞 \(70pts\)
-
等价于求在满足 \(\prod\limits_{i=1}^{m}k_{i}>n(k_{i} \ge 2)\) 的情况下 \(xm+(-m+\sum\limits_{i=1}^{m}k_{i})y\) 的最小值。
-
\(m\) 上界为 \(\left\lceil \log_{2}n \right\rceil\) ,在 \(n=10^{18}\) 时取到 \(60\) 。
-
部分分
- 子任务 \(1\) :打表加手摸。
- 子任务 \(3\) :爆搜。
点击查看代码
ll ans=0x7f7f7f7f; void dfs(ll dep,ll n,ll mul,ll sum,ll x,ll y) { if(mul>n) { ans=min(ans,dep*x+(sum-dep)*y); return; } for(ll i=2;i<=n+1;i++) { dfs(dep+1,n,i*mul,sum+i,x,y); if(i*mul>n) { break; } } } int main() { #define Isaac #ifdef Isaac freopen("dice.in","r",stdin); freopen("dice.out","w",stdout); #endif ll n,m,dis,cnt,x,y,i,j,k; cin>>n>>x>>y; if(x==1&&y==1) { ans=2; cnt=dis=m=1; for(cnt=dis=m=1,ans=2;m<n;m+=dis) { if(cnt%3==0) { dis*=2; } if(cnt%3==1) { dis+=dis/2; } ans++; cnt++; if(n<=m+dis-1) { break; } } } else { dfs(0,n,1,0,x,y); } cout<<ans<<endl; return 0; }
-
正解
- 考虑枚举 \(m\) ,此时需要求 \(\sum\limits_{i=1}^{m}k_{i}\) 的最小值,由均值不等式先令 \(k_{i}=\max(2,\left\lfloor \sqrt[m]{n} \right\rfloor)\) ,然后再进行适当调整加 \(1\) 。
- 在对拍时发现直接使用
pow(n,1.0/m)
可能会出现精度问题,多调整一遍即可。 - 中途会爆
long long
,使用unsigned long long
代替即可。
点击查看代码
ull f[70][70]; int main() { #define Isaac #ifdef Isaac freopen("dice.in","r",stdin); freopen("dice.out","w",stdout); #endif ull n,x,y,ans=0x7f7f7f7f7f7f7f7f,sum,mul,i,j; cin>>n>>x>>y; for(i=1;i<=60;i++) { sum=0; mul=1; for(j=1;j<=i;j++) { f[i][j]=max(2.0,pow(n,1.0/i)); mul*=f[i][j]; sum+=f[i][j]; } while(mul<=n)//至多对全局加两遍 { for(j=1;j<=i&&mul<=n;j++) { mul/=f[i][j]; f[i][j]++; mul*=f[i][j]; sum++; } } ans=min(ans,x*i+(sum-i)*y); } cout<<ans<<endl; return 0; }
\(T2\) B. 星海浮沉录 \(20pts\)
-
部分分
- \(20pts\) :观察到 \(\operatorname{mex}\) 随区间长度增大不降,只取长度 \(=x\) 的区间进行判断。权值线段树维护即可。
点击查看代码
struct SMT { struct SegmentTree { int len,sum,cnt; }tree[500010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } void build(int rt,int l,int r) { tree[rt].len=r-l+1; tree[rt].sum=tree[rt].cnt=0; if(l==r) { return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void update(int rt,int l,int r,int pos,int val) { if(l==r) { tree[rt].cnt+=val; tree[rt].sum=(tree[rt].cnt>=1); return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } int query(int rt,int l,int r) { if(l==r) { return l; } int mid=(l+r)/2; if(tree[lson(rt)].sum==tree[lson(rt)].len) { return query(rson(rt),mid+1,r); } else { return query(lson(rt),l,mid); } } }T; int a[500010]; int query(int n,int x) { T.build(1,0,n); int ans=0x7f7f7f7f; for(int i=1;i<=x;i++) { T.update(1,0,n,a[i],1); } ans=min(ans,T.query(1,0,n)); for(int i=x+1;i<=n;i++) { T.update(1,0,n,a[i-x],-1); T.update(1,0,n,a[i],1); ans=min(ans,T.query(1,0,n)); } return ans; } int main() { #define Isaac #ifdef Isaac freopen("star.in","r",stdin); freopen("star.out","w",stdout); #endif int n,q,pd,x,i; scanf("%d%d",&n,&q); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } for(i=1;i<=q;i++) { scanf("%d%d",&pd,&x); if(pd==1) { swap(a[x],a[x+1]); } else { printf("%d\n",query(n,x)); } } return 0; }
-
正解
- 考虑找到一个最小的 \(k \in [0,n]\) 使得存在一个长度 \(=x\) 的区间不包含 \(k\) 。即若设 \(pos_{i,j}\) 表示 \(i\) 在原数列中第 \(j\) 次出现的位置,等价于求最小的 \(k\) 满足存在 \(j\) 使得 \(pos_{k,j}-pos_{k,j-1}>x\) 。
- 每个数的全局 \(\max\) 临项差修改时使用
multiset
一并维护,线段树维护前缀 \(\max\) 后在线段树上二分即可。
点击查看代码
int a[500010],id[500010]; vector<int>pos[500010]; multiset<int>s[500010]; struct SMT { struct SegmentTree { int maxx; }tree[2000010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx); } void build(int rt,int l,int r) { if(l==r) { tree[rt].maxx=*prev(s[l].end()); return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(int rt,int l,int r,int pos,int val) { if(l==r) { tree[rt].maxx=val; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } int query(int rt,int l,int r,int k) { if(l==r) { return l; } int mid=(l+r)/2; if(tree[lson(rt)].maxx>k) { return query(lson(rt),l,mid,k); } else { return query(rson(rt),mid+1,r,k); } } }T; int main() { #define Isaac #ifdef Isaac freopen("star.in","r",stdin); freopen("star.out","w",stdout); #endif int n,q,pd,x,i; cin>>n>>q; for(i=0;i<=n;i++) { pos[i].push_back(0); } for(i=1;i<=n;i++) { cin>>a[i]; id[i]=pos[a[i]].size(); s[a[i]].insert(i-pos[a[i]].back()); pos[a[i]].push_back(i); } for(i=0;i<=n;i++) { s[i].insert(n+1-pos[i].back()); pos[i].push_back(n+1); } T.build(1,0,n); for(i=1;i<=q;i++) { cin>>pd>>x; if(pd==1) { if(a[x]!=a[x+1]) { s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]]-pos[a[x]][id[x]-1])); s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]+1]-pos[a[x]][id[x]])); pos[a[x]][id[x]]++; s[a[x]].insert(pos[a[x]][id[x]]-pos[a[x]][id[x]-1]); s[a[x]].insert(pos[a[x]][id[x]+1]-pos[a[x]][id[x]]); T.update(1,0,n,a[x],*prev(s[a[x]].end())); x++; s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]]-pos[a[x]][id[x]-1])); s[a[x]].erase(s[a[x]].find(pos[a[x]][id[x]+1]-pos[a[x]][id[x]])); pos[a[x]][id[x]]--; s[a[x]].insert(pos[a[x]][id[x]]-pos[a[x]][id[x]-1]); s[a[x]].insert(pos[a[x]][id[x]+1]-pos[a[x]][id[x]]); T.update(1,0,n,a[x],*prev(s[a[x]].end())); swap(a[x],a[x-1]); swap(id[x],id[x-1]); } } else { cout<<T.query(1,0,n,x)<<endl; } } return 0; }
\(T3\) C. 勾指起誓 \(12pts\)
-
部分分
- \(Subtask 1\) :模拟。
点击查看代码
const ll p=998244353; ll ans[30],a[100010]; char c[100010][30]; vector<ll>tmp; set<ll>s; set<ll>::iterator it; int main() { #define Issac #ifdef Issac freopen("yilihun.in","r",stdin); freopen("yilihun.out","w",stdout); #endif ll n,m,cnt,i; scanf("%lld%lld",&n,&m); for(i=1;i<=n;i++) { scanf("%s",c[i]+1); a[i]=i; } do { s.clear(); for(i=1;i<=m;i++) { s.insert(i); } for(i=1;i<=n;i++) { cnt=0; for(it=s.begin();it!=s.end();it++) { cnt+=(c[a[i]][*it]=='1'); } if(cnt==0||cnt==s.size()) { continue; } for(it=s.begin();it!=s.end();it++) { if(c[a[i]][*it]=='0') { tmp.push_back(*it); } } while(tmp.empty()==0) { s.erase(tmp.back()); tmp.pop_back(); } } for(it=s.begin();it!=s.end();it++) { ans[*it]=(ans[*it]+1)%p; } }while(next_permutation(a+1,a+1+n)); for(i=1;i<=m;i++) { printf("%lld\n",ans[i]); } return 0; }
-
正解
\(T4\) D. 第八交响曲 \(0pts\)
-
部分分
- \(10pts\) :\(\frac{n(n-1)}{2}\) 次暴力判断。
点击查看代码
int main() { #define Isaac #ifdef Isaac freopen("symphony.in","r",stdin); freopen("symphony.out","w",stdout); #endif int n,i,j; cin>>n; cout<<n*(n-1)/2<<endl; for(i=n;i>=1;i--) { for(j=1;j<i;j++) { cout<<"CMPSWP R"<<j<<" R"<<i<<endl; } } return 0; }
-
正解
- 观察到部分排序间可以并行,考虑双调排序。
- 双调序列是指满足先单调递增后单调递减(单峰)或先单调递减后单调递增(单谷),或者可以通过循环位移满足上述条件的序列。
- \(Batcher\) 定理:将一个长度为 \(2n\) 的双调序列 \(\{ a \}\) 分成 \([1,n],[n+1,2n]\) 两部分,分别将 \(a_{i}\) 与 \(a_{i+n}(i \in [1,n])\) 进行比较,较大者放入 \(\max\) 序列,较小者放入 \(\min\) 序列,则得到的 \(\max,\min\) 序列仍为双调序列,且 \(\max\) 序列中的任意一个元素不小于 \(\min\) 序列中的任意一个元素。
- 分讨加手摸即可证明。
- 双调排序的基本思路是将原序列排序成双调序列,然后将双调序列排序成单调序列。
- 对于前者先将原序列分成两半,前后分别递归排序成递增/递减序列,然后就得到了一个双调序列。
- 将后面的序列翻转后就变成了单峰序列,然后利用 \(Batcher\) 定理进行排序。
- 先将 \(n\) 扩充成 \(2\) 的整数次幂,空位补充一个极大值,最后再进行忽略。
- 分治树的每一层所有操作均可以并行进行。
- 单位时间 数为 \(\sum\limits_{i=1}^{\left\lceil \log_{2}n \right\rceil}i=\frac{\left\lceil \log_{2}n \right\rceil(\left\lceil \log_{2}n \right\rceil+1)}{2}\)
点击查看代码
int pos[210],id[210][210],cnt=0; vector<pair<int,int> >ans[210]; void divide(int l,int r,int dep,int hdep) { if(l==r) { return; } if(id[dep][hdep]==0) { cnt++; id[dep][hdep]=cnt; } int mid=(l+r)/2; for(int i=l;i<=mid;i++) { if(mid+i-l+1<=r) { ans[id[dep][hdep]].push_back(make_pair(i,mid+i-l+1));//分成两半进行比较 } } divide(l,mid,dep+1,hdep); divide(mid+1,r,dep+1,hdep); } void solve(int l,int r,int dep) { if(l==r) { return; } int mid=(l+r)/2; solve(l,mid,dep+1); solve(mid+1,r,dep+1); if(pos[dep]==0) { cnt++; pos[dep]=cnt; } for(int i=1;i<=mid;i++) { if(l+i-1<r-i+1)//翻转 { ans[pos[dep]].push_back(make_pair(l+i-1,r-i+1)); } } divide(l,mid,dep+1,dep); divide(mid+1,r,dep+1,dep); } int main() { #define Isaac #ifdef Isaac freopen("symphony.in","r",stdin); freopen("symphony.out","w",stdout); #endif int n,len=1,i,j; cin>>n; for(len=1;len<n;len<<=1); solve(1,len,1); cout<<cnt<<endl; for(int i=1;i<=cnt;i++) { for(int j=0;j<ans[i].size();j++) { if(ans[i][j].first<=n&&ans[i][j].second<=n) { cout<<"CMPSWP R"<<ans[i][j].first<<" R"<<ans[i][j].second<<" "; } } cout<<endl; } return 0; }
- 观察到部分排序间可以并行,考虑双调排序。
总结
- \(T1\) 炸
long long
了,挂了 \(30pts\) ;最后 \(20 \min\) 才成功口胡出正解。 - \(T4\) 多写了个
=
,挂了 \(10pts\) 。
后记
-
\(T4\) 附加文件仅下发了
checker.cpp
,但没下发testlib
。而且checker.cpp
检验合法时直接rand()
排列进行判断。点击查看代码
// Author : HeRaNO // Modified by YunQian #include "testlib.h" #define MAXN 105 const int testCase = 100; const int changeTime = 5; const int swapTime = 5; int N[] = {0, 8, 13, 16, 32, 53, 64, 73, 82, 91, 100}; int T[11][3]={ {0, 0, 0}, {28, 9, 6}, {78, 14, 10}, {120, 17, 10}, {496, 33, 15}, {1378, 54, 21}, {2016, 65, 21}, {2628, 74, 28}, {3321, 83, 28}, {4095, 92, 29}, {4950, 101, 30} }; int n; bool isdigit(std::string &s) { for (char i : s) if (!(i >= '0' && i <= '9')) return false; return true; } std::vector<std::pair<int, int>> valid(int line, std::vector<std::string> &instructions) { std::vector<bool> vis(n + 1, false); std::vector<std::pair<int, int>> res; quitif(instructions.size() % 3 != 0, _wa, "wrong parameters format (line #%d)", line); for (int i = 0; i < instructions.size() / 3; i++) { std::string instruction = instructions[3 * i]; std::string R1 = instructions[3 * i + 1]; std::string R2 = instructions[3 * i + 2]; quitif(instruction != "CMPSWP", _wa, "wrong instruction name: %s (line #%d - instruction #%d)", instruction.c_str(), line, i + 1); quitif(R1.front() != 'R', _wa, "wrong register Ri name: %s (line #%d - instruction #%d)", R1.c_str(), line, i + 1); quitif(R2.front() != 'R', _wa, "wrong register Rj name: %s (line #%d - instruction #%d)", R2.c_str(), line, i + 1); int r1, r2; R1 = R1.substr(1); R2 = R2.substr(1); quitif(!isdigit(R1), _wa, "wrong register Ri format: R%s (line #%d - instruction #%d)", R1.c_str(), line, i + 1); quitif(!isdigit(R2), _wa, "wrong register Rj format: R%s (line #%d - instruction #%d)", R2.c_str(), line, i + 1); try { r1 = std::stoi(R1); r2 = std::stoi(R2); } catch (std::exception& err) { quitf(_wa, "wrong register format: %s (line #%d - instruction #%d)", err.what(), line, i + 1); } quitif(!(1 <= r1 && r1 <= n), _wa, "wrong register Ri format: i < 1 or i > n (line #%d - instruction #%d)", line, i + 1); quitif(!(1 <= r2 && r2 <= n), _wa, "wrong register Ri format: i < 1 or i > n (line #%d - instruction #%d)", line, i + 1); quitif(r1 == r2, _wa, "R%d is used twice in line #%d", r1, line); quitif(vis[r1], _wa, "R%d is used twice in line#%d", r1, line); quitif(vis[r2], _wa, "R%d is used twice in line#%d", r2, line); vis[r1] = vis[r2] = true; res.push_back({r1, r2}); } return res; } void cmpswp(int &a, int &b) { if (a > b) std::swap(a, b); return ; } bool canSwap(std::vector<int> rs, const std::vector<std::pair<int, int>> &allSwapR) { for (auto [r1, r2] : allSwapR) cmpswp(rs[r1 - 1], rs[r2 - 1]); return std::is_sorted(rs.begin(), rs.end()); } void testSort(const std::vector<std::pair<int, int>> &allSwapR, bool isRandom = true, bool desc = false) { for (int change = 1; change <= changeTime; change++) { std::vector<int> rs(n, 0); for (int i = 0; i < n; i++) rs[i] = rnd.next(n); if (!isRandom) { if (desc) std::sort(rs.begin(), rs.end(), std::greater<int>()); else std::sort(rs.begin(), rs.end()); } for (int i = 1; i <= testCase; i++) { if (!canSwap(rs, allSwapR)) quitf(_wa, "the commands are wrong - test case %d (random: %s, desc: %s)", i, isRandom ? "true" : "false", desc ? "true" : "false"); int swapT = rnd.next(1, swapTime); while (swapT--) { // std::vector<int> swapPos = rnd.distinct(2, 0, n - 1); // int a = swapPos.front(); // int b = swapPos.back(); int a=rnd.next(n), b=rnd.next(n - 1); if(b>=a)b++; std::swap(rs[a], rs[b]); } } } return ; } void registerGenForTest(int argc, char *argv[]) { random_t::version = 1; rnd.setSeed(argc, argv); return ; } int main(int argc, char *argv[]) { registerTestlibCmd(argc, argv); registerGenForTest(argc, argv); n = inf.readInt(); int testCase = 0; for (int i = 1; i <= 10; i++) if (N[i] == n) testCase = i; quitif(!testCase, _fail, "didn't found the testcase which n = %d", n); std::vector<std::pair<int, int>> allSwapR; int t1 = T[testCase][0]; int t2 = T[testCase][1]; int t3 = T[testCase][2]; int t = ouf.readInt(); ouf.nextLine(); quitif(t > t1, _wa, "t > t1"); for (int i = 1; i <= t; i++) { std::string program; std::vector<std::string> instructions; std::vector<std::pair<int, int>> swapR; ouf.readLineTo(program); instructions = split(trim(program), ' '); swapR = valid(i, instructions); for (auto item: swapR) allSwapR.push_back(item); } testSort(allSwapR); testSort(allSwapR, false); testSort(allSwapR, false, true); if (t1 >= t && t > t2) quitp(round(10.0 + 20.0 / (t - t2))/100.0, "correct answer with t1 = %d >= t = %d > t2 = %d", t1, t, t2); if (t2 >= t && t > t3) quitp(round(30.0 + 70.0 * (t2 - t + 1) / (t2 - t3))/100.0, "correct answer with t2 = %d >= t = %d > t3 = %d", t2, t, t3); quitf(_ok, "correct answer with t = %d <= t3 = %d", t, t3); return 0; }
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18546780,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。