P10134 [USACO24JAN] Cowmpetency S 题解
给定长度为 \(n\) 的数组 \(c\),其中有一些位置不确定,以及 \(q\) 条限制 \((a_i,h_i)\),要求满足 \(h_i\) 是第一个严格大于 \(c_1,\ldots,c_{a_i}\) 的位置且字典序最小。
首先对限制条件进行转化,一个限制 \((a_i,h_i)\) 代表 \(c_{h_i}>\max_{j=1}^{a_i}{c_j}\)。其实还有隐含条件,对于 \(a_i<k<h_i\),有 \(c_k\leq \max_{j=1}^{a_i}{c_j}\)。
那么有以下两种情况是无解的:
- \(a_i=a_j,h_i\neq h_j\),第一个大于前面最大值的位置显然是唯一的;
- \(a_i<a_{i+1},a_{i+1}<h_i\),由于 \(\max_{j=a_i+1}^{a_{i+1}-1}{c_j}<c_{h_i}\),所以 \(\max(c_1,\ldots,c_{a_i})=\max(c_1,\ldots,c_{a_{i+1}})\),那么显然第一个大于这个的位置也是唯一的。
接下来就可以开始填数了。为了结果最小,先默认填成 \(1\)。挨个处理限制,现在要保证前 \(a_i\) 个数的最大值不能小于区间 \((a_i,h_i)\) 的最大值(否则 \(h_i\) 就不是第一个大的位置了)。如果当前前 \(a_i\) 个数最大值不够,那么就挑一个最靠近 \(a_i\) 的位置升高来满足条件。但这个位置不能随便选,如果这个点 \(x\) 满足 \(a_k<x<h_k\),升高了 \(x\),那么 \(k\) 这条限制中前 \(a_k\) 个最大值就有可能不满足条件了,又要升高靠近 \(a_k\) 的位置,没完没了。所以在 \((a_k,h_k)\) 中的位置不能选。
因为是从左到右枚举的,可以用一个栈维护,栈顶自然就是最靠近 \(a_i\) 可以升高的位置。枚举 \(1\) 到 \(n\),如果 \(i\) 待修改并且不在任意的 \((a_k,h_k)\) 中就进栈。维护前缀最大值 \(m1\)。求 \((a_i,h_i)\) 范围内的最大值 \(m2\)。
- 如果当前位置是某个左端点 \(i=a_k\):
- 如果 \(m1<m2\),说明需要升高。如果此时栈空说明没有可选位置,无解。否则取栈顶的位置修改为 \(m2\)。
- 更新前缀最大值
- 如果当前位置是某个右端点 \(i=h_k\):
- 如果 \(c_i\) 可以修改,为了满足限制,\(c_i\) 修改为 \(m2+1\)。
这样做有小概率会不对,最后再判一次是否满足这些条件:
- \(c_k\leq \max_{j=1}^{a_i}{c_j}\),其中 \(k\in(a_i,h_i)\)
- \(c_{h_i}>\max_{j=1}^{a_i}{c_j}\)
- \(c_i\leq C\)
整个算法可以做到 \(O(n+q)\)。
代码有注释。
#include<bits/stdc++.h>
#define L(a,b,c,d) for(int a=b;a<=c;a+=d)
#define R(a,b,c,d) for(int a=b;a>=c;a-=d)
using namespace std;
typedef long long i64;
typedef __int128 i128;
const int N=2e5+5;
void solve();
signed main(){
int Test=1;
scanf("%d",&Test);
while(Test--) solve();
return 0;
}
void solve(){
int n,q,C;
scanf("%d%d%d",&n,&q,&C);
vector<int> c(n+1);
vector<bool> f(n+1,0); // 是否是需要填的位置
L(i,1,n,1){
scanf("%d",&c[i]);
if(c[i]==0) f[i]=1;
}
vector<pair<int,int>> p(q);
vector<int> h(n+1,0); // 表示一对限制
L(i,0,q-1,1){
scanf("%d%d",&p[i].first,&p[i].second);
if(h[p[i].first]!=0&&h[p[i].first]!=p[i].second){ // 如果有 a 相同但 h 不同的情况,第一个大的位置显然是唯一的,因此无解
printf("-1\n");
return;
}
h[p[i].first]=p[i].second;
}
int t=0; // 当前的右端点
vector<bool> s(n+1,0); // 是否处于某个 (a,h) 区间内
L(i,1,n,1){
if(t>i) s[i]=1; // 说明 i 在某个 (a,h) 内
if(h[i]){ // i 在区间内且 i 对应的右端点不等于区间右端点,max(1...a) 和 max(1...i) 相等,所以不可能有这个情况
if(t>i&&h[i]!=t){
printf("-1\n");
return;
}
t=max(t,h[i]);
}
}
vector<int> st,b(n+1,0); // 栈,一个位置至少是多少
int mx=0,hx=0; // 前缀最大值,当前右端点
L(i,1,n,1){
if(f[i]&&!s[i]) st.push_back(i); // 需要填并且不在限制区间内,可以用于补救
if(f[i]) c[i]=max(1,b[i]); // 需要填,那么就至少是 b[i]
mx=max(mx,c[i]); // 更新前缀最大值
if(h[i]&&hx==0){
hx=h[i]; // 更新右端点
int mxr=0; // 限制区间的最大值
L(k,i+1,hx-1,1) mxr=max(mxr,(c[k]==0)?1:c[k]);
if(mx<mxr){ // 前缀最大值小于限制区间最大值,需要补救
if(st.empty()){ // 没有可供补救的位置,无解
printf("-1\n");
return;
}
c[st.back()]=mxr; // 补救的位置拔高成区间最大值
mx=max(mx,mxr); // 更新前缀最大值
}
b[hx]=max(b[hx],mx+1); // 右端点这个位置满足限制至少是前缀最大值+1
}
if(i==hx-1) hx=0;
}
vector<int> pre(n+1,0); // 前缀最大值
L(i,1,n,1){
pre[i]=max(pre[i-1],c[i]);
if(c[i]>C||c[i]<1){ // 满足条件
printf("-1\n");
return;
}
}
for(auto [x,y]:p){
if(c[y]<=pre[x]||pre[y-1]!=pre[x]){ // 某个 h 不大于 1...a 最大值 或者 区间 a+1...h-1 大于 1...a
printf("-1\n");
return;
}
}
L(i,1,n,1) printf("%d%c",c[i],(i==n)?'\n':' ');
}
本文来自博客园,作者:jess1ca1o0g3,转载请注明原文链接:https://www.cnblogs.com/jess1ca1o0g3/p/22456147

浙公网安备 33010602011771号