[AGC049E] Increment Decrement

题目大意:

我们可以对于一个非负整数序列进行如下两种操作任意次.

    1.选定一项使其 $+1$ 或者 $−1$ , 会花费 $1$ 的代价.
    2.选定一个区间使区间中的每一项 $+1$ 或者 $−1$ , 会花费 C 的代价.

定义一个非负整数序列的代价为从全 0 序列变为该序列所需的最小代价.
给定 $n$ 个序列 $B_i$​ , 每个序列长为 $K$ , 依次将每个 $A_i$​ 赋值为$B_{i,1},B_{i,2},\dots,B_{i,K}$ , 求所有 $K^n$ 个可能的 $A$ 序列代价和.

$n,c,k<=50$ $b_i<=10^9$

题解:

本题的关键点在于slope trick.
首先考虑一个序列$a$,我们如何进行dp使得花费最小呢.
因为有两种操作,我们不好直接维护,所以我们考虑分一下层.
假设我们已经做完了所有的$2$操作,得到一个序列$b$,我们考虑构造这样一个$b$.
最小化:
$$\sum\limits_{i=1}^{n}{\left\vert {a_i-b_i}\right\vert+C*max(0,b_{i+1}-b_i)}$$
直接dp,设$f_{i,j}$表示前$i$个位置,$b_i=j$的最小代价:
$$f_{i,j}=\min\limits_{k>=0}{\begin{Bmatrix}{f_{i-1,k}+C*max(j-k,0)}\end{Bmatrix}}+\left\vert {j-a_i}\right\vert$$
把$\left\vert {j-a_i}\right\vert$放在后面转移的时候再加:
$$f_{i,j}=\min\limits_{k>=0}{\begin{Bmatrix}{f_{i-1,k}+\left\vert{a_{i-1}-k}\right\vert+C*max(j-k,0)}\end{Bmatrix}}$$
初始化$f_{0,j}=C*max(j,0)$,答案是$f_{n+1,0}$.

下面证明$f_{i,j}$是下凸的,考虑归纳证明.首先$f_{1,j}$是下凸的.然后在原函数上加上$\left\vert{a_{i-1}-k}\right\vert$,一个下凸函数加上一个下凸函数还是一个下凸函数.接着设$g(x)=f_{i-1,x}+\left\vert{a_{i-1}-x}\right\vert,h(x)=C*max(x,0)$.就是对着$g,h$做一次$(min,+)$卷积,因为$g,h$都是下凸,所以做完得到的新函数还是下凸.

我们已经证明了$f_{i,j}$是下凸的了,所以我们就可以用slope trick来维护这个东西了.维护斜率变化的分界点.
因为我们求$f_{n+1,0}$,所以同时维护$v_0$的值.

显然斜率在$[0,C]$之间.
一开始我们往集合中加入$C个0$,表示$f_{1,j}$的函数.加入一个$\left\vert{a_{i-1}-k}\right\vert$相当于在$a_{i-1}$处加入两个点表示斜率在这里变化$2$,接着和h做$(min,+)$卷积,因为之前的加入使得最前面的一段斜率为$-1$,最后一段的斜率变成$C+1$也就是我们要把最大和最小的两个分界点弹出.
同时我们考虑$v0$的变化$v_0+a_{i-1}-min$,这个要在图像上画一下,相当于先把最前面一段往上掰$a_{i-1}$,再往下掰那个最小的分界点.

这样我们就模拟完了整个过程这样做的时间复杂度是$o(nlogn)$

我们考虑原问题,直接做是不好做的,但是我们发现对于一个序列$v0$会把所有的$a_{i}$都加上,然后减去了所有的最小的分界点.
所以我们考虑用这个序列的和减去弹出去的和.我们把原来的$b$矩阵离散,每次把大于等于$x$的$b_{i}$看成$1$,小于的看成$0$,算出$1$被弹出来的个数.
这样做一次我们就相当于求出了大于等于$x$的数被弹出来的个数,差分一下就知道了$b_i$被弹出的个数.

求个数的时候直接模拟上述slope trick过程,发现当维护分界点的可重集新加入一个绝对值后,$1$的个数达到$C+2$的时候一定会弹出$1$.

 

代码:

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int n,c,m;
int pw[55],a[55][55],b[55][55],zhan[10005],cnt=0,ans=0;
int f[55][55];
int solve(int x){
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(b[i][j]>=x)a[i][j]=1;
      else a[i][j]=0;
    }
  }
  memset(f,0,sizeof(f));
  f[0][0]=1;
  for(int i=1;i<=n;i++){
    int c0=0,c1=0;
    for(int j=1;j<=m;j++){
      if(a[i][j])c1++;
      else c0++;
    }
    for(int j=0;j<=c+2;j++){
      int s=j,t=c+2-j;
      if(s&&t)s--;
      else if(s)s-=2;
      f[i][s+2]=(f[i][s+2]+1ll*f[i-1][j]*c1%mod)%mod;
      f[i][s]=(f[i][s]+1ll*f[i-1][j]*c0%mod)%mod;
    }
  }
  int res=1ll*pw[n]*n%mod;
  for(int i=1;i<=n;i++){
    res=(res-1ll*f[i][c+2]*pw[n-i]%mod+mod)%mod;
  }
  return res;
}
int main(){
  // freopen("[AGC049E].in","r",stdin);
  // freopen("[AGC049E].out","w",stdout);
  scanf("%d%d%d",&n,&c,&m);
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      scanf("%d",&b[i][j]);
      zhan[++cnt]=b[i][j];
    }
  }
  sort(zhan+1,zhan+1+cnt);
  cnt=unique(zhan+1,zhan+1+cnt)-zhan-1;
  pw[0]=1;
  for(int i=1;i<=n;i++)pw[i]=1ll*pw[i-1]*m%mod;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      ans=(ans+b[i][j])%mod;
    }
  }
  ans=1ll*ans*pw[n-1]%mod;
  int ss=0;
  for(int i=1;i<=cnt;i++){
    ss=(ss+1ll*zhan[i]*(solve(zhan[i]+1)-solve(zhan[i])+mod)%mod)%mod;
  }
  printf("%d\n",(ans-ss+mod)%mod);
  return 0;
}
View Code

 

posted @ 2023-06-18 16:32  星棋居  阅读(44)  评论(0)    收藏  举报