YZOJ P3791 餐馆
时间限制:2000MS 内存限制:524288KB
难度:\(6.0\)
-
题目描述
在一条东西向的街道上有 \(n\) 个餐馆,从西向东编号为 \(1\) 至 \(n\),第 \(i\) 个餐馆和第 \(i+1\) 个餐馆的距离为 \(a_i\) 。
吃货小W喜欢到这条街道的餐馆里吃饭。现在,小W得到了 \(m\) 张餐票,每张餐票可以用于在街道上的任一餐馆里吃一餐。在第 \(i\) 个餐馆中,使用第 \(j\) 张餐票吃饭,可以获得的美味度为 \(b_{i,j}\) 。注意,每张餐票最多用一次,但在同一餐馆内可以使用任意多张餐票。
小W打算用完这 \(m\) 张餐票。他可以选择任一餐馆作为起点,每次吃饭时,可以选择一个餐馆,然后从当前位置(上次吃饭的地点,如果不存在则为起点)出发前往该餐馆并用任意一张未用过的餐票吃一餐,直到吃完 \(m\) 餐为止。小W希望最大化每次吃饭的美味度之和减去路上经过的总路程的值。
-
输入格式
输入第一行包含两个正整数 \(n,m\) 。
第二行包含 \(n-1\) 个正整数 \(a_1,a_2,\cdots,a_{n-1}\) 。
接下来 \(n\) 行,每行包含 \(m\) 个正整数,其中第 \(i\) 行第 \(j\) 个数为 \(b_{i,j}\) 。
-
输出格式
输出一行一个整数,表示所求的最大值。
-
样例输入
1 2 3 4 5 |
3 4 1 4 2 2 5 1 1 3 3 2 2 2 5 1 |
-
样例输出
1 |
11 |
-
样例说明
最优方案如下:以餐馆 \(1\) 为起点,在餐馆 \(1\) 使用第 \(1\) 张餐票、第 \(3\) 张餐票,然后前往餐馆 \(2\) 使用第 \(2\) 张餐票、第 \(4\) 张餐票。
-
数据规模与约定
对于所有数据,\(nm \leq 10^6\),\(n \geq 2\),\(a_i, b_{i, j} \leq 10^9\) 。
Source:ARC067 F – Yakiniku Restaurants,数据范围有改动(加强)
注意到(很显然爆零的我没有注意到)最优解肯定是在一段区间内,所以有
\(f_{l, r}=\sum\limits_{j=1}^m{\max\limits_{i=l}^r{b_{i, j}}} – \sum\limits_{i=l}^{r-1}a_i\)
直接 \(O(n^2)\) 枚举 \((l, r)\) , RMQ \(O(m)\) 转移,暴力总复杂度 \(O(n^2m)\) 。
考虑优化,这里有两种办法:
法1,转换问题,对于每个 \((l, r)\) 区间寻找最优的 \(b_{i, j}\) 转化为 对于每个 \(b_{i, j}\) 寻找它能贡献的 \((l, r)\) 区间。对于一个 \(b_{i, j}\) ,很明显它能贡献的区间为 \((\)它左边第一个不比它小的数\(,\)它右边第一个比它大的数\()\) (既不重复也不漏算),这个过程可以用笛卡尔树维护(我也不知道为什么dalao们都用笛卡尔树(果然我不是dalao))两次单调栈维护,值得注意的是正反的两个单调栈有且只有一个必须加等号
发现可以把这一的贡献关系表示成 \(l\) 为横坐标 \(r\) 为纵坐标的坐标系 中的一些矩形,增加贡献区间就相当于矩形覆盖。即每次对于一个 \(l\) ,在所有包含 \(l\) 的贡献组成的平面中,找到权值最大的一个点(它的纵坐标相当于 \(r\)),用它来贡献 \(f_{l,}\) 。注意到这个过程可以使用扫描线(按 \(l\) 从小到大扫描)+线段树维护(扫到 \(l\) 时合法矩形中点的点权最大值),时间复杂度 \(O\left(nm \log \left(nm\right)\right)\) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
#include <cstdio> #include <cstdlib> #include <cstring> #include <climits> #include <algorithm> #define _max(_a_,_b_) ((_a_)>(_b_)?(_a_):(_b_)) #ifdef ONLINE_JUDGE char __B[1<<20],*__S=__B,*__T=__B; #define getchar() (__S==__T&&(__T=(__S=__B)+fread(__B,1,1<<20,stdin),__S==__T)?EOF:*__S++) #endif inline int getnum() { register char c=0; while(!(c>='0' && c<='9')) c=getchar(); register int a=0; while(c>='0' && c<='9') { a*=10;a+=c-'0'; c=getchar(); } return a; } int _b[2050505],*b[105050]; long long a[105050]; struct _event { int x,l,r,val; bool operator < (const _event&o)const { return x<o.x; } }evt[2050505]; int le; #define Ls(_o) ((_o)<<1) #define Rs(_o) (Ls(_o)|1) long long smx[105050<<2],tag[105050<<2]; #define PushUp(_o){ \ smx[_o]=_max(smx[Ls(_o)],smx[Rs(_o)]);} #define PushDown(_o){ \ smx[Ls(_o)]+=tag[_o],tag[Ls(_o)]+=tag[_o]; \ smx[Rs(_o)]+=tag[_o],tag[Rs(_o)]+=tag[_o]; \ tag[_o]=0;} void BuildTree(register int O,register int L,register int R) { if(L==R) { smx[O]=-a[L-1]; return; } register int mid=(L+R)>>1; BuildTree(Ls(O),L,mid); BuildTree(Rs(O),mid+1,R); PushUp(O); } int ML,MR,MV; void ModifyTreeS(register int O,register int L,register int R) { if(L>=ML && R<=MR) { smx[O]+=MV,tag[O]+=MV; return; } if(tag[O]) PushDown(O); register int mid=(L+R)>>1; if(ML<=mid) ModifyTreeS(Ls(O),L,mid); if(MR>mid) ModifyTreeS(Rs(O),mid+1,R); PushUp(O); } int QL,QR; long long _Qret; void QueryTree(register int O,register int L,register int R) { if(L>=QL && R<=QR) { _Qret=_max(_Qret,smx[O]); return; } if(tag[O]) PushDown(O); register int mid=(L+R)>>1; if(QL<=mid) QueryTree(Ls(O),L,mid); if(QR>mid) QueryTree(Rs(O),mid+1,R); } int main() { register int N=getnum(),M=getnum(); for(register int i=1;i<=N-1;i++) a[i]=a[i-1]+getnum(); for(register int i=0,lst=0;i<=M;i++) b[i]=&_b[lst],lst+=N+2; for(register int i=1;i<=N;i++) for(register int j=1;j<=M;j++) b[j][i]=getnum(),b[j][N+1]=INT_MAX; for(register int j=1;j<=M;j++) { register int top=0; static int stk[105050],lson[105050],rson[105050]; stk[top=0]=0; /*for(register int i=1;i<=N;i++) { while(top && b[j][stk[top]] <= b[j][i]) top--; lson[i]=stk[top]+1; stk[++top]=i; } stk[top=0]=N+1; for(register int i=N;i>=1;i--) { while(top && b[j][stk[top]] < b[j][i]) top--; rson[i]=stk[top]-1; stk[++top]=i; } for(register int i=1;i<=N;i++) { //printf("[%d, (%d), %d]\n",lson[i],i,rson[i]); evt[++le]=(_event){lson[i],i,rson[i],b[j][i]}; evt[++le]=(_event){i+1,i,rson[i],-b[j][i]}; }*/ for(register int i=1;i<=N+1;i++) { while(top && b[j][stk[top]] < b[j][i]) { evt[++le]=(_event){lson[top],stk[top],i-1,b[j][stk[top]]}; evt[++le]=(_event){stk[top]+1,stk[top],i-1,-b[j][stk[top]]}; top--; } stk[++top]=i; lson[top]=stk[top-1]+1; } } std::sort(&evt[1],&evt[le+1]); BuildTree(1,1,N); long long ans=LLONG_MIN; for(register int i=1,k=1;i<=N;i++) { //printf("event %d\n",i); while(k<=le && evt[k].x==i) { ML=evt[k].l,MR=evt[k].r,MV=evt[k].val; //printf("add %d %d v:%d\n",ML,MR,MV); ModifyTreeS(1,1,N); k++; } QL=i,QR=N,_Qret=LLONG_MIN,QueryTree(1,1,N); long long tans=_Qret+a[i-1]; ans=_max(ans,tans); //printf("query %d %d v:%lld\n",QL,QR,_Qret); } printf("%lld\n",ans); return 0; } |
法2,注意到若 \(f_{l, }\) 的最优贡献点在 \(k\) ,那么 \(f_{l+1, }\) 的最优贡献点一定不会在 \(k\) 之前,即决策单调性。证明自行感性理解(其实是我不会证)。所以分治一下即可,以 \(mid\) 为 \(l\) 时最优的 \(r\) 可能在 \([kl, kr]\) 中,时间复杂度 \(O\left(nm \log n\right)\) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
#include <cstdio> #include <cstdlib> #include <cstring> #include <climits> #define _min(_a_,_b_) ((_a_)<(_b_)?(_a_):(_b_)) #define _max(_a_,_b_) ((_a_)>(_b_)?(_a_):(_b_)) #ifdef ONLINE_JUDGE char __B[1<<20],*__S=__B,*__T=__B; #define getchar() (__S==__T&&(__T=(__S=__B)+fread(__B,1,1<<20,stdin),__S==__T)?EOF:*__S++) #endif inline int getnum() { register char c=0; while(!(c>='0' && c<='9')) c=getchar(); register int a=0; while(c>='0' && c<='9') { a*=10;a+=c-'0'; c=getchar(); } return a; } int N,M; int _b[2050505],*b[105050]; long long a[105050]; int _log[105050],__fb[20505050],*_fb[2050505],**fb[105050]; #define segmax(_j, _l, _r) \ _max(fb[_j][_log[_r-_l+1]][_l],fb[_j][_log[_r-_l+1]][_r-(1<<_log[_r-_l+1])+1]) long long ans; void Divide(register int l,register int r,register int kl,register int kr) { if(l>r) return; register int mid=(l+r)>>1,kb=kl,kll=_max(mid,kl); long long tgans=LLONG_MIN; for(register int i=kll;i<=kr;i++) { long long tans=a[mid-1]-a[i-1]; for(register int j=1;j<=M;j++) tans+=segmax(j,mid,i); if(tans>tgans) tgans=tans,kb=i; } ans=_max(ans,tgans); Divide(l,mid-1,kl,kb); Divide(mid+1,r,kb,kr); } int main() { register int N=getnum(),M=getnum(); ::N=N,::M=M; for(register int i=1;i<=N-1;i++) a[i]=a[i-1]+getnum(); for(register int i=0,lst=0;i<=M;i++) b[i]=&_b[lst],lst+=N+2; for(register int i=1;i<=N;i++) for(register int j=1;j<=M;j++) b[j][i]=getnum(); for(register int i=2;i<=N;i++) _log[i]=_log[i>>1]+1; for(register int k=0,lst=0;k<=(_log[N]+2)*(M+2)+1;k++) _fb[k]=&__fb[lst],lst+=N+2; for(register int j=0,lst=0;j<=M+1;j++) fb[j]=&_fb[lst],lst+=_log[N]+2; for(register int j=1;j<=M;j++) { for(register int i=1;i<=N;i++) fb[j][0][i]=b[j][i]; for(register int k=1;k<=_log[N];k++) for(register int i=1;i<=N;i++) fb[j][k][i]=_max(fb[j][k-1][i],fb[j][k-1][i+(1<<(k-1))]); } ans=LLONG_MIN; Divide(1,N,1,N); printf("%lld\n",ans); return 0; } |