题解 P13509 [OOI 2024] Almost Certainly
cjh20090318 · · 题解
提供一个考场思路。
分析
子任务 1 :N \le 100
两个多重集完全等价时,需要的操作次数即为
几乎等价的条件允许了
对于每个前缀序列排序后询问,枚举
子任务 2 :N \le 500
同样的对于每个前缀序列排序后询问。
同样的对于每个前缀序列排序后询问。
为了让
所以找到每一个满足
总体时间复杂度
子任务 4 :a_i < b_{i+1}
根据限制得到
极长区间即为
子任务 5 :a_i \le a_{i+1}, b_i \le b_{i+1}
保证前缀序列有序,无需再排序。
但该子任务并没有保证极长区间。
只需要维护和当前
综合以上可以获得
//the code is from chenjh
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
typedef long long LL;
int n;
int a[MAXN],b[MAXN];
void solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
LL ans=0;//完全等价的代价。
int ret=0,la,lb=1;//分别表示
for(int i=1;i<=n;i++){
ans+=a[i]-b[i];
if(i>1&&a[i]>=a[i-1]&&b[i]>=b[i-1]){//已经有序则无需排序。
a[i-1]<b[i]&&(lb=max(lb,i));//不满足条件,更新极长区间起点。
ret=max(ret,a[i]-b[lb]);
}
else{
inplace_merge(a+1,a+i,a+i+1),inplace_merge(b+1,b+i,b+i+1);//懒得手写排序,所以直接归并。
la=lb=ret=0;
for(int j=i;j>0;--j)
(j>=i||a[j]<b[j+1])&&(la=a[j]),ret=max(ret,la-b[j]),a[j-1]<b[j]&&(lb=max(lb,j));
}
printf("%lld ",ans-ret);
}
putchar('\n');
}
int main(){
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}
子任务 6 :无特殊限制。
考试时败在了最后
考虑将每个极长区间表示为
如果两个区间有交集,则可以合并为同一个区间。
证明是容易的,如果区间
如果区间被已有区间完全包含,就不必合并,也不必加入,因为不会产生更大的答案。
所求为所有区间的长度最大值。
于是直接用 std::set 维护区间,时间复杂度
代码
//the code is from chenjh
#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
int n;
int a[MAXN],b[MAXN];
void solve(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
LL ans=0;
int ret=0;
set<PII> S;
for(int i=1;i<=n;i++){
ans+=a[i]-b[i];
PII c(b[i],a[i]);
bool fl=1;
while(!S.empty()){
auto it=S.lower_bound(c);
if(it!=S.end()&&it->first==c.first&&it->second>=c.second){fl=0;break;}//区间被完全包含,合并后不产生贡献,退出。
if(it!=S.begin()&&prev(it)->second>=c.second&&prev(it)->first<=c.first){fl=0;break;}
if(it!=S.end()&&it->first<=c.second)
c.second=max(c.second,it->second),S.erase(it);//向右侧合并。
else if(it!=S.begin()&&(--it)->second>=c.first)
c.first=min(c.first,it->first),S.erase(it);//向左侧合并。
else break;//没有区间有交集了,退出。
}
if(fl) S.insert(c),ret=max(ret,c.second-c.first);
printf("%lld ",ans-ret);
}
putchar('\n');
}
int main(){
int T;scanf("%d",&T);
while(T--) solve();
return 0;
}