【线段树维护矩阵转移】【Codeforces】573D. Bear and Cavalry

题意

给出一个 [1,n] [ 1 , n ] 的排列,每次交换两个数,每次询问一个每一位都与这个数列不同排列,这样的排列的最大权值,一个排列的权值定义为 a[i]b[p[i]] a [ i ] ∗ b [ p [ i ] ]

题解

如果没有限制,由排序不等式,显然当 a a b b 都顺序排列是最优解,现在有限制以后,可以证明(其实我不会):每一位最多与排序后与它相差2的数配对。

这样就可以DP了, f[i][j] f [ i ] [ j ] 表示前 i i 位,前 ij i − j 位已经配对的最优解。同时根据交换法,还可以缩小前一状态的范围,这里就不再赘述。

但是有修改操作,如果每次刷DP,复杂度太高,注意到每次只与前 3 3 位有关,可以考虑矩阵优化,这里需要修改一下矩阵乘法的规则:

[xy][acbd]=[max{x+a,x+c}max{y+b,y+d}] [ x y ] ∗ [ a b c d ] = [ m a x { x + a , x + c } m a x { y + b , y + d } ]

可以证明,这样的矩阵乘法也是满足结合律的。

于是可以构造如下矩阵(如果不能转移,对应的位就是 − ∞

aibiaibi1+ai1bimax{aibi1+ai1bi2+ai2bi,aibi2+ai1bi+ai2bi1}00 [ a i b i 0 − ∞ a i b i − 1 + a i − 1 b i − ∞ 0 m a x { a i b i − 1 + a i − 1 b i − 2 + a i − 2 b i , a i b i − 2 + a i − 1 b i + a i − 2 b i − 1 } − ∞ − ∞ ]

每次暴力修改所有有关的位,用线段树维护就可以了,答案就是 tree[1].s[0][0] t r e e [ 1 ] . s [ 0 ] [ 0 ]

代码

#include
#include
#include
using namespace std;
typedef long long LL;
const int maxn=30006;
int n,q,p[maxn],c[maxn];
struct data{
    int id;LL x;
    bool operator <(const data&b)const{return xbool ck(int x,int y){return p[a[x].id]!=b[y].id;}
struct matrix{
    LL s[3][3];
    matrix operator *(const matrix&b){
        matrix c;memset(c.s,192,sizeof(c.s));
        for(int i=0;i<3;i++)
         for(int j=0;j<3;j++)
          for(int k=0;k<3;k++) c.s[i][j]=max(c.s[i][j],s[i][k]+b.s[k][j]);
        return c;
    }
    inline void update(int x){
        memset(s,192,sizeof(s));s[0][1]=s[1][2]=0;
        if(ck(x,x))s[0][0]=a[x].x*b[x].x;
        if(x>1&&ck(x,x-1)&&ck(x-1,x))s[1][0]=a[x].x*b[x-1].x+a[x-1].x*b[x].x;
        if(x>2){
            if(ck(x,x-2)&&ck(x-1,x)&&ck(x-2,x-1))s[2][0]=a[x].x*b[x-2].x+a[x-1].x*b[x].x+a[x-2].x*b[x-1].x;
            if(ck(x,x-1)&&ck(x-1,x-2)&&ck(x-2,x))s[2][0]=max(s[2][0],a[x].x*b[x-1].x+a[x-1].x*b[x-2].x+a[x-2].x*b[x].x);
        }
    }
}tree[maxn*4];
void update(int k,int l=1,int r=n,int p=1){
    if(l==r)return  tree[p].update(l);int mid=l+r>>1;
    if(k<=mid)update(k,l,mid,p<<1);else update(k,mid+1,r,p<<1|1);
    tree[p]=tree[p<<1]*tree[p<<1|1];
}
int main(){
    freopen("cavalry.in","r",stdin);freopen("cavalry.out","w",stdout);
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i].x),a[i].id=i,p[i]=i;
    for(int i=1;i<=n;i++)scanf("%lld",&b[i].x),b[i].id=i;
    sort(a+1,a+1+n);sort(b+1,b+1+n);for(int i=1;i<=n;i++)c[a[i].id]=i;
    for(int i=1;i<=n;i++)update(i);
    while(q--){
        int x,y;scanf("%d%d",&x,&y);swap(p[x],p[y]);
        for(int i=c[x];i3;i++)update(i);for(int i=c[y];i3;i++)update(i);
        printf("%lld\n",tree[1].s[0][0]);
    }
    return 0;
}

你可能感兴趣的:(线段树,矩阵乘法,Codeforces)