bzoj4194: Mat
思路:令K[i]=k[1]+k[2]+...+k[i],B[i]=b[1]+b[2]+..+b[i]
先取出每行第一个数,那么第i行第j列的元素就是A[i]+i*K[j]+B[j]
那么每行的答案ans[i]=min(i*K[j]+B[j])+A[i]
稍微变形:B[j]=i*(-K[j])+ans[i]-A[i]
把-K[j]看成横坐标,B[j]看成纵坐标,维护凸包搞一搞就可以了。愿意用单调队列用单调队列,愿意用单调栈的用单调栈,作死用splay的就用splay
考场上发神经写的splay维护,其实只要单调队列就可以了。splay代码太丑就不贴了。
代码:
#include<ctime> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long #define min(a,b) (a<b?a:b) const int maxn=200010; const double eps=1e-10,inf=1e18; using namespace std; int n,m,q[maxn];ll A[maxn],K[maxn],B[maxn],cnt,top,ans;char ch; struct node{ll x,y;}p[maxn]; double slope(int a,int b){ if (p[b].x-p[a].x<eps) return inf*(p[b].y-p[a].y); return 1.0*(p[b].y-p[a].y)/(p[b].x-p[a].x); } bool cmp(node a,node b){return a.x==b.x?a.y<b.y:a.x<b.x;} ll query(double k,int i){ int l=1,r=top; if (!top) return (int)30077; while (1){ int mid=(l+r)>>1; double lk=slope(q[mid-1],q[mid]); double rk=slope(q[mid],q[mid+1]); if (lk<=eps+k&&rk+eps>=k) return p[q[mid]].y+1ll*i*p[q[mid]].x+A[i]; else if (k>rk) l=mid+1;else r=mid-1; } } int main(){ scanf("%d%d",&n,&m);m--; for (int i=1;i<=n;i++) scanf("%lld",&A[i]); for (int i=1;i<=m;i++){ scanf("%lld%lld",&K[i],&B[i]); p[i].x=p[i-1].x+K[i],p[i].y=p[i-1].y+B[i]; } sort(p+1,p+1+m,cmp); for (int i=1;i<=m;i++){ while (top>1&&slope(q[top-1],q[top])>slope(q[top],i)) --top; q[++top]=i; } q[0]=0,q[top+1]=maxn-1,p[0]=(node){p[1].x-1,(ll)inf},p[maxn-1]=(node){p[q[top]].x+1,(ll)inf}; for (int i=1;i<=n;i++) printf("%lld\n",min(A[i],query(-1.0*i,i))); return 0; }