把一个长度为 的序列(每个数为 )分成 段,使得所有段的和的绝对值的最大值最小,求出一种段末尾字典序最小的划分.
网上大部分都是特别"显然"的题解,本题解尽量不那么显然,求轻喷
令 .
现在姑且不管字典序,只求如何求出一组最大值最小的情况.
设 表示子段绝对值最大值.
则有 .
-
对于 且 ,那么我们取 的 个位置即可.
-
对于 且 ,我们可以发现 .
现在证明一下 的正确性.
我们消去所有的0段,则可以得到 个1 or -1.
我们现在需要能构造出 组合法段.
先不考虑 的限制,如果我们划分出 段的话一定合法,然后考虑0 段中可以删除的位置.
由于 ,等价于我们每次 的变化至多为1. 所以,我们把任意0段中的 最大值删去一定合法.
所以就顺利得出 段合法.
-
其他情况,我们均分 个数成 段即可.
事实上,我们代码把后两种情况归为一种考虑.( 的时候做法比较明显下面就不考虑了)
由上,其实我们得到了 能划分成 段当且仅当 .
现在开始考虑字典序的约束
现在假设我们已经处理了 剩余 段要选择,那么我们现在需要考虑 是否合法.
则要满足:
与此同时,我们要还有最小化当前的 .
观察式子可以发现 是一个非常重要的量(出现了两次)
我们可以把所有 相等的位置划分为一个集合,那么这个集合取最小值就是答案.
同时,前面的位置被淘汰.
上面就是一个单调队列的模型了.
所以我们只要维护 个单调队列即可.
复杂度?
我们每次只需扫描 个单调队列,所以总复杂度: .
又一次被HNOI的题目征服~~
#include<cstdio>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=5e5+10,M=1e6+10;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-') f=-1; c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x<0) putchar('-'),x=-x;
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {qw(x); putchar(' ');}
template<class o> void pr2(o x) {qw(x); puts("");}
int n,m,d,a[N],b[N],c[N],tot;
struct rec {
int x,id;//数,位置
bool operator <(rec b) const {return x<b.x;}
} t[N],ans;
int cnt[N*2],now;
struct Q {
int l,r;
Q(int x=1) {l=x; r=x-1;}
void push(rec a) {
while(l<=r&&a<t[r]) r--;
t[++r]=a;
}
void get() {
while(l<=r&&t[l].id<now) l++;
if(l<=r&&t[l]<ans) ans=t[l];
}
} q[N*2];
void add(int x) { q[b[x+1]+n].push((rec){a[x],x});}
int main() {
qr(n); qr(m);
for(int i=1;i<=n;i++) qr(a[i]),qr(b[i]),b[i]=(b[i]?1:-1);
for(int i=n;i;i--) b[i]+=b[i+1],cnt[b[i+1]+n]++,tot+=!b[i];
for(int i=0,s=0;i<=2*n;i++) q[i]=Q(s),s+=cnt[i];
if(!b[1]) d=(tot<m);
else d=(abs(b[1])-1)/m+1;
if(!d) {
tot=0;
for(int i=1;i<=n;i++)
if(!b[i+1]) c[++tot]=i;
now=1;
for(int i=1,j=1;i<m;i++) {
while(tot-j>=m-i) q[0].push((rec){a[c[j]],c[j]}),j++;
ans.x=N; q[0].get(); pr1(ans.x); now=ans.id+1;
}
}
else {
int r=now=1;
while(n-r>=m-1)add(r++);
while(m>1) {
ans.x=N;
int x=b[now]+n;
for(int j=x-d;j<=x+d;j++)
if(abs(j-n)<=(m-1)*d) q[j].get();
pr1(ans.x); now=ans.id+1;
m--; add(r++);
}
}
pr2(a[n]);
return 0;
}