[NOI2019] 序列
题面#
得分情况#
100pts: 6人
>=84pts: 11人
>=64pts: 23人
题解#
20pts: n ≤ 18 n\leq 18 n≤18#
暴力枚举选哪
L
L
L个作为都选的,剩余的
K
−
L
K-L
K−L个贪心选取,用set
即可。
28pts: n ≤ 30 n\leq 30 n≤30#
DP,记录第一个数组选的个数,第二个数组选的个数,和公共的个数,进行转移。
时间复杂度: O ( n 4 ) O(n^4) O(n4)
40pts idea1: n ≤ 150 n\leq 150 n≤150#
将所有下标按照
a
i
+
b
i
a_i+b_i
ai+bi排序。
枚举第
L
L
L个公共下标的位置
x
x
x,我们有如下结论:
对于前
x
x
x个下标,最优解不存在一个下标在两个数组中都没有选。
结论显然,若存在,完全可以把一个公共下标放在这个位置,答案会更优。
于是一定存在一个
y
y
y,使得前
x
x
x个位置中
a
a
a恰好有
y
y
y个没选,
b
b
b恰好有
x
−
L
−
y
x-L-y
x−L−y个没选。后
n
−
x
n-x
n−x个位置中
a
a
a恰好有
K
−
(
x
−
y
)
K-(x-y)
K−(x−y)个选了,
b
b
b恰好有
K
−
L
−
y
K-L-y
K−L−y个选了。
那么我们就可以dp求出每个前缀恰好
a
a
a有
u
u
u个没选,
b
b
b有
v
v
v个没选的答案,以及后缀
a
a
a有
u
u
u个选了
b
b
b有
v
v
v个选了的答案。
实际上枚举的这个
x
x
x时一个公共位置的上界。
时间复杂度: O ( n 3 ) O(n^3) O(n3)
64pts idea1: n ≤ 2000 n\leq 2000 n≤2000#
在上述40pts的做法下继续思考。
存在一个
y
y
y,使得前
x
x
x个位置中
a
a
a恰好有
y
y
y个没选,
b
b
b恰好有
x
−
L
−
y
x-L-y
x−L−y个没选。后
n
−
x
n-x
n−x个位置中
a
a
a恰好有
K
−
x
+
y
K-x+y
K−x+y个位置选了,
b
b
b恰好有
K
−
L
−
y
K-L-y
K−L−y个位置选了。
有一种贪心的想法:选
a
a
a的时候,前面最小的
y
y
y个不选,后面最大的
K
−
x
+
y
K-x+y
K−x+y个加上;选
b
b
b的时候同理,前面最小的
x
−
L
−
y
x-L-y
x−L−y个不选,后面最大的
K
−
L
−
y
K-L-y
K−L−y个加上。
显然有个小问题:
a
a
a和
b
b
b可能同时删掉了某个下标,这样前
x
x
x个位置就有大于
L
L
L个公共的了。
但是,如果
a
a
a和
b
b
b同时没选某个下标,这个方案会比所有
a
a
a和
b
b
b没有同时没选某个下标的方案优,且这个方案显然不是最优,那么最终答案的
x
x
x肯定不是现在的枚举的这个
x
x
x。
于是预处理出前缀和后缀各自的前
x
x
x大/小和,贪心的做即可。
时间复杂度: O ( n 2 ) O(n^2) O(n2)
84pts idea1: ∑ n ≤ 3 e 5 \sum n\leq 3e5 ∑n≤3e5#
选
a
a
a前面最小的
y
y
y个删掉,后面最大的
K
−
x
+
y
K-x+y
K−x+y个加上,
b
b
b选前面最小的
x
−
L
−
y
x-L-y
x−L−y个删掉,后面最大的
K
−
L
−
y
K-L-y
K−L−y个加上。
我们设
a
0
a0
a0为
a
a
a前面
x
x
x个元素的集合,
a
1
a1
a1为
a
a
a后面
n
−
x
n-x
n−x个元素的集合,
b
0
,
b
1
b0,b1
b0,b1同理。
一个不同的思考方式是,进行
K
−
L
K-L
K−L次操作。我们每次可以:
- 将 a 0 a0 a0的最小元素删掉, a 1 a1 a1的最大元素加上。
- 将 b 0 b0 b0的最小元素删掉, b 1 b1 b1的最大元素加上。
我们可以用数据结构对任意
u
u
u次计算出第
u
u
u次操作
1
1
1或操作
2
2
2的增量。
不妨假设最后一次我们进行的是操作
1
1
1,那么这意味着进行一次操作
1
1
1的增量比操作
2
2
2的增量要大,反之亦然。
于是,假设进行了
y
y
y次操作
1
1
1,
K
−
L
−
y
K-L-y
K−L−y次操作
2
2
2,那么第
K
−
L
−
y
+
1
K-L-y+1
K−L−y+1次操作
2
2
2的代价比第
y
y
y次操作
1
1
1的代价要大。注意到这样的
y
y
y是可二分的,也就是说如果第
y
y
y次操作
1
1
1的代价比第
K
−
L
−
y
+
1
K-L-y+1
K−L−y+1次操作
2
2
2的代价要小,那么对于所有
y
′
>
y
y'>y
y′>y,第
y
y
y次操作
1
1
1的代价也会比第
K
−
L
−
y
′
+
1
K-L-y'+1
K−L−y′+1次操作
2
2
2的代价要小。
于是可以二分这个
y
y
y,使用支持加入和查询第
k
k
k大的数据结构维护即可做到
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
100pts idea1:#
引理:对于
i
i
i和
j
j
j,设
x
=
i
x=i
x=i时的最优解(为了方便讨论假设最优解唯一)中,删除的下标集合为
S
i
S_i
Si,对于
x
=
j
x=j
x=j时的下标集合为
S
j
S_j
Sj,则
S
i
S_i
Si为
S
j
S_j
Sj的子集。
证明:
假设 x = i x=i x=i时的最优解存在一个下标 u u u被删除,但是在 x = j x=j x=j时该下标未被删除。
不妨假设这个下标在 a a a中,且 b b b中 j j j删除的元素个数不比 i i i删除的元素个数少。
那么我们可以尝试找一个 v ≤ i v\leq i v≤i,使得 i i i中 v v v没有被删除,但是在 j j j中被删除,然后我们可以在 j j j中用 a v a_v av替换 a u a_u au,这个替换一定不会使解变劣,否则我们可以在 i i i中进行逆操作使得 i i i的解变优。
假设找不到这样的 v ≤ i v\leq i v≤i,这说明在前 i i i个元素中 i i i中删除的元素时 j j j的真子集,我们在 b b b中尝试找一个下标 l ≤ i l\leq i l≤i使得它在 i i i中没被删除而在 j j j中被删除,如果这个下标也找不到,那么 S i S_i Si时 S j S_j Sj的子集,这意味着我们 i i i的方案本身就符合 j j j的条件,所以 x = j x=j x=j时我们可以直接用 x = i x=i x=i的答案。
我们再找一个下标 m > i m>i m>i使得它在 i i i中没被选而在 j j j中被选,根据 b b b中 j j j删除的元素个数不比 i i i删除的元素个数少的假设我们也能证明这个 m m m一定能找到。
于是我们可以用 a v a_v av替换 a u a_u au, b l b_l bl替换 b m b_m bm,同样的道理可以证明这个替换不会使解变劣。
我们根据引理容易发现这个
y
y
y时单调的,而且如果当前枚举的
y
y
y小于正确答案的
y
y
y,那么我们直接将
y
+
1
y+1
y+1答案不会变劣(这是因为
84
84
84分做法里的单调性)。
所以我们将二分改成双指针就可以将时间复杂度变为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
但是我们注意到,由于
a
a
a选前面最小的
y
y
y个删掉,
b
b
b选前面最小的
x
−
L
−
y
x-L-y
x−L−y个删掉,如果
x
x
x加一,为了保证两个数都不减,
y
y
y只能不变或者加一。
也就是说,每一步我们的可能性只有两种:
y
y
y加一或者不加。我们可以想一想这个做法是在干什么。
对于
x
=
K
x=K
x=K,我们将和是前
L
L
L大的下标都选上,然后在后面
n
−
K
n-K
n−K个下标中,
a
a
a和
b
b
b都各自选最大的
K
−
L
K-L
K−L个。
那么如果
x
x
x加一,思考一下
y
y
y加一或者不加分别对应哪些操作。
-
x
x
x这个下标在
a
a
a和
b
b
b两个数组中都选了:
操作1:将 a 0 a0 a0中的最小值删掉, a 1 a1 a1中没选的最大值加进来。
操作2:将 b 0 b0 b0中的最小值删掉, b 1 b1 b1中没选的最大值加进来。 -
x
x
x这个下标只在一个数组中选了(不妨假设是
a
a
a):
操作1:将 a 0 a0 a0中的最小值删掉, a 1 a1 a1中没选的最大值加进来, b 1 b1 b1中选了的最小值删掉。
操作2:将 b 0 b0 b0中的最小值删掉。 -
x
x
x这个下标在两个中都没选:
操作1:将 a 0 a0 a0中的最小值删掉, b 1 b1 b1中选了的最小值删掉。
操作2:将 b 0 b0 b0中的最小值删掉, a 1 a1 a1中选了的最小值删掉。
于是使用 6 6 6个优先队列即可解决这个问题。
64~84pts idea2:#
注意到对于固定的
L
L
L,答案关于
K
−
L
K-L
K−L是凸的,因此可以使用wqs二分
。
二分一个权值
w
w
w,限制公共下标数量
L
L
L但不限制
K
K
K,然后对于除了公共下标外每个多选的下标都给一个
w
w
w的额外代价,二分
w
w
w使最优解恰好为
K
K
K。
固定
w
w
w时,可以贪心求解。
40~64pts idea3: n ≤ 30 → n ≤ 2000 n\leq 30\rightarrow n\leq 2000 n≤30→n≤2000#
考虑费用流模型。
至少
L
L
L个下表相同,等价于至多有
K
−
L
K-L
K−L个下标不同。
对于每个 i i i:
连边 S → a i S\rightarrow a_i S→ai,容量为 1 1 1,边权为 a i a_i ai。
连边 b i → T b_i\rightarrow T bi→T,容量为 1 1 1,边权为 b i b_i bi。
连边 a i → b i a_i\rightarrow b_i ai→bi,容量为 1 1 1,边权为 0 0 0,表示选择一对相同的下标。
新建点 U U U, V V V,连边 U → V U\rightarrow V U→V,容量为 K − L K-L K−L(称为自由流量),边权为 0 0 0,表示选择一对不同的下标。
对每个 i i i,连边 a i → U a_i\rightarrow U ai→U, V → b i V\rightarrow b_i V→bi。
注意限制总流量为 K K K。
100pts idea3:#
考虑模拟费用流:
观察算法流程,所有和
S
S
S,
T
T
T直接相连的边不会退流,所以可以忽略这些边的反向边,而中间的边会退流,对应每一对的添加操作。
观察增光路有多少形态:
假设某次选择下标对
(
a
i
,
b
j
)
(a_i,b_j)
(ai,bj),
其中分别对应以下
5
5
5种方案:
1.
i
=
j
i=j
i=j。
2.
i
≠
j
i\not=j
i=j,消耗
1
1
1自由流量。
3.使
a
i
a_i
ai与
b
i
b_i
bi(先前已选)配对,将
b
j
b_j
bj与
a
j
a_j
aj(先前已选)配对,增加
1
1
1自由流量。
4.使
a
i
a_i
ai与
b
i
b_i
bi(先前已选)配对,
b
j
b_j
bj与先前与
b
i
b_i
bi配对的
a
x
a_x
ax配对。
5.使
b
i
b_i
bi与
a
i
a_i
ai(先前已选)配对,
a
j
a_j
aj与先前与
a
i
a_i
ai配对的
b
x
b_x
bx配对。
(其实图中还存在其他形式的增光路,但显然存在更优的形式,故只考虑这5种即可)
考虑怎么维护以上
5
5
5种方案。
可以用五个堆
q
,
q
a
,
q
b
,
q
a
2
,
q
b
2
q,q_a,q_b,q_{a_2},q_{b_2}
q,qa,qb,qa2,qb2,分别表示:
q
:
q:
q:两边都未选的
a
i
+
b
i
a_i+b_i
ai+bi的
m
a
x
max
max。
q
a
:
q_a:
qa:
A
A
A数组未选的数的
m
a
x
max
max。
q
b
q_b
qb同理。
q
a
2
:
q_{a_2}:
qa2:
a
i
a_i
ai未选而
b
i
b_i
bi已选的
a
i
m
a
x
a_{i_{max}}
aimax。
q
b
2
q_{b_2}
qb2同理。
若某堆顶不存在则视为
−
I
N
F
-INF
−INF。
用两个
b
o
o
l
bool
bool数组维护某一下标是否被取过。
当取走某一下标时,若另一侧未取,则可以对相应的
q
a
2
q_{a_2}
qa2或
q
b
2
q_{b_2}
qb2添加元素。
模拟方案的过程,取
m
a
x
max
max即可。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
具体细节参考代码:
#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
#define mkp make_pair
#define pa pair
#define fir first
#define sec second
using namespace std;
const int N=2e5+10,INF=1e9;
int T,n,K,L,A[N],B[N];bool visa[N],visb[N];
priority_queue<pa<int,int> > q,qa,qb,qa2,qb2;
void seta(int R){
visa[R]=1;
if(!visb[R]) qb2.push(mkp(B[R],R));
}
void setb(int R){
visb[R]=1;
if(!visa[R]) qa2.push(mkp(A[R],R));
}
void solve(){
scanf("%d%d%d",&n,&K,&L);int lim=K-L,mx=0,op=0;ll ans=0;
for(int i=1;i<=n;i++) scanf("%d",&A[i]);
for(int i=1;i<=n;i++) scanf("%d",&B[i]);
for(int i=1;i<=n;i++) q.push(mkp(A[i]+B[i],i)),qa.push(mkp(A[i],i)),qb.push(mkp(B[i],i));
q.push(mkp(-INF,0));qa.push(mkp(-INF,0));qb.push(mkp(-INF,0));qa2.push(mkp(-INF,0));qb2.push(mkp(-INF,0));
ans=0;
for(int i=1;i<=K;i++){
mx=op=0;
while(visa[q.top().sec]||visb[q.top().sec]) q.pop();
while(visa[qa.top().sec]) qa.pop();
while(visb[qb.top().sec]) qb.pop();
while(visa[qa2.top().sec]) qa2.pop();
while(visb[qb2.top().sec]) qb2.pop();
pa<int,int> qnow=q.top(),qanow=qa.top(),qbnow=qb.top(),qa2now=qa2.top(),qb2now=qb2.top();
mx=qnow.fir;op=1;
if(qa2now.fir+qb2now.fir>mx){
mx=qa2now.fir+qb2now.fir;
op=3;
}
if(qa2now.fir+qbnow.fir>mx){
mx=qa2now.fir+qbnow.fir;
op=4;
}
if(qanow.fir+qb2now.fir>mx){
mx=qanow.fir+qb2now.fir;
op=5;
}
if(qanow.sec!=qbnow.sec&&lim){
if(qanow.fir+qbnow.fir>mx){
mx=qanow.fir+qbnow.fir;
op=2;
}
}
if(op==1){seta(qnow.sec);setb(qnow.sec);}
if(op==2){seta(qanow.sec);setb(qbnow.sec);lim--;}
if(op==3){seta(qa2now.sec);setb(qb2now.sec);lim++;}
if(op==4){seta(qa2now.sec);setb(qbnow.sec);}
if(op==5){seta(qanow.sec);setb(qb2now.sec);}
ans+=mx;
}
printf("%lld\n",ans);
while(!q.empty()) q.pop();
while(!qa.empty()) qa.pop();
while(!qb.empty()) qb.pop();
while(!qa2.empty()) qa2.pop();
while(!qb2.empty()) qb2.pop();
for(int i=1;i<=n;i++) visa[i]=visb[i]=0;
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%d",&T);
while(T--) solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?