bzoj4426 - 最大生产率

一个突破口是:若区间 \(A\subseteq B\),则将 \(B\) 加入 \(A\) 所在集合没有丝毫影响。

我们可以将所有区间分为两类(至于有相等的区间的情况就见仁见智啦!):至少包含一个区间的区间 / 一个区间都不包含的区间。那么最终划分的集合无非就是三种:仅包含第一类、仅包含第二类、两类都包含。考虑与划分集合等效的一个过程:先将第二类进行划分集合,然后将第一类中某些区间一个一个放到第二类划分好的集合里(这个过程中集合数量不变!),然后将第一类剩下来的区间划分。不难发现第二步操作中将每个待放入的第一类区间放到它包含的第二类区间所在集合是最优的,对答案无影响,可以假装无事发生。

于是现在问题就转化为:将第二类进行划分(每个区间都要在恰好一个集合里),将第一类划分(可以有区间不被选),并且两类划分总集合数量为 \(m\) 时的最大交和。这样就可以将两类分别处理,然后用背包合并。此时第二类左端点显然关于右端点单调,那么容易引出一个线性 DP 的方案,于是只要解决第一类。第一类显然依然能通过上述方法拆出两类(只不过多出一个可以有区间不被选的条件,无伤大雅),然后一直拆拆拆,最终将 \(n\) 个区间拆成若干互不相交的层,每层跑一下,最终用背包合并。

考虑每层如何做。按照右端点(左端点)排序,最优方案显然是每个集合都是连续的一段,线性 DP \(f_{i,j}\) 表示前 \(i\) 个区间划分成 \(j\) 个集合的最大答案。枚举最右端的区间,\(f_{i,j}=\max\limits_{k\in[1,i],r_k>l_i}\{f_{k-1,j-1}+r_k-l_i\}\),如果不是拆出的第一层,还要往 \(f_{i-1,j}\) 转移因可以不选。直接做是三方的,虽然在原题上可过,但既然可以优化到两方就优化吧。容易发现 max 中候选 \(k\) 构成一个区间,且没有既关于 \(k\) 又关于 \(i\) 的项,于是可以对每个 \(j\) 使用单调队列优化。设 \(n,m\) 同阶,这样总复杂度是 \(\mathrm O(\sum S_in)=\mathrm O\!\left(n^2\right)\),其中 \(S\) 是每一层的区间数。

然后说一下关于背包合并的事情。其实就是每时每刻维护一下当前划分成 \(i\in[0,n]\) 个集合的最大答案,然后对每层用当前的 \(f_{S_i,*}\) 更新,每次更新是平方复杂度,总复杂度就是。。。三方??但不难发现合并到第 \(x\) 层时只有背包的前 \(\sum\limits_{i=1}^xS_i\)​ 层有值,那复杂度其实就是个树上背包的弱化版,可以看作将所有层当作一个点挂到根下面形成一个菊花。事实上根本不用树形背包理解,不就是 \(\sum\limits_{i<j}S_iS_j\leq n^2\) 嘛。。。以及不将 \(n,m\) 看作同阶的话,背包大小与 \(m\) 取 min,依然不需要树形背包分析,不就 \(\mathrm T\leq \sum\limits_{i<j}S_i\min(S_j,m)\leq nm\) 嘛。。。

code
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int inf=0x3f3f3f3f;
const int N=210;
int n,m;
pair<int,int> rg[N];
bool cmp(pair<int,int> x,pair<int,int> y){
	if(x.Y!=y.Y)return x.Y<y.Y;
	return x.X>y.X;
}
int dp[N][N];
int sz,knp[N],tmp[N];
int head,tail,q[N];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>rg[i].X>>rg[i].Y;
	sort(rg+1,rg+n+1,cmp);
	vector<vector<pair<int,int> > > vv;
	vector<pair<int,int> > v(rg+1,rg+n+1);
	while(v.size()){
		int mx=-1;
		vector<pair<int,int> > u,w;
		for(int i=0;i<v.size();i++){
			if(mx>=v[i].X)w.pb(v[i]);else u.pb(v[i]);
			mx=max(mx,v[i].X);
		}
		vv.pb(u),v=w;
	}
	knp[0]=0;for(int i=1;i<=n;i++)knp[i]=-inf;
	for(int i=0;i<vv.size();i++){
		v=vv[i];
//		for(int j=0;j<v.size();j++)cout<<v[j].X<<","<<v[j].Y<<" ";puts("");
		dp[0][0]=0;for(int j=1;j<=v.size();j++)dp[j][0]=i?0:-inf;
		for(int k=1;k<=n;k++){
			head=tail=0;
			dp[0][k]=-inf;
			for(int j=1;j<=v.size();j++){
				dp[j][k]=-inf;
				int l=v[j-1].X,r=v[j-1].Y;
				while(head<tail&&dp[q[tail-1]-1][k-1]+v[q[tail-1]-1].Y<=dp[j-1][k-1]+r)tail--;
				q[tail++]=j;
				while(head<tail&&v[q[head]-1].Y<=l)head++;
				if(head<tail)dp[j][k]=dp[q[head]-1][k-1]+v[q[head]-1].Y-l;
				if(i)dp[j][k]=max(dp[j][k],dp[j-1][k]);
			}
		}
		for(int j=0;j<=sz+v.size();j++)tmp[j]=-inf;
		for(int j=0;j<=sz;j++)for(int k=0;k<=v.size();k++)tmp[j+k]=max(tmp[j+k],knp[j]+dp[v.size()][k]);
		sz+=v.size();
		for(int j=0;j<=sz;j++)knp[j]=tmp[j];
//		for(int j=0;j<=sz;j++)cout<<knp[j]<<" ";puts("");
	}
	cout<<knp[m]<<"\n";
	return 0;
}

upd:稍微想想就发现 wsbl。。。。第一次拆的时候,第一类的问题由于有了不必每个区间都选的条件,并不需要继续往下拆,一个贪心就能解决了。因为对于选 \(x\) 个集合的方案,考察任意一种存在集合有超过一个区间的方案,直接将每个集合的区间扔掉直到只剩下一个区间显然会不劣。那就直接排序取前 \(x\) 个罢。

上一篇:【模板】【高精度】


下一篇:获得执行计划方法三-sql_trace