LOJ #2379. 「HNOI2013」旅行

把一个长度为 n n 的序列(每个数为 ± 1 \pm 1 )分成 m m 段,使得所有段的和的绝对值的最大值最小,求出一种段末尾字典序最小的划分.

n 5 1 0 5 , m 2 1 0 5 n\le 5*10^5,m\le 2*10^5

题目

网上大部分都是特别"显然"的题解,本题解尽量不那么显然,求轻喷

t [ i ] = {   1 ( b [ i ] = 0 ) 1 ( b [ i ] = 1 ) , s [ i ] = i = 1 n t [ i ] ] , S = s [ 1 ] t[i]=\begin{cases}\ -1&(b[i]=0) \\ 1& (b[i]=1)\end{cases},s[i]=\sum_{i=1}^n t[i]],S=s[1] .

现在姑且不管字典序,只求如何求出一组最大值最小的情况.

d d 表示子段绝对值最大值.

则有 d = { [ c 0 < m ] ( S = 0 ) S m e l s e d=\begin{cases} [c_0<m]&(S=0)\\ \lceil \dfrac {|S|} m \rceil & else\end{cases} .

  • 对于 S = 0 S=0 c 0 m c_0\ge m ,那么我们取 b [ i ] = 0 b[i]=0 m m 个位置即可.

  • 对于 S = 0 S=0 c 0 < m c_0<m ,我们可以发现 d 1 d\ge 1 .

    现在证明一下 d = 1 d=1 的正确性.

    我们消去所有的0段,则可以得到 S |S| 个1 or -1.

    我们现在需要能构造出 m m 组合法段.

    先不考虑 m m 的限制,如果我们划分出 n n 段的话一定合法,然后考虑0 段中可以删除的位置.

    由于 d = 1 d=1 ,等价于我们每次 s s 的变化至多为1. 所以,我们把任意0段中的 s s 最大值删去一定合法.

    所以就顺利得出 m m 段合法.

  • 其他情况,我们均分 S |S| 个数成 m m 段即可.

事实上,我们代码把后两种情况归为一种考虑.( d = 0 d=0 的时候做法比较明显下面就不考虑了)

由上,其实我们得到了 [ i , n ] [i,n] 能划分成 t t 段当且仅当 n i + 1 t , c e i l ( s i t ) d n-i+1\ge t,ceil(\dfrac{|s_i|}{t})\le d .

现在开始考虑字典序的约束

现在假设我们已经处理了 [ 1 , l ) , [1,l), 剩余 t t 段要选择,那么我们现在需要考虑 [ l , r ] [l,r] 是否合法.

则要满足:

{ s l s r + 1 d s r + 1 t 1 d n r t 1 \begin{cases} |s_l-s_{r+1}|\le d\\ \dfrac{|s_{r+1}|}{t-1}\le d\\n-r\ge t-1\end{cases}

与此同时,我们要还有最小化当前的 a r a_r .

观察式子可以发现 s r + 1 s_{r+1} 是一个非常重要的量(出现了两次)

我们可以把所有 s r + 1 s_{r+1} 相等的位置划分为一个集合,那么这个集合取最小值就是答案.

同时,前面的位置被淘汰.

上面就是一个单调队列的模型了.

所以我们只要维护 2 n 2n 个单调队列即可.

复杂度?

我们每次只需扫描 O ( d ) O(d) 个单调队列,所以总复杂度: O ( n + d m ) = O ( n + S ) O(n+dm)=O(n+|S|) .

又一次被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;
}


猜你喜欢

转载自blog.csdn.net/qq_42886072/article/details/106627362
今日推荐