CF1930C Lexicographically Largest 题解
发表于|更新于
|阅读量:
前言
很有意思的一道题。
做法参考了 CF 官方题解。
思路
考虑进行一次操作会对其他元素造成的影响。
删除 ai 会使其右边的所有元素下标减少 1,而我们的任务是让最后得到的集合 S 字典序最大,所以我们希望每个数的下标都尽量不要减少。
但问题在于 S 并不是一个可重集,故我们不能直接从右往左执行操作,因为这样给 S 带来的元素虽然是最大的,但可能会出现重复的情况,例如样例里的 2 1
,如果先删右边的 1 再删 2 就会带来两个重复的 3,去重后字典序小于答案 3 2
。
于是我们可以考虑每个数的下标减小了几次,记作 ci (0≤ci<i),则任务就变成了对于每个 ai 找到最小的 ci,使得对于任意 j<i,ai+i−ci=aj+j−cj。发现无论求解 ci 的顺序如何,答案最优性都不会受到影响。
同时,由于 c1=0,如果 a1 在 a2 之前删除则 c2=1(反之 c2=0),继续推导可得任意一个满足 0≤ci<i 的序列 c 都是可以被某种操作顺序构造出来的,于是答案的可行性也得到了保证。
现在的问题就变成了怎么找当前最小的 ci,满足 ai+i−ci 还没有在答案里出现过。
如果对于所有 ci∈[0,x),ai+i−ci 都出现过,则存在j<i 使得 ai+i−(x−1)=aj+j+cj 且不存在 k<i 使 ai+i−x=ak+k−ck。第一个式子可以化简为 ai+i−x=aj+j+cj−1。也就是说,当前的答案 vi 只有两种情况,如果是 ai+i 没有出现过则 ci=0,vi 直接等于 ai+i,否则 vi=max{vj−1} (j<i,ai+i≤vj−1)。
实现方面可以用两个 set 维护之前出现过的答案和待选的答案集合,如果 ai+i 出现过就在待选的集合里二分查找需要的答案。
于是这道题我们就做完了,代码比较好写。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void solve(){ read(n); for(int i=1;i<=n;i++){ read(a[i]); a[i]+=i; } vector<ll> sol; set<ll> vis,now; for(int i=1;i<=n;i++){ ll v; if(!vis.count(a[i])) v = a[i]; else{ auto it = now.upper_bound(a[i]);it--; v = *it; } sol.pb_(v); vis.insert(v);now.erase(v); if(!vis.count(v-1)) now.insert(v-1); } sort(sol.begin(),sol.end(),greater<ll>()); for(ll i:sol) cout<<i<<" "; cout<<"\n"; }
|