bzoj4937 [CEOI2016]popeala

Captain_Paul

2018-11-04 19:16:51

Personal

$cwbc$的膜你赛题 首先写出暴力$dp$ $f[i][j]$表示前$i$个测试点分成$j$个区间的最小得分总和 $O(nm^3)$预处理出$w(l,r)$表示把$[l,r]$划分成一段区间的得分 然后就可以$O(m^2S)$转移了 $f[i][j]=min\left\{f[k][j-1]+w(k+1,r)\right\},k∈[0,i)$ ------------ 发现可以更快地预处理$w(l,r)$ 固定左端点$l$,向右移动右端点 每向右移动一个点,可以$O(n)$维护还有几个人可以通过这个测试点 然后乘以区间总分就可以,复杂度$O(nm^2)$ ------------ 用$a$数组保存测试点分值的前缀和 设$A(l,r)$表示能通过$[l,r]$测试点的人数 那么转移可以写成:$f[i][j]=min\left\{f[k][j-1]+A(k+1,i)*(a[i]-a[k])\right\},k∈[0,i)$ 因为$n<=50$,所以$A$的范围其实是很小的 考虑将转移按照$A$分类,在每一类中$A$是一个常数 那么有$f[i][j]=A*a[i]+min\left\{f[k][j-1]-A*a[k]\right\},k∈[0,i)$ 可以发现,当常数$A$固定时,满足$A(k+1,i)=A$的$k$一定是一个区间 并且当$i$增大时,这个区间的左右端点都会向右移动 所以相当于对一些左右端点单调不降的区间取区间最小值 可以用单调队列来实现 ------------ 具体做法:对每个$i$和每个$A$处理出对应的$k$的区间,按$j$从小到大一层层转移。在每一层中,按$A$分类,每一类可以做到$O(m)$,总复杂度$O(nmS)$ 代码好难写啊$Orz$ 而且$bzoj$好像不能在$memset$里面用$127$? 只能用$for$循环初始化 注意:因为这$S$个答案相互独立,所以每一次都要初始化!并且不能用一般的滚动数组,否则会影响后面的答案 ``` #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=55,M=2e5+5,inf=2e9; char s[N][M]; int n,m,T,a[M],f[M],g[M],L[N][M],R[N][M],q[M<<2],w[M<<2],pre[N]; inline void del(int x,int y) { for (int i=1;i<=n;i++) if (q[i]==x){for (int j=i;j<n;j++) q[j]=q[j+1]; break;} q[n]=y; } int main() { scanf("%d%d%d",&n,&m,&T); for (int i=1,x;i<=m;i++) scanf("%d",&x),a[i]=a[i-1]+x; for (int i=1;i<=n;i++) scanf("%s",s[i]+1); for (int j=1;j<=m;j++) { for (int i=1;i<=n;i++) if (s[i][j]=='0') del(pre[i],j),pre[i]=j; L[n][j]=q[n]+1,R[n][j]=j; for (int i=1;i<n;i++) L[i][j]=q[i]+1,R[i][j]=q[i+1]; L[0][j]=1,R[0][j]=q[1]; } for (int i=1;i<=m;i++) f[i]=inf; for (int k=1;k<=T;k++) { for (int i=0;i<=m;i++) g[i]=inf; for (int i=0;i<=n;i++) { int h=1,t=0,r=-1; for (int j=1;j<=m;j++) { while (r<R[i][j]-1) { ++r; int cur=f[r]-i*a[r]; while (h<=t&&cur<=w[t]) --t; q[++t]=r; w[t]=cur; } while (h<=t&&q[h]<L[i][j]-1) ++h; if (h<=t) g[j]=min(g[j],w[h]+i*a[j]); } } memcpy(f,g,sizeof(g)); printf("%d\n",f[m]); } return 0; } ```