现在有 A,B 两个工厂,你有 n 天的时间去生产 K 张光盘。一开始每张光盘都没有被加工,对于一张光盘,你需要先把他送到 A 工厂加工,然后将加工后的光盘送到 B 工厂再加工,最终生产出来,注意对于一个工厂每天最多加工一张光盘,但是一天内你可以将一张光盘从 A 加工再送到 B 处加工。对于 A 工厂,其第 i 天加工一张光盘的代价为 Ai , B 工厂为 Bi 。给定 N,K,Ai,Bi ,问生产 K 张光盘的最少代价为多少。
1≤K≤N≤105
0≤Ai,Bi≤109
我们先来看一个搞笑的做法。将 A 工厂拆为 n 个点,分别表示每一天的 A 工厂,记为 Ai ,同理将 B 工厂拆为 n 个点,接着原点 S 向 Ai 连流量为1费用为 Ai 的边, Ai 向 Bi 连流量为1费用为0的边, Bi 向汇点 T 连流量为1费用为 Bi 的边, Bi 向 Bi+1 连流量为正无穷费用为0的边,然后跑一遍总流量为 K 的最小费用最大流即可。
这个算法的正确性比较显然,一个光盘可以视为1个流量,并且一个光盘必须在 Ai,Bj 生产,满足 i≤j 。
接下来我们基本上都是立足于这个基本模型来解决问题。
不妨将每个 Ai 视为1个左括号,将每个 Bi 视为一个右括号。一开始他们排成了 ()()()⋯ 。
我们最终的方案,相当于从这个序列中提取出一个权值最小的子序列,并且他是一个合法的括号序列,包含恰有 K 个左括号与右括号。
首先我们每次必然可以取出一个 () ,将他们加入当前的序列中。这样必然是合法的。
但考虑另外一种情况,我们是可以取出 )( ,比如说一开始的序列为 (()) ,加入 )( 就变成了 (()()) ,这也是一个合法的情况。
不妨将 ( 视为+1,将 ) 视为-1,记 Si 表示前 i 个字符的前缀和。那么一个合法的括号序列,必须满足 ∀jSj≥0,SN=0 ,注意到最后一个条件,假如序列中左括号与右括号数量相同,那是必然满足的。
考虑假如当前取出 () ,位置分别为 L,R ,那么相当于对 [L,R−1] 的 Si 都加上1.
假如当前取出 )( ,那么相当于对 [L,R−1] 的 Si 都减去1,因为要合法,那么当前的 )( 合法的充要条件为 minR−1i=L(Si)>0 。
接下来的做法就比较高能了。
我们不妨用线段树来维护一些(dui)值。
设当前结点代表的区间为 [L,R]
1. ma 表示权值最小的 (
2. mb 表示权值最小的 )
3. lr 表示权值最小的 ()
4. rl 表示权值最小的 )(
5. lrl 表示权值最小的 )( ,设位置为 l,r ,必须满足 minr−1i=lSi>minRi=LSi
6. min 表示区间最小的 Si
7. la 表示权值最小的 ( ,设位置为 l ,满足 minli=LSi>minRi=LSi
8. lb 表示权值最小的 ) ,设位置为 r ,满足 minRi=rSi>minRi=LSi
9. tag 表示区间中的 Si 都要加上 tag
那么这样我们就可以轻松(蛤铪)进行合并了。最终合法的 )( 就相当于 [1,N] 的 lrl ,因为 SN 必然等于0。假如当前要合并 p=[L,mid],q=[mid+1,R] ,我们分情况讨论 p.min<q.min,p.min=q.min,p.min>q.min 即可。比如当前 p.min<q.min ,那么 la=p.la ,因为假如跨越了 p ,那么最小值就必然等于区间最小值了。 lb 可以是 p.lb,q.mb , lrl 可以是 p.lrl,p.lb+q.ma 。以此类推。
最终我们每次取出最小的 () 或 )( ,模拟一下就好了。算法的正确性可以从一开始搞笑的做法推断出来。
总的时间复杂度为 O(nlogn) 。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fe first
#define se second
using namespace std;
const int MAXN = 200005;
typedef pair<int,int> P;
struct Node
{
P lr,rl,lrl;
int Min,tag,ma,mb,la,lb;
}T[MAXN * 4];
int S[MAXN],N,K;
P operator +(const P &a,const P &b)
{
return S[a.fe] + S[a.se] < S[b.fe] + S[b.se] ? a : b;
}
int mint(int a,int b)
{
return S[a] < S[b] ? a : b;
}
Node operator +(const Node &a,const Node &b)
{
Node tmp;
tmp.lr = (a.lr + b.lr) + P(a.ma,b.mb);
tmp.rl = (a.rl + b.rl) + P(a.mb,b.ma);
tmp.ma = mint(a.ma,b.ma),tmp.mb = mint(a.mb,b.mb);
tmp.Min = min(a.Min,b.Min);
if (a.Min < b.Min)
{
tmp.la = a.la;
tmp.lb = mint(a.lb,b.mb);
tmp.lrl = a.lrl + (b.rl + P(a.lb,b.ma));
}
if (a.Min == b.Min)
{
tmp.la = a.la,tmp.lb = b.lb;
tmp.lrl = a.lrl + b.lrl + P(a.lb,b.la);
}
if (a.Min > b.Min)
{
tmp.la = mint(b.la,a.ma);
tmp.lb = b.lb;
tmp.lrl = a.rl + P(a.mb,b.la) + b.lrl;
}
tmp.tag = 0;
return tmp;
}
void Mark(int jd,int tag)
{
T[jd].Min += tag;
T[jd].tag += tag;
}
void Lazy_Down(int jd)
{
if (!T[jd].tag) return;
Mark(jd << 1,T[jd].tag),Mark(jd << 1 | 1,T[jd].tag);
T[jd].tag = 0;
}
void Build(int l,int r,int jd)
{
if (l == r)
{
T[jd].Min = 0;
if (l & 1) T[jd].ma = l; else T[jd].mb = l;
return;
}
int mid = l + r >> 1;
Build(l,mid,jd << 1),Build(mid + 1,r,jd << 1 | 1);
T[jd] = T[jd << 1] + T[jd << 1 | 1];
}
void Change(int l,int r,int i,int j,int s,int jd)
{
if (j < l || i > r) return;
if (i <= l && r <= j) Mark(jd,s); else
{
int mid = l + r >> 1;
Lazy_Down(jd);
Change(l,mid,i,j,s,jd << 1),Change(mid + 1,r,i,j,s,jd << 1 | 1);
T[jd] = T[jd << 1] + T[jd << 1 | 1];
}
}
void Upd(int l,int r,int p,int jd)
{
if (l == r) return;
int mid = l + r >> 1;
Lazy_Down(jd);
if (p <= mid) Upd(l,mid,p,jd << 1); else
Upd(mid + 1,r,p,jd << 1 | 1);
T[jd] = T[jd << 1] + T[jd << 1 | 1];
}
int main()
{
scanf("%d%d", &N, &K);
for(int i = 1,x = 0;i <= N;i ++)
{
scanf("%d", &x);
S[(i * 2) - 1] = x;
}
for(int i = 1,x = 0;i <= N;i ++)
{
scanf("%d", &x);
S[(i * 2)] = x;
}
S[0] = (1 << 30) - 1;
Build(1,2 * N,1);
long long ans = 0;
for(int i = 1;i <= K;i ++)
{
P cr = T[1].lr + T[1].lrl;
int x = cr.fe,y = cr.se;
if (x & 1) Change(1,2 * N,x,y - 1,1,1); else
Change(1,2 * N,x,y - 1,-1,1);
ans += S[x] + S[y];
S[x] = S[y] = (1 << 30) - 1;
Upd(1,2 * N,x,1),Upd(1,2 * N,y,1);
}
printf("%lld\n", ans);
return 0;
}