IOI2020集训队作业-18 (AGC037F)

A - CF553E Kyoya and Train


B - AGC037F Counting of Subarrays

Sol

考虑如何检查一个序列是否能够属于某个级别 ( k , l ) (k,l) (k,l)

  1. 如果序列中只有一种元素,只需检查序列的长度是否为 1 1 1或者大于等于 L L L就可以了。
  2. 如果序列中的元素多于一种,则考虑序列中最小的元素 m m m,找出所有的极长的连续为 m m m的段 [ l 1 , r 1 ] , [ l 2 , r 2 ] ⋯ [ l k , r k ] [l_1,r_1],[l_2,r_2]\cdots [l_k,r_k] [l1,r1],[l2,r2][lk,rk],将每一段替换成 ⌊ r i − l i + 1 L ⌋ \lfloor {r_i - l_i + 1 \over L}\rfloor Lrili+1 m + 1 m+1 m+1。如果存在一段长度不到 L L L则序列不合法。
  3. 重复2.直到序列中只有一个元素。

这样检查的复杂度是 O ( n ) O(n) O(n)的,因为每一次是用 O ( L ) O(L) O(L)的时间代价让序列中的元素减少了 L − 1 L-1 L1个。

对原序列模拟这个过程,并对每一个新的元素记录下: L i L_i Li表示原序列中有多少个位置,满足当它作为被检查的区间左端点的时候,到这一步左端点为 i i i R i R_i Ri表示原序列中有多少个位置,满足当它作为被检查的区间右端点的时候,到这一步右端点为 i i i。每一步的时候对于最小元素构成的极长段统计 ∑ i = j 或 者 j − i + 1 ≥ L L i ⋅ R j \sum_{i=j 或者j-i+1 \ge L} L_i \cdot R_j i=jji+1LLiRj并加入答案就可以了。注意由于在把 m m m改成 m + 1 m+1 m+1之前,极长段内的元素之间的贡献已经统计过了,所以改成 m + 1 m+1 m+1之后要减去这些由 m m m变成 m + 1 m+1 m+1的元素构成的极长段内的贡献。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}

const int N=6e5+10,inf=2e9;
int fa[N],ch[N][2],mx[N],mi[N],sz[N],val[N],ncnt,rt;
int Lx[N],Rx[N],vis[N];
int L;
inline void push_up(int c) {
	sz[c]=sz[ch[c][0]]+sz[ch[c][1]]+1;
	mi[c]=min(min(mi[ch[c][0]],mi[ch[c][1]]),vis[c]?inf:val[c]);
	mx[c]=max(max(mx[ch[c][0]],mx[ch[c][1]]),vis[c]?0:val[c]);
}
inline bool get(int x) { return ch[fa[x]][1]==x; }
inline void rotate(int x) {
	int f=fa[x],ff=fa[f],d=get(x);
	fa[x]=ff; if(ff) ch[ff][ch[ff][1]==f]=x;
	fa[ch[x][d^1]]=f,ch[f][d]=ch[x][d^1];
	fa[f]=x,ch[x][d^1]=f; push_up(f),push_up(x);
}
void splay(int x,int gl) {
	for(int f=fa[x];f!=gl;rotate(x),f=fa[x])
		if(fa[f]!=gl) rotate(get(x)==get(f)?f:x);
	if(!gl) rt=x;
}
int kth(int x,int k) {
	if(sz[ch[x][0]]+1==k) return x;
	if(k<=sz[ch[x][0]]) return kth(ch[x][0],k);
	return kth(ch[x][1],k-sz[ch[x][0]]-1);
}
void debug(int x) {
	if(!x) return;
	debug(ch[x][0]);
	printf("%d ",vis[x]?-1:val[x]);
	debug(ch[x][1]);
	push_up(x);
}
void Debug() { printf("debug:"),debug(rt),puts(""); sz[0]=0; }
int split(int l,int r) {
	int x=kth(rt,l-1),y=kth(rt,r+1);
	splay(x,0),splay(y,x);
	return ch[y][0];
}
int lv[N],rv[N],V[N],Vi[N],lim;
void build(int l,int r,int &c,int f) {
	if(l>r) return (void)(c=0);
	int mid=l+r>>1; c=++ncnt;
	val[c]=V[mid],vis[c]=Vi[mid],fa[c]=f;
	Lx[c]=lv[mid],Rx[c]=rv[mid];
	build(l,mid-1,ch[c][0],c);
	build(mid+1,r,ch[c][1],c);
	push_up(c);
}
struct item {
	int p,l,r;
	item(int p=0,int l=0,int r=0): p(p),l(l),r(r) {}
};
vector<item> A;
void find(int x,int pr) {
	if(mi[x]>lim) return;
	find(ch[x][0],pr); pr+=sz[ch[x][0]];
	if(val[x]==lim&&!vis[x]) A.PB(item(pr+1,Lx[x],Rx[x]));
	find(ch[x][1],pr+1);
}

int main() {
	mi[0]=inf;
	int n; rd(n),rd(L);
	for(int i=1;i<=n;++i) rd(V[i]),lv[i]=rv[i]=1;
	Vi[0]=Vi[n+1]=1;
	build(0,n+1,rt,0);
	Vi[0]=Vi[n+1]=0;
	ll ans=0;
	while(true) {
		A.clear(),lim=mi[rt],find(rt,0);
		for(int l=0,r;l<A.size();l=r+1) {
			r=l; while(r+1<A.size()&&A[r+1].p==A[r].p+1) ++r;
			ll s=0;
			for(int i=l;i<=r;++i) {
				if(i-L+1>=l) s+=A[i-L+1].l;
				ans+=s*A[i].r;
				ans+=A[i].l*A[i].r;
			}
		}
		if(mi[rt]>=mx[rt]) break;
		int pre=0;
		for(int l=0,r;l<A.size();l=r+1) {
			r=l; while(r+1<A.size()&&A[r+1].p==A[r].p+1) ++r;
			int len=(r-l+1)/L;
			int u=split(A[l].p+pre,A[r].p+pre);
			pre-=r-l+1; pre+=max(len,1);
			if(!len) {
				vis[u]=1;
				ch[u][0]=ch[u][1]=0;
				push_up(u),push_up(fa[u]),push_up(rt);
				continue;
			}
			for(int i=0;i<len;++i) lv[i]=rv[i]=0,V[i]=lim+1;
			for(int i=l+L-1;i<=r;++i) rv[(i-l+1)/L-1]+=A[i].r;
			for(int i=l;i<=r-L+1;++i) lv[len-(r-i+1)/L]+=A[i].l;
			for(int i=0,s=0;i<len;++i) {
				if(i-L+1>=0) s+=lv[i-L+1];
				ans-=s*rv[i];
				ans-=lv[i]*rv[i];
			}
			build(0,len-1,ch[fa[u]][0],fa[u]);
			push_up(fa[u]),push_up(rt);
		}
	}
	printf("%lld",ans);
	return 0;
}

AGC029C Lexicographic constraints

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值