Cfz Round 3 E / 洛谷 P10033 题解
发表于|更新于
|阅读量:
前言
我场切了,看来这场题目顺序很诈骗,明明 C 和 D 都没写出来。
等等,这道题竟然评绿了?
好激动,第一次场切绿,所以来写篇题解。
题意
给定一个 1∼n 的排列 p。你需要构造一个长度为 n 的序列 a,满足:
- 序列 a 中的每个元素均为不大于 n 的正整数;
- 不存在有序整数二元组 (l,r),满足 1≤l≤r≤n 且 i=l∑rai=i=l∑rpi;或报告无解。
其中,1∼n 的排列指满足所有不大于 n 的正整数恰好出现一次的序列。
数据范围
设 ∑n 表示单个测试点中 n 的和。
对于所有数据,1≤T≤5000,2≤n≤106,∑n≤106,保证 p 是 1∼n 的排列。
思路
我们可以尝试考虑最终答案序列 a 的形式。
a 和原序列 p 一样都由从 1 到 n 的整数构成,但不同点在于 a 并不是 1∼n 的排列,a 中的数字出现多少次都可以。
而题目里要求 ∀l,r∈[1,n],i=l∑rai=i=l∑rpi,直觉告诉我让 p 中的全部数字都大于 a 就能很轻松地满足这个条件了;
但 pi 的取值最大只能为 n,且序列 a 里必定有一个 n,所以一个全部为 n 的 p 序列已经可以满足除了 l=r=i(ai=n) 的所有情况了,因为不管怎么取 l 和 r,i=l∑rpi 都一定是大于 i=l∑rai 的。
那么 ai=n 的这一位 pi 该怎么改呢?我们顺着刚刚的思路,也给 i 这一位放一个除了 n 以外尽量大的数,设这个数 pi=n−k(1≤k<n)。
那么在取 l,r 时,如果 l>i 或 r<i,则 ∑p 的值仍为 (r−l+1)×n 不受影响,一定满足要求;
但如果 l≤i≤r,∑p 就不一定能够大于 ∑a 了。
于是我们换个思路,尝试让 pi 的值能够取到一个临界值 [lS,rS],正好使这时在 [lS,rS] 内的 ∑p<∑a,但凡 l 再比 lS 小 1 或 r 比 rS 大 1,∑p 就会超过 ∑a。
上述的这个过程我们可以通过从大到小枚举 pi (也就是 n−k),同时枚举 l 和 r 完成,看起来枚举了三个数很吓人,实际上复杂度并不高,因为临界值很快就会被取到,不信的话可以尝试构造一个能够尽量增加枚举次数的序列 a,它大概长成这个样子:
⋯n−2,n,n−1,n−3,⋯
可以看到,上面的这个序列已经很努力地在卡掉了 pi(ai=n) 在 [n−3,n−1] 中的值,但这已经是它能做到的极限了,不太优雅的证明如下:
当序列 a 能卡掉 pi=n−1、n−2 时,n−1 与 n−2 一定正好在 n 的两侧,此时 n−3 自然也被卡掉了;
想卡掉 n−4 就只能在 n−1 的同一侧放一个 n−3,但这样做你就永远卡不掉 n−5 了,因为 n−3 与 n−1 在同侧,与 n−2 在异侧;
反之亦然,如果想卡掉 n−5 那 n−4 就一定没法被卡掉。
所以我们不太优雅地证明了在 [n−5,n−1] 里一定能取到 pi。
到这里我们几乎已经解决了本题,接下来考虑序列 a 的长度 n 太小,导致 n−5<1 的情况,下面是一个例子:
显然我们取遍了 pi 也没能取到一个值不会被特定的 [l,r] 给卡掉,但有了前面的经验,我们只要反过来构造就好了:
把除了 ai=1 对应的 pi 以外的 p 值都设成 1,让 ∑p 尽量小于 ∑a,然后再枚举 pi 即可。
用上面的方法就能构造出序列 1 1 1 2
,满足条件。
如果上面两种方法都构造不出来序列 p,那就是因为序列长度 n 过短(也就是 n=2),可以证明这个时候一定无解,输出 -1
即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| int a[1000005],ans[1000005],pf[1000005]; inline int ques(int l,int r){return pf[r]-pf[l-1];} void solve() { int t;read(t); pf[0] = 0; while(t--){ int n;read(n); for(int i=1;i<=n;i++) read(a[i]); for(int i=1;i<=n;i++) pf[i] = pf[i-1]+a[i]; a[0] = 0;a[n+1] = 0; bool get = 0; for(int i=1;i<=n;i++){ if(a[i]!=n) ans[i] = n; else{ for(int j=n-1;j>=1;j--){ get = 1; int sum = n,fsum = j; for(int l=0;i-l>=1;l++){ for(int r=0;i+r<=n;r++){ sum = n+(l?ques(i-l,i-1):0)+(r?ques(i+1,i+r):0); fsum = j+(l+r)*n; if(fsum>=sum){ if(fsum==sum) get = 0; break; } } } if(get){ ans[i] = j; break; } } } } if(get){ for(int i=1;i<=n;i++) cout<<ans[i]<<" "; } else{ for(int i=1;i<=n;i++){ if(a[i]!=1) ans[i] = 1; else{ for(int j=2;j<=n;j++){ get = 1; int sum = 1,fsum = j; for(int l=0;i-l>=1;l++){ for(int r=0;i+r<=n;r++){ sum = n+(l?ques(i-l,i-1):0)+(r?ques(i+1,i+r):0); fsum = j+(l+r)*n; if(fsum<=sum){ if(fsum==sum) get = 0; break; } } } if(get){ ans[i] = j; break; } } } } if(get){ for(int i=1;i<=n;i++) cout<<ans[i]<<" "; } else cout<<-1; } cout<<"\n"; } }
|
其他
感觉自己能做出来这道题还是因为最近 cf 的毒瘤题刷的比较多,可能对思维有点用;但是这一场的 C 和 D 竟然都没写出来,数论、位运算之类的还是要多练练,以及这场虽然没考到但我的算法也非常菜。
写这篇题解是在比赛当天(2023/12/31),希望大家在新的一年里都能在 OI 上取得好成绩。
以上,如果题解里有疏漏的地方欢迎各位神犇指正。