运气大战
你的班上n个同学要去参加一项集体比赛。每个人有实力值和运气值。每个人的实力值是确定的,但是运气值是飘忽不定的。一个人的发挥是他的实力值wi和运气值的乘积,即wi⋅rci。班级的发挥是所有人发挥之和。每个人有一个初始运气值ri,但是每次比赛的时候,每个人的运气值是所有人运气值的一个排列,并且要满足,排列之后ii的运气值不是ri。即满足,ii的运气值是rci,{ci}是1−n1−n的排列,且满足ci≠i。
现在有Q场比赛,每场比赛前会交换两人的初始运气值。问你每场比赛班级可以获得的最大发挥是多少。
n<=30000
由于排序不等式,我们尽量想顺序放。两边都排序。
由于 n 个不能配的干扰,又不能完全顺序放。
有个结论,最后匹配出第 i 个人的运气值是第 j 个的话,∣i−j∣≤2 。这个结论从最小化逆序对的个数来看,自己把附近几个线连起来画一画证明一下。
这样就可以用 dp[i]表示到 i 为止所有配好的最优答案。计算的时候需要用到前三轮的答案然后讨论一下。这个是 O(nq)的,可以过70%。
用线段树记录区间答案。区间记录这样的信息:把这个区间前0-2个和后0-2个元素去掉的答案,用3x3的矩阵维护。这样复杂度是O(qlogn)。
对于线性递推式可以用矩阵优化,这个可以用矩阵维护dp。
由于出题人良心发现将时限改为3s,常数小的nq算法也能过1.5s左右.
AC Code:
#include<bits/stdc++.h>
#define maxn 30005
#define LL long long
using namespace std;
int n,q,w[maxn],r[maxn],c1[maxn],c2[maxn];
inline bool cmp1(const int &u,const int &v){ return w[u] > w[v]; }
inline bool cmp2(const int &u,const int &v){ return r[u] > r[v]; }
int loc[maxn];
const LL inf = -1ll<<60;
inline LL calc(int a,int b)
{
if(a<=0 || b<=0 || a>n || b>n) return inf;
return 1ll * w[c1[a]] * r[c2[b]];
}
LL dp[maxn];
LL cst[maxn][2];
inline void upd(int x)
{
if(x<=0 || x>n) return;
cst[x][0] = calc(x-1,x) + calc(x,x-1);
cst[x][1] = max(
calc(x-2,x-1)+calc(x-1,x)+calc(x,x-2),
calc(x,x-1)+calc(x-1,x-2)+calc(x-2,x)
);
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&w[i]),c1[i]=i;
for(int i=1;i<=n;i++) scanf("%d",&r[i]),c2[i]=i;
sort(c1+1,c1+n+1,cmp1);
sort(c2+1,c2+n+1,cmp2);
for(int i=1;i<=n;i++) loc[c2[i]] = i , upd(i);
for(int u,v;q--;)
{
scanf("%d%d",&u,&v);
swap(r[u],r[v]),swap(loc[u],loc[v]);
c2[loc[u]] = u, c2[loc[v]] = v;
for(int i=0;i<3;i++)
upd(c1[u]+i),upd(c1[v]+i),upd(c2[u]+i),upd(c2[v]+i);
for(int i=1;i<=n;i++)
dp[i] = max((i>1 ? dp[i-2]:0) + cst[i][0] , (i>2?dp[i-3]:0) + cst[i][1]),
(c1[i]!=c2[i]) && (dp[i] = max(dp[i] , dp[i-1] + calc(i,i)));
printf("%lld\n",dp[n]);
}
}