[CmdOI2019]任务分配问题

一、题目

点此看题

二、解法

首先有一个朴素 d p dp dp,设 d p [ i ] [ j ] dp[i][j] dp[i][j]为选出来 i i i个段,考虑到前 j j j位的最小顺序对个数:
d p [ i ] [ j ] = d p [ i − 1 ] [ k ] + c o s t ( k + 1 , j ) dp[i][j]=dp[i-1][k]+cost(k+1,j) dp[i][j]=dp[i1][k]+cost(k+1,j)暴力做可以从后往前,每次向树状数组里面添加,然后算顺序对,时间复杂度 O ( n 2 k log ⁡ n ) O(n^2k\log n) O(n2klogn)

有一个决策单调性,就是数要划分得尽量平均,也就是多考虑一个数决策点不会前移,证明:[CmdOI2019]任务分配问题_第1张图片

其中灰色线段表示上一次的决策点, s 1 s1 s1表示红色段和黄色点产生的顺序对, s 2 s2 s2表示黄色点和绿色段产生的顺序对, s 3 s3 s3表示黄色点和新考虑的点的顺序对。由上一次的决策可知 s 1 < s 2 s1s1<s2(要不然决策点会往钱移动),所以 s 1 < s 2 + s 3 s1s1<s2+s3,这说明了决策点前移肯定不优,证明了我们尽量分得均匀的决策单调性。

怎么利用这个决策单调性呢?用一个类似整体二分的算法,函数 s o l v e ( l , r , t l , t r ) solve(l,r,tl,tr) solve(l,r,tl,tr)表示用 [ t l , t r ] [tl,tr] [tl,tr]的决策点去更新 [ l , r ] [l,r] [l,r] d p dp dp值,考虑处理出 m i d mid mid的最优决策点 p p p,那么 [ l , m i d − 1 ] [l,mid-1] [l,mid1] [ t l , p ] [tl,p] [tl,p]来更新, [ m i d + 1 , r ] [mid+1,r] [mid+1,r] [ p , t r ] [p,tr] [p,tr]来更新,这样一直递归即可。还有一个问题,怎么算顺序对,可以用莫队的方法,因为对于每一层相邻段的移动和当前段的枚举都是 O ( n ) O(n) O(n)次的,所以复杂度有保证,总复杂度 O ( n k log ⁡ n 2 ) O(nk\log n^2) O(nklogn2)

#include 
#include 
using namespace std;
const int M = 25005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,l=1,r,a[M],b[M];ll f[30][M],ans;
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int f)
{
	for(;x<=n;x+=lowbit(x))
		b[x]+=f;
}
int ask(int x)
{
	int s=0;
	for(;x>=1;x-=lowbit(x))
		s+=b[x];
	return s;
}
ll query(int L,int R)
{
	while(L<l)
	{
		ans+=r-l+1;
		ans-=ask(a[--l]);
		add(a[l],1);
	}
	while(r<R)
	{
		ans+=ask(a[++r]);
		add(a[r],1);
	}
	while(l<L)
	{
		add(a[l],-1);
		ans+=ask(a[l++]);
		ans-=r-l+1;
	}
	while(r>R)
	{
		add(a[r],-1);
		ans-=ask(a[r--]);
	}
	return ans;
}
void solve(int x,int l,int r,int tl,int tr)
{
	if(l>r || tl>tr) return ;
	int mid=(l+r)>>1,p=0;
	f[x][mid]=1e18;
	for(int i=tl;i<=min(tr,mid-1);i++)
	{
		ll tmp=f[x-1][i]+query(i+1,mid);
		if(tmp<f[x][mid]) f[x][mid]=tmp,p=i;
	}
	solve(x,l,mid-1,tl,p);
	solve(x,mid+1,r,p,tr);
}
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1;i<=n;i++)
	{
		f[1][i]=f[1][i-1]+ask(a[i]);
		add(a[i],1);
	}
	for(int i=1;i<=n;i++) b[i]=0;
	for(int i=2;i<=k;i++)
		solve(i,i,n,i-1,n);
	printf("%lld\n",f[k][n]);
}

你可能感兴趣的:(dp)