题目

题目链接:https://codeforces.com/contest/573/problem/D
\(n\) 个人和 \(n\) 匹马,第 \(i\) 个人对应第 \(i\) 匹马。第 \(i\) 个人能力值 \(w_i\),第 \(i\) 匹马能力值 \(h_i\),第 \(i\) 个人骑第 \(j\) 匹马的总能力值为 \(w_i\times h_j\),整个军队的总能力值为 \(\sum w_i\times h_j\)(一个人只能骑一匹马,一匹马只能被一个人骑)。有一个要求:每个人都不能骑自己对应的马。让你制定骑马方案,使得整个军队的总能力值最大。现在有 \(m\) 个操作,每次给出 \(a,b\),交换 \(a\)\(b\) 对应的马。每次操作后你都需要输出最大的总能力值。
\(n\leq 30000,m\leq 10000\)

思路

考虑一个最简单的问题:给定两个长度相同正整数序列 \(a,b\),要求将 \(b\) 序列重新排序,最大化 \(\sum a_i\times b_i\)
这个问题显然只需要将 \(a,b\) 分别排序然后一一匹配即可。因为如果 \(a<b<c<d\)\(ad+bc<ac+bd\)
那么再考虑本题不带修改的情况。多了每一个人不能匹配与他编号相同的马的条件。依然将两个序列排序,我们可以证明对于任意一个人 \(i\),能和它匹配的马的编号一定在 \([i-2,i+2]\) 内。注意这里是排序后的序列。
因为假设 \(a_i\) 匹配了 \(b_{i+3}\),那么在 \(a_{i+1},a_{i+2},a_{i+3}\) 中至少可以找到一个元素,交换它和 \(a_i\) 所匹配马后依然没有人匹配和它编号相同的马。画一下就很好理解了。
所以我们设 \(f_i\) 表示前 \(i\) 个人恰好匹配完前 \(i\) 匹马的最大权值,那么 \(f_i\) 只可能从 \(f_{i-3},f_{i-2},f_{i-1}\) 转移而来。

\[f_i=\max(f_{i-3}+z_{i},f_{i-2}+y_i,f_{i-1}+x_i) \]

其中 \(x_i,y_i,z_i\) 就是前 \(1,2,3\) 对分别匹配的最大贡献,可以暴力枚举所有情况。
观察到每次修改只是单点修,并且询问是全局询问(因为我们排了序后原来编号就是乱序了),而且这个转移是 \(\max\) 里面套个加法,所以直接上动态 dp 即可。

\[\begin{bmatrix} f_{i-3} & f_{i-2} & f_{i-1} \end{bmatrix} \begin{bmatrix} -\infty & -\infty& z_i \\ 0 & -\infty & y_i \\ -\infty & 0 & x_i \end{bmatrix} = \begin{bmatrix} f_{i-2} & f_{i-1} & f_{i} \end{bmatrix} \]

时间复杂度 \(O((n+m)\omega^3\log n)\),其中 \(\omega=3\)

代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=30010;
const ll Inf=1e18;
int n,m,id[N];

struct node
{
	int a,id;
}a[N],b[N];

bool cmp(node x,node y)
{
	return x.a<y.a;
}

struct Matrix
{
	ll a[4][4];
	Matrix() { memset(a,0xcf,sizeof(a)); }
	
	friend Matrix operator *(Matrix a,Matrix b)
	{
		Matrix c;
		for (int i=1;i<=3;i++)
			for (int j=1;j<=3;j++)
				for (int k=1;k<=3;k++)
					c.a[i][j]=max(c.a[i][j],a.a[i][k]+b.a[k][j]);
		return c;
	}
}mat[N],fad;

struct SegTree
{
	Matrix a[N*4];
	
	void update(int x,int l,int r,int k)
	{
		if (l==r) { a[x]=mat[l]; return; }
		int mid=(l+r)>>1;
		if (k<=mid) update(x*2,l,mid,k);
			else update(x*2+1,mid+1,r,k);
		a[x]=a[x*2]*a[x*2+1];
	}
}seg;

ll calc(int l,int r)
{
	if (l<=0) return -Inf;
	int n=r-l+1,c[5]={0,0,0,0,0}; ll ans=-Inf;
	for (int i=1;i<=n;i++) c[i]=l+i-1;
	do {
		ll sum=0;
		for (int i=1;i<=n;i++)
		{
			if (a[l+i-1].id==b[c[i]].id) { sum=-Inf; break; }
			sum+=1LL*a[l+i-1].a*b[c[i]].a;
		}
		ans=max(ans,sum);
	} while (next_permutation(c+1,c+1+n));
	return ans;
}

void upd(int i)
{
	mat[i].a[1][1]=-Inf; mat[i].a[1][2]=-Inf; mat[i].a[1][3]=calc(i-2,i);
	mat[i].a[2][1]=0; mat[i].a[2][2]=-Inf; mat[i].a[2][3]=calc(i-1,i);
	mat[i].a[3][1]=-Inf; mat[i].a[3][2]=0; mat[i].a[3][3]=calc(i,i);
	seg.update(1,1,n,i);
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i].a),a[i].id=i;
	for (int i=1;i<=n;i++)
		scanf("%d",&b[i].a),b[i].id=i;
	sort(a+1,a+1+n,cmp); sort(b+1,b+1+n,cmp);
	for (int i=1;i<=n;i++) id[b[i].id]=i;
	for (int i=1;i<=n;i++) upd(i);
	fad.a[1][3]=0;
	while (m--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		swap(b[id[x]].id,b[id[y]].id);
		swap(id[x],id[y]);
		for (int i=0;i<=2;i++)
		{
			if (id[x]+i<=n) upd(id[x]+i);
			if (id[y]+i<=n) upd(id[y]+i);
		}
		printf("%lld\n",(fad*seg.a[1]).a[1][3]);
	}
	return 0;
}