P5979 [PA2014] Druzyny 題解

zifanwang發表於2024-09-19

對於一個固定的右端點 \(r\),左端點 \(l\) 合法當且僅當 \(\max(d_l,d_{l+1},\dots d_r)\le r-l+1 \le\min(c_l,c_{l+1},\dots,c_r)\)

容易得到一個很樸素的 dp:記 \(f_i\) 表示前 \(i\) 個位置可以分成的組的數目的最大值,\(g_i\) 表示有多少種分組方案能達到最大值,直接列舉左端點轉移,時間複雜度 \(\mathcal O(n^2)\)。轉移點不是連續的,不好直接最佳化。

考慮對這個 dp 進行一個分治,對於每個分治的區間 \([l,r]\) 處理 \(f_{[l,\text{mid}]},g_{[l,\text{mid}]}\)\(f_{[\text{mid}+1,r]},g_{[\text{mid}+1,r]}\) 的貢獻。發現 \(c\) 對每個右端點的限制是只能取某個字尾的左端點,可以直接二分出這個字尾,接下來只要考慮 \(d\) 的限制。

很容易想到倒著列舉 \([l,\text{mid}]\) 中的每個位置,維護 \(d\) 的最大值,可以得到一個右端點的位置 \(\ge \max(d_{[l,\text{mid}]})+l-1\) 的限制。考慮對 \([\text{mid}+1,r]\) 每個位置建一個桶,在這個限制的對應位置處插入這個左端點。接下來正著列舉 \([\text{mid}+1,r]\) 中的每個位置,把桶內的位置插入線段樹。對於每個右端點也可以得到一個類似的限制,線上段樹上查詢即可,最後更新一下就做完了。

時間複雜度 \(\mathcal O(n\log^2n)\),參考程式碼:

#include<bits/stdc++.h>
#define ll long long
#define mxn 1000003
#define md 1000000007
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
struct node{
	ll a,c;
}f[mxn],t[mxn<<2];
int n,lg[mxn],ps[mxn],a[mxn],b[20][mxn];
vector<int>s[mxn];
const int INF=1e9;
node operator+(node x,node y){
	return {max(x.a,y.a),((x.a>=y.a?x.c:0)+(y.a>=x.a?y.c:0))%md};
}
int ask2(int l,int r){
	int k=lg[r-l+1];
	return min(b[k][l],b[k][r-(1<<k)+1]);
}
void change(int p,int x,node y,int l,int r){
	if(l==r){t[p]=y;return;}
	int mid=(l+r)>>1;
	if(x<=mid)change(p<<1,x,y,l,mid);
	else change(p<<1|1,x,y,mid+1,r);
	t[p]=t[p<<1]+t[p<<1|1];
}
node ask(int p,int l,int r,int L,int R){
	if(l<=L&&R<=r)return t[p];
	int mid=(L+R)>>1;
	if(l<=mid&&r>mid)return ask(p<<1,l,r,L,mid)+ask(p<<1|1,l,r,mid+1,R);
	if(l<=mid)return ask(p<<1,l,r,L,mid);
	return ask(p<<1|1,l,r,mid+1,R);
}
void build(int p,int l,int r){
	t[p]={-INF,0};
	if(l==r)return;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
}
void solve(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1;
	solve(l,mid);
	rep(i,mid+1,r)s[i].clear();
	int mx=0;
	drep(i,mid,l){
		if(mx+i<=r)s[max(mx+i,mid+1)].pb(i);
		mx=max(mx,a[i]);
	}
	mx=0;
	rep(i,mid+1,r){
		mx=max(mx,a[i]);
		for(int j:s[i])change(1,j,{f[j].a+1,f[j].c},0,n);
		int L=max(ps[i]-1,l),R=min(i-mx,mid);
		if(L<=R)f[i]=f[i]+ask(1,L,R,0,n);
	}
	rep(i,l,mid)change(1,i,{-INF,0},0,n);
	solve(mid+1,r);
}
signed main(){
	scanf("%d",&n);
	rep(i,2,n)lg[i]=lg[i>>1]+1;
	rep(i,1,n)scanf("%d%d",&a[i],&b[0][i]);
	rept(k,1,20){
		rep(i,1,n-(1<<k)+1){
			b[k][i]=min(b[k-1][i],b[k-1][i+(1<<(k-1))]);
		}
	}
	rep(i,1,n){
		int l=1,r=i;
		while(l<r){
			int mid=(l+r)>>1;
			if(ask2(mid,i)>=i-mid+1)r=mid;
			else l=mid+1;
		}
		ps[i]=l;
	}
	f[0].c=1;
	rep(i,1,n)f[i].a=-INF;
	build(1,0,n);
	solve(0,n);
	if(f[n].a<=0)puts("NIE");
	else cout<<f[n].a<<" "<<f[n].c;
	return 0;
}