bzoj 3711 Druzyny

cdq 分治 + 线段树.

设 $f(i)$ 表示前 $i$ 个人能分成的组数最大值,转移有,
$$
f(i)=\max_{j=0}^{i-1} \lbrace f(j) \rbrace +1 ,\max c\le i-j \le \min d
$$
其中, $\max c$ 表示区间 $[j+1,i]$ 内最大的 $c$ , $\min d$ 表示区间 $[j+1,i]$ 内最小的 $d$ .

$d$ 的限制是容易处理的,若只考虑 $d$ 的限制,对于每个 $i$ ,合法的 $j$ 显然是一段后缀,设为 $[g(i),i-1]$ .

而 $g(i)​$ 是不降的,可以用双指针扫,每次用线段树问一下区间最小的 $d​$ ,就能在 $O(n\log n)​$ 的时间内预处理出来.

要加上 $c$ 的限制,考虑 cdq 分治,要算出 $[l,r]$ 内的每个 $f(i)$ ,若 $l=r$ ,就返回.

否则,先取划分位置 $k$ 表示区间 $[l+1,r]$ 内 $c$ 最大的位置.

递归处理 $[l,k-1]$ 后,考虑 $[l,k-1]$ 的 $dp$ 值对 $[k,r]$ 的贡献,再递归处理 $[k,r]$ .

在区间 $[k,r]$ 内依次枚举 $i$ ,计算左边区间对右边区间每个 $i$ 的贡献,并且时刻维护合法的 $j$ 中, $f(j)$ 的最大值.

初始时,合法的 $j$ 所在区间为 $[\max(l,g(i)),\min(k-1,i-c_k)]$ .

每当 $i$ 往右边移动一个位置,当 $i< k+c_k$ 时, $j$ 所在区间右端点也会往右边移动一个位置,直接把它的贡献加入.

而 $j​$ 所在区间左端点也可能往右边移动,由于不支持删除一段区间的贡献,只能用线段树重新查询一次.

当 $i$ 移动到 $k+c_k$ 时,右端点不再移动,而左端点单调不降,每次二分出左端点相同的一段 $i$ ,用线段树一起更新答案.

计算贡献的同时还要计算方案数,可以定义一个二元组 $(mx,cnt)​$ ,一起转移即可.

这样计算,时间复杂度 $T(n)=T(x)+T(n-x)+\min(x,n-x)​$ ,为 $O(n\log n)​$ .

如果我们对于每个 $i$ ,都对合法的 $j$ 所在区间用线段树查询一次,由于划分位置不一定是区间中点,就退化了.

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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
//%std
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
fh=-1,jp=getchar();
while (jp>='0'&&jp<='9')
out=out*10+jp-'0',jp=getchar();
return out*fh;
}
const int P=1e9+7,inf=1e9;
int add(int a,int b)
{
return (a+b>=P)?(a+b-P):(a+b);
}
const int MAXN=1e6+100000;
struct Info
{
int mx,cnt;
Info(int mx=0,int cnt=0):mx(mx),cnt(cnt) {}
friend Info operator + (const Info &a,const Info &b)
{
if(a.mx!=b.mx)
return a.mx>b.mx?a:b;
else
return Info(a.mx,add(a.cnt,b.cnt));
}
Info operator + (int x)
{
return Info(mx+x,cnt);
}
Info operator += (Info b)
{
*this=*this+b;
return *this;
}
} f[MAXN];
int n,c[MAXN],d[MAXN],g[MAXN];
struct node
{
int maxc,pos,mind;
Info f,tag;
} Tree[MAXN<<1];
#define root Tree[o]
#define lson Tree[o<<1]
#define rson Tree[o<<1|1]
void pushup(int o)
{
root.maxc=max(lson.maxc,rson.maxc);
if(lson.maxc>rson.maxc)
root.pos=lson.pos;
else
root.pos=rson.pos;
root.mind=min(lson.mind,rson.mind);
root.f=lson.f+rson.f;
}
void BuildTree(int o,int l,int r)
{
root.tag=Info(-inf,0);
if(l==r)
{
root.maxc=c[l];
root.pos=l;
root.mind=d[l];
root.f=f[l];
return;
}
int mid=(l+r)>>1;
BuildTree(o<<1,l,mid);
BuildTree(o<<1|1,mid+1,r);
pushup(o);
}
int query_pos(int o,int l,int r,int L,int R)
{
if(L<=l && r<=R)
return root.pos;
int mid=(l+r)>>1;
if(R<=mid)
return query_pos(o<<1,l,mid,L,R);
if(L>mid)
return query_pos(o<<1|1,mid+1,r,L,R);
int lp=query_pos(o<<1,l,mid,L,R);
int rp=query_pos(o<<1|1,mid+1,r,L,R);
return c[lp]>c[rp]?lp:rp;
}
int query_mind(int o,int l,int r,int L,int R)
{
if(L>R)
return n+1;
if(L<=l && r<=R)
return root.mind;
int mid=(l+r)>>1;
if(R<=mid)
return query_mind(o<<1,l,mid,L,R);
if(L>mid)
return query_mind(o<<1|1,mid+1,r,L,R);
int ld=query_mind(o<<1,l,mid,L,R);
int rd=query_mind(o<<1|1,mid+1,r,L,R);
return min(ld,rd);
}
void upd_add(int o,int l,int r,int L,int R,Info c)
{
if(L<=l && r<=R)
{
root.tag+=c;
return;
}
int mid=(l+r)>>1;
if(L<=mid)
upd_add(o<<1,l,mid,L,R,c);
if(R>mid)
upd_add(o<<1|1,mid+1,r,L,R,c);
}
Info query_f(int o,int l,int r,int L,int R)
{
if(L>R)
return Info(-inf,0);
if(L<=l && r<=R)
return root.f;
int mid=(l+r)>>1;
if(R<=mid)
return query_f(o<<1,l,mid,L,R);
if(L>mid)
return query_f(o<<1|1,mid+1,r,L,R);
Info lf=query_f(o<<1,l,mid,L,R);
Info rf=query_f(o<<1|1,mid+1,r,L,R);
return lf+rf;
}
Info query_f(int pos)
{
int o=1,l=0,r=n;
Info res=Info(-inf,0);
while(l!=r)
{
res+=root.tag;
int mid=(l+r)>>1;
if(pos<=mid)
o=o<<1,r=mid;
else
o=o<<1|1,l=mid+1;
}
return res+root.tag;
}
void upd_set(int o,int l,int r,int pos,Info c)
{
if(l==r)
{
root.f=c;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)
upd_set(o<<1,l,mid,pos,c);
else
upd_set(o<<1|1,mid+1,r,pos,c);
root.f=lson.f+rson.f;
}
void modify(int l,int k,int r)
{
int i=max(c[k]+l,k);
if(g[i]>=k || i>r)
return;
int jl=max(l,g[i]),jr=i-c[k];
Info tmp=query_f(1,0,n,jl,jr)+1;
while(i<=k+c[k]-1 && i<=r)
{
if(g[i]>jl)
{
if(g[i]>=k)
return;
jl=g[i];
tmp=query_f(1,0,n,jl,jr)+1;
}
f[i]+=tmp;
++jr;
if(jr>=jl)
tmp+=f[jr]+1;
++i;
}
while(i<=r)
{
if(g[i]>jl)
{
if(g[i]>=k)
return;
jl=g[i];
}
tmp=query_f(1,0,n,jl,k-1)+1;
int t=query_mind(1,0,n,jl+1,n);
if(t>r)
{
upd_add(1,0,n,i,r,tmp);
return;
}
upd_add(1,0,n,i,t-1,tmp);
i=t;
}
}
void cdq(int l,int r)
{
if(l==r)
{
if(l)
upd_set(1,0,n,l,f[l]+=query_f(l));
return;
}
int k=query_pos(1,0,n,l+1,r);
cdq(l,k-1);
modify(l,k,r);
cdq(k,r);
}
int main()
{
n=read();
for(int i=1; i<=n; ++i)
c[i]=read(),d[i]=read();
BuildTree(1,0,n);
for(int i=0; i<=n; ++i)
d[i]=n+1,f[i]=Info(-inf,0);
f[0]=Info(0,1);
for(int i=0,j=0; i<=n; ++i)
{
while(j<i && i-j>query_mind(1,0,n,j+1,i))
++j;
g[i]=j;
if(d[g[i]]>n)
d[g[i]]=i;
}
BuildTree(1,0,n);
cdq(0,n);
if(f[n].mx>0)
printf("%d %d\n",f[n].mx,f[n].cnt);
else
puts("NIE");
return 0;
}