P9338 [JOIST 2023] 合唱 / Chorus 题解

P9338 [JOIST 2023] 合唱 / Chorus

思路

其实也没有 smb 声称的那么难。

考虑神秘数形结合法是困难的,于是直接考虑转移的意义。
一个关键的观察是如果我们将 A 与 B 的位置单独拉出来,我们会发现匹配一定是一一对应的。也就是第 \([l,r]\) 个 A 如果要组成一首歌,那么一定匹配的是第 \([l,r]\) 个 B。

于是直接设 \(f_{i,j}\) 表示当前这首歌以第 \(i\) 个 A 和 B 结尾,已经完成了 \(j\) 首歌的最小代价。发现其转移是经典形式

\[f_{i,j}=\min_{k<i}f_{k,j-1}+w(k,i) \]

其中 \(w(l,r)\) 指的是中间的代价。可以观察到代价 \(w(l,r)=\sum_{i=l+1}^r \max(0,b_i-l)\)。其中 \(b_i\) 是 B 上比 A 序列上第 \(i\) 个小的个数。(实际上就是前面有多少个 B)

于是我们考虑如何计算这个 \(w\)。发现可以用类似前缀和的东西。设 \(p_i\) 表示 \(i\) 之后第一个 \(b_i>=l\) 的位置。然后设 \(s\)\(b\) 数组的前缀和。

于是有

\[w(l,r)=s_r-s_{p_l-1}-l\times (r-p_l+1) \]

然后发现可以转化成斜率优化的形式,然后发现有凸性可以套上 wqs 二分,然后做完了。

code

发现斜率与横坐标都单增,于是用优先队列优化。没写过,有点生疏。(一般都是李超线段树抛弃大脑)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+7,inf=1e9+7;
int p[N],s[N],b[N],n,K,g[N],f[N];
struct node{int x,y,i;}stk[N];
int get(node id,int k){return id.y-id.x*k;}
void calc(int val){
	int l=0,r=0;//应该将 f_0 也视为队列中有一个点(约等于初始化,可能如果初始化比较特殊还要额外调值,类似于李超线段树那样) 
	for(int i=1;i<=n;i++)f[i]=g[i]=stk[i].x=stk[i].y=stk[i].i=0;
	for(int i=1;i<=n;i++){
		while(l<r&&(get(stk[l],i)>get(stk[l+1],i)||(get(stk[l],i)==get(stk[l+1],i)&&g[stk[l].i]>g[stk[l+1].i])))l++;
		f[i]=get(stk[l],i)+s[i]+val;g[i]=g[stk[l].i]+1;
		node t={i,i*(p[i]-1)+f[i]-s[p[i]-1],i};
		while(l<r&&(t.y-stk[r].y)*(stk[r].x-stk[r-1].x)<=(stk[r].y-stk[r-1].y)*(t.x-stk[r].x))r--;
		stk[++r]=t;
	}
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>K;
	for(int i=1,tmp=0,loc=0;i<=2*n;i++){char x;cin>>x;if(x=='A')b[++loc]=tmp;else ++tmp;}
	for(int i=1;i<=n;i++)s[i]=s[i-1]+b[i];for(int i=0;i<=n;i++)p[i]=lower_bound(b+1,b+n+1,i)-b,p[i]=max(p[i],i+1);
	int l=0,r=1e12,ans=0;
	while(l<=r){
		int mid=(l+r)>>1;calc(mid);if(g[n]<=K)r=mid-1,ans=mid;
		else l=mid+1;
	}
	calc(ans);cout<<f[n]-K*ans;return 0;
}
posted @ 2025-07-31 20:36  all_for_god  阅读(24)  评论(2)    收藏  举报