多校联训 DS 专题

CF1039D You Are Given a Tree

容易发现,当 \(k\) 不断增大时,答案不断减小,且 \(k\) 的答案不超过 \(\lfloor\frac {n}{k}\rfloor\) ,因此不同的答案个数是 \(\sqrt n\) 级别的,可以用一种类似整体二分的方式求解。

对于一个 \(k\) ,从叶子节点贪心向上匹配即可得到答案。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int ver[200005],ne[200005],head[100005],cnt;
inline void link(int x,int y){
	ver[++cnt]=y;
	ne[cnt]=head[x];
	head[x]=cnt;
}
int fa[100005];
vector<int> dfn;
void dfs(int x,int fi){
	fa[x]=fi;
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		if(u==fi)continue;
		dfs(u,x);
	}dfn.push_back(x);
}
int dp[100005];
inline int Get(int mid){
	for(int i=1;i<=n;i++)dp[i]=1;
	int res=0;
	for(auto it:dfn){
		if(dp[it]==-1||dp[fa[it]]==-1)continue;
		if(dp[it]+dp[fa[it]]>=mid)res++,dp[fa[it]]=-1;
		else dp[fa[it]]=max(dp[fa[it]],dp[it]+1);
	}
	return res;
}
void solve(int l=2,int r=n,int ql=Get(2),int qr=Get(n)){
	if(l>r||ql==qr){
		for(int i=l;i<=r;i++)printf("%d\n",ql);
		return ;
	}
	int mid=(l+r)>>1,tmpl=Get(mid),tmpr=Get(mid+1);
	solve(l,mid,ql,tmpl);solve(mid+1,r,tmpr,qr);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int x,y;scanf("%d%d",&x,&y);
		link(x,y);link(y,x);
	}
	dfs(1,0);dp[0]=-1;
	printf("%d\n",n);solve();

	return 0;
}




CF983E NN country

考虑倍增,设 \(up[i][x]\) 表示从 \(x\) 点开始经过 \(2^i\) 条路线可以到达的最靠上的点,还需要讨论是否有一条路径能跨过 \(\text{lca}\) ,这是一个 \(\text{dfs}\) 序上的二维数点问题。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,q;
int ver[400005],ne[400005],head[200005],tot;
inline void link(int x,int y){
	ver[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
}
int fa[19][200005],dep[200005],dfn[200005],cnt,siz[200005];
void dfs1(int x,int fi){
	fa[0][x]=fi;dep[x]=dep[fi]+1;dfn[x]=++cnt;siz[x]=1;
	for(int i=1;i<19;i++)fa[i][x]=fa[i-1][fa[i-1][x]];
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		if(u==fi)continue;
		dfs1(u,x);siz[x]+=siz[u];
	}
}
inline int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=18;~i;i--)if(dep[fa[i][x]]>=dep[y])x=fa[i][x];
	if(x==y)return x;
	for(int i=18;~i;i--){
		if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
	}
	return fa[0][x];
}
int up[19][200005];
inline int cmp(int x,int y){
	return dep[x]<dep[y]?x:y;
}
void dfs2(int x,int fi){
	for(int i=head[x];i;i=ne[i]){
		int u=ver[i];
		if(u==fi)continue;
		dfs2(u,x);up[0][x]=cmp(up[0][x],up[0][u]);
	}
}
vector<int> vec[200005];
namespace Seg{
	int tree[15000005],le[15000005],ri[15000005],cnt,rt[200005];
	void insert(int loc,int &i,int old,int l=1,int r=n){
		if(loc<l||loc>r)return ;
		i=++cnt;tree[i]=tree[old]+1;le[i]=le[old];ri[i]=ri[old];
		if(l==r)return ;
		int mid=(l+r)>>1;
		insert(loc,le[i],le[old],l,mid);insert(loc,ri[i],ri[old],mid+1,r);
	}
	int query(int fr,int to,int a,int b,int l=1,int r=n){
		if(fr>r||to<l)return 0;
		if(fr<=l&&to>=r)return tree[b]-tree[a];
		int mid=(l+r)>>1;
		return query(fr,to,le[a],le[b],l,mid)+query(fr,to,ri[a],ri[b],mid+1,r);
	}
}
inline int solve(int x,int y){
	int lc=lca(x,y),res=0;
	for(int i=18;~i;i--)if(up[i][x]&&dep[up[i][x]]>dep[lc])res+=(1<<i),x=up[i][x];
	for(int i=18;~i;i--)if(up[i][y]&&dep[up[i][y]]>dep[lc])res+=(1<<i),y=up[i][y];
	if(x!=lc&&dep[up[0][x]]>dep[lc])return -1;
	if(y!=lc&&dep[up[0][y]]>dep[lc])return -1;
	return res+(lc==x||lc==y||Seg::query(dfn[x],dfn[x]+siz[x]-1,Seg::rt[dfn[y]-1],Seg::rt[dfn[y]+siz[y]-1])?1:2);
}
int main(){
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int x;scanf("%d",&x);
		link(x,i);
	}
	dfs1(1,1);dep[0]=n;
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		int x,y;scanf("%d%d",&x,&y);
		int lc=lca(x,y);vec[dfn[x]].push_back(dfn[y]);vec[dfn[y]].push_back(dfn[x]);
		up[0][x]=cmp(up[0][x],lc);
		up[0][y]=cmp(up[0][y],lc);
	}
	for(int i=1;i<=n;i++){
		Seg::rt[i]=Seg::rt[i-1];
		for(auto it:vec[i])Seg::insert(it,Seg::rt[i],Seg::rt[i]);
	}
	dfs2(1,1);
	for(int i=1;i<19;i++){
		for(int j=1;j<=n;j++)up[i][j]=up[i-1][up[i-1][j]];
	}
	scanf("%d",&q);
	while(q--){
		int x,y;scanf("%d%d",&x,&y);
		printf("%d\n",solve(x,y));
	}


	return 0;
}



[AGC001F] Wide Swap

考虑原排列的逆变换,条件变为对于相邻两个数 \(Q_i,Q_j\) ,如果 \(|Q_i-Q_j|\ge k\) ,这两个数可以发生交换,要求数字 \(1\) 所在的位置尽量靠前。在此基础上,数字 \(2\) 所在的位置尽量靠前 ... 这样看起来就舒服多了。

发现对于所有满足 \(|u - v| < k\)\((u, v)\),它们在 \(Q\) 中出现的相对位置被固定了。

考虑拓扑排序,将所有边反向,每次取队列中最大的元素倒着填充拓扑序,直接做是 \(O(n^2)\) 的,线段树优化即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
int P[500005];
pair<int,int> tree[2000005];
void build(int l=1,int r=n,int i=1){
	if(l==r){
		tree[i]=make_pair(P[l],l);return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,i<<1);build(mid+1,r,i<<1|1);
	tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
void del(int loc,int l=1,int r=n,int i=1){
	if(loc<l||loc>r)return ;
	if(l==r){
		tree[i]=make_pair(0,0);return ;
	}
	int mid=(l+r)>>1;
	del(loc,l,mid,i<<1);del(loc,mid+1,r,i<<1|1);
	tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
pair<int,int> query(int fr,int to,int l=1,int r=n,int i=1){
	if(fr>r||to<l)return make_pair(0,0);
	if(fr<=l&&to>=r)return tree[i];
	int mid=(l+r)>>1;
	return max(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
}
priority_queue<int> q;
int ans[500005];
bool vis[500005];
inline bool check(int x){
	if(vis[x])return 0;
	return query(x-k+1,x+k-1).second==x;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&P[i]);
	build();
	for(int i=1;i<=n;i++)if(check(i))vis[i]=1,q.push(i);
	for(int i=n;i;i--){
		int x=q.top();q.pop();
		ans[x]=i;del(x);
		int tmp=query(x-k+1,x-1).second;
		if(tmp&&check(tmp))vis[tmp]=1,q.push(tmp);
		tmp=query(x+1,x+k-1).second;
		if(tmp&&check(tmp))vis[tmp]=1,q.push(tmp);
	}
	for(int i=1;i<=n;i++)printf("%d\n",ans[i]);

	return 0;
}



CF1578B Building Forest Trails

断环为链,几个点联通的条件是线段有交,对于每个点,维护其同一个集合内的前驱和后继,加入一条边时合并两个集合,然后检查这个区间内有没有点的前驱或后继伸到区间外面即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
namespace Seg1{
	pair<int,int> tree[800005];
	void build(int l=1,int r=n,int i=1){
		if(l==r){
			tree[i]=make_pair(l,l);
			return ;
		}
		int mid=(l+r)>>1;
		build(l,mid,i<<1);build(mid+1,r,i<<1|1);
		tree[i]=max(tree[i<<1],tree[i<<1|1]);
	}
	pair<int,int> query(int fr,int to,int l=1,int r=n,int i=1){
		if(fr>r||to<l)return make_pair(0,0);
		if(fr<=l&&to>=r)return tree[i];
		int mid=(l+r)>>1;
		return max(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
	}
	void update(int loc,int v,int l=1,int r=n,int i=1){
		if(loc<l||loc>r)return ;
		if(l==r){
			tree[i]=make_pair(v,l);
			return ;
		}
		int mid=(l+r)>>1;
		update(loc,v,l,mid,i<<1);update(loc,v,mid+1,r,i<<1|1);
		tree[i]=max(tree[i<<1],tree[i<<1|1]);
	}
}
namespace Seg2{
	pair<int,int> tree[800005];
	void build(int l=1,int r=n,int i=1){
		if(l==r){
			tree[i]=make_pair(l,l);
			return ;
		}
		int mid=(l+r)>>1;
		build(l,mid,i<<1);build(mid+1,r,i<<1|1);
		tree[i]=min(tree[i<<1],tree[i<<1|1]);
	}
	pair<int,int> query(int fr,int to,int l=1,int r=n,int i=1){
		if(fr>r||to<l)return make_pair(1e9,1e9);
		if(fr<=l&&to>=r)return tree[i];
		int mid=(l+r)>>1;
		return min(query(fr,to,l,mid,i<<1),query(fr,to,mid+1,r,i<<1|1));
	}
	void update(int loc,int v,int l=1,int r=n,int i=1){
		if(loc<l||loc>r)return ;
		if(l==r){
			tree[i]=make_pair(v,l);
			return ;
		}
		int mid=(l+r)>>1;
		update(loc,v,l,mid,i<<1);update(loc,v,mid+1,r,i<<1|1);
		tree[i]=min(tree[i<<1],tree[i<<1|1]);
	}
}
set<int> s[200005];
int dsu[200005];
inline void insert(int v,int x){
	auto it=s[x].insert(v).first;
	if(it!=s[x].begin()){
		auto L=it;--L;
		Seg1::update(*L,v);Seg2::update(v,*L);
	}
	else Seg2::update(v,v);
	auto R=it;++R;
	if(R!=s[x].end())Seg1::update(v,*R),Seg2::update(*R,v);
	else Seg1::update(v,v);dsu[v]=x;
}
inline void merge(int x,int y){
	x=dsu[x];y=dsu[y];if(x==y)return ;
	if(s[x].size()>s[y].size())swap(x,y);
	for(auto it:s[x])insert(it,y);
}
inline void cover(int l,int r){
	if(l>r)swap(l,r);
	merge(l,r);auto it=Seg1::query(l,r-1);
	while(it.first>r){
		merge(it.second,r);
		it=Seg1::query(l,r-1);
	}
	it=Seg2::query(l+1,r);
	while(it.first<l){
		merge(it.second,l);
		it=Seg2::query(l+1,r);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	Seg1::build();Seg2::build();
	for(int i=1;i<=n;i++)dsu[i]=i;
	for(int i=1;i<=n;i++)s[i].insert(i);
	while(m--){
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(op==1)cover(x,y);
		else putchar(dsu[x]==dsu[y]?'1':'0');
	}


	return 0;
}


CF1129D Isolation

考虑暴力 \(dp\)\(O(n^2)\) 的,线段树不太容易做,分块即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k,s=500;
int a[100005],pre[100005],nxt[100005];
const int md=998244353;
int dp[100005],tmp[205][100005],high[100005],sum[205],tag[205];
int le[100005],ri[100005],sqr[100005];
inline void build(int x){
	for(int i=le[x];i<=ri[x];i++)tmp[x][high[i]]=0;
	for(int i=le[x];i<=ri[x];i++)high[i]=high[i]+tag[x];tag[x]=0;sum[x]=0;
	for(int i=le[x];i<=ri[x];i++){
		if(high[i]<=k)sum[x]=(sum[x]+dp[i])%md;
		tmp[x][high[i]]=(tmp[x][high[i]]+dp[i])%md;
	}
}
inline void update(int l,int r,int v){
	for(int i=l;i<=r&&i<=ri[sqr[l]];i++){
		tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]-dp[i])%md;
		if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]-dp[i])%md;
		high[i]+=v;
		tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]+dp[i])%md;
		if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]+dp[i])%md;
	}
	if(sqr[l]==sqr[r])return ;
	for(int i=sqr[l]+1;i<sqr[r];i++){
		if(~v&&k>=tag[i])sum[i]=(sum[i]-tmp[i][k-tag[i]])%md;
		else if(v==-1&&k>=tag[i]-1)sum[i]=(sum[i]+tmp[i][k-tag[i]+1])%md;
		tag[i]+=v;
	}
	for(int i=le[sqr[r]];i<=r;i++){
		tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]-dp[i])%md;
		if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]-dp[i])%md;
		high[i]+=v;
		tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]+dp[i])%md;
		if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]+dp[i])%md;
	}
}
inline int query(int l,int r){
	int res=0;
	for(int i=l;i<=r&&i<=ri[sqr[l]];i++)if(high[i]+tag[sqr[i]]<=k)res=(res+dp[i])%md;
	if(sqr[l]==sqr[r])return res;
	for(int i=sqr[l]+1;i<sqr[r];i++)res=(res+sum[i])%md;
	for(int i=le[sqr[r]];i<=r;i++)if(high[i]+tag[sqr[i]]<=k)res=(res+dp[i])%md;
	return res;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=0;i<=n;i++)sqr[i]=i/s+1;dp[0]=1;
	for(int i=0;i<=n;i++)ri[sqr[i]]=i;
	for(int i=n;~i;i--)le[sqr[i]]=i;
	for(int i=1;i<=n;i++)pre[i]=1;
	for(int i=1;i<=n;i++){
		if(!nxt[a[i]])update(0,i-1,1),nxt[a[i]]=i;
		else {
			update(pre[a[i]]-1,nxt[a[i]]-1,-1);pre[a[i]]=nxt[a[i]]+1;nxt[a[i]]=i;
			update(pre[a[i]]-1,nxt[a[i]]-1,1);
		}
		dp[i]=query(0,i-1);
		tmp[sqr[i]][high[i]]=(tmp[sqr[i]][high[i]]+dp[i])%md;
		if(high[i]+tag[sqr[i]]<=k)sum[sqr[i]]=(sum[sqr[i]]+dp[i])%md;
		if(i==ri[sqr[i]])build(sqr[i]);
	}
	printf("%d",(dp[n]+md)%md);

	return 0;
}

[AGC015E] Mr.Aoki Incubator

将点按 \(X\) 排序,容易发现可以给一个点染色的是一个区间,线段树优化 \(dp\) 即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int md=1e9+7;
struct dong{
    int x,v,id;
}a[200005];
struct suan{
    int l,r;
    friend bool operator <(suan a,suan b){
        return a.r<b.r||a.r==b.r&&a.l<b.l;
    }
}b[200005];
int tree[800005],le[200005],ri[200005],f[200005];
int l,t,n,m,mi,mx,ans;
inline bool cmp(dong a,dong b){
    return a.v<b.v;
}
inline bool cmp2(dong a,dong b){
    return a.x<b.x;
}
void change(int i,int l,int r,int a,int b){
    if(l==r){
        tree[i]=(tree[i]+b)%md;
        return;
    }
    int mid=(l+r)>>1;
    if(a<=mid)change(i<<1,l,mid,a,b);else change(i<<1|1,mid+1,r,a,b);
    tree[i]=(tree[i<<1]+tree[i<<1|1])%md;
}
int query(int i,int l,int r,int a,int b){
    if(l==a&&r==b)return tree[i];
    int mid=(l+r)>>1;
    if(b<=mid)return query(i<<1,l,mid,a,b);
    else if(a>mid)return query(i<<1|1,mid+1,r,a,b);
    else return (query(i<<1,l,mid,a,mid)+query(i<<1|1,mid+1,r,mid+1,b))%md;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i].x,&a[i].v);
	}
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)a[i].id=i;
    sort(a+1,a+n+1,cmp2);
    mi=n+1;
    for(int i=n;i;i--){
        mi=min(mi,a[i].id);
        le[a[i].id]=mi;
    }
    mx=0;
    for(int i=1;i<=n;i++){
        mx=max(mx,a[i].id);
        ri[a[i].id]=mx;
    }
    for(int i=1;i<=n;i++)b[i].l=le[i],b[i].r=ri[i];
    sort(b+1,b+n+1);
    for(int i=1;i<=n;i++){
        if(b[i].l==1)f[i]++;
        t=max(b[i].l-1,1);
        f[i]=(f[i]+query(1,1,n,t,b[i].r))%md;
        change(1,1,n,b[i].r,f[i]);
        if(b[i].r==n)ans=(ans+f[i])%md;
    }
    printf("%d\n",ans);

	return 0;
}

[AGC007E] Shik and Travel

考虑二分,发现对于一棵子树,可以伸出来两条路径,然后将两个儿子的四条路径两条匹配,两条留下,其中匹配长度之和不能超过 \(\text{mid}\) ,可以用 \(\text{map}\) 维护,匹配时直接 \(\text{upper_bound}\)\(O(n\log^3 n)\) 的,双指针可以做到 \(O(n\log^2 n)\) ,实际运行时差别不大。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int le[200005],ri[200005],val[200005];
map<long long,long long> mp[200005];
long long mid;
void dfs(int x){
	if(!le[x]&&!ri[x]){
		mp[x].clear();mp[x][val[x]]=val[x];
		return ;
	}
	int L=le[x],R=ri[x];
	dfs(L);dfs(R);
	if(mp[L].size()>mp[R].size())swap(L,R);
	vector<pair<long long,long long> > tmp;
	for(auto it:mp[L]){
		auto it1=mp[R].upper_bound(mid-it.second);
		if(it1==mp[R].begin())continue;else it1--;
		tmp.push_back(make_pair(it.first,it1->second));
		tmp.push_back(make_pair(it1->second,it.first));
	}
	sort(tmp.begin(),tmp.end());
	mp[x].clear();long long mn=1e18;
	for(int i=0;i<tmp.size();i++){
		if(mn>=tmp[i].second)mp[x][tmp[i].first+val[x]]=tmp[i].second+val[x],mn=tmp[i].second;
	}
//	cout<<"check "<<mid<<" "<<x<<endl;
//	for(auto it:mp[x])cout<<it.first<<" "<<it.second<<endl;
}
int main(){
	scanf("%d",&n);
	for(int i=2;i<=n;i++){
		int x;scanf("%d%d",&x,&val[i]);
		if(le[x])ri[x]=i;else le[x]=i;
	}
	long long l=0,r=1e9,ans=1e9;
	while(l<=r){
		mid=(l+r)>>1;
		dfs(1);
		if(mp[1].size())ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld",ans);


	return 0;
}

CF1446D2 Frequency Problem (Hard Version)

发现最大区间的两个众数中一定有一个是整个序列的众数,可以枚举另一个数做到 \(O(n|a|)\) ,考虑根号分治对于出现次数大于 \(\sqrt n\) 的数暴力做,对于小于 \(\sqrt n\) 的数枚举答案即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,s=500;
int a[200005],tot[200005],pre[400005];
int ans;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)tot[a[i]]++;
	int Tmp=0;
	for(int i=1;i<=n;i++)if(tot[i]>tot[Tmp])Tmp=i;
	for(int t=1;t<=n;t++){
		if(tot[t]<=s||tot[t]==Tmp)continue;
		for(int i=0;i<=2*n;i++)pre[i]=0;
		int cnt=0;
		for(int i=1;i<=n;i++){
			if(a[i]==Tmp)cnt++;
			else if(a[i]==t)cnt--;
			if(pre[cnt+n]||!cnt)ans=max(ans,i-pre[n+cnt]);
			else pre[cnt+n]=i;
		}
	}
	for(int t=1;t<=s;t++){
		int cnt=0,L=0;
		for(int i=1;i<=n;i++)tot[i]=0;
		for(int i=1;i<=n;i++){
			tot[a[i]]++;
			if(tot[a[i]]==t)cnt++;
			while(tot[a[i]]>t){
				if(tot[a[L+1]]==t)cnt--;
				tot[a[++L]]--;
			}
			if(cnt>=2)ans=max(ans,i-L);
		}
	}
	printf("%d\n",ans);

	return 0;
}



CF765F Souvenirs

考虑扫描线,先递归右儿子,再递归左儿子,如果在当前区间得到的答案比之前更新过的区间劣,直接退出即可,容易发现答案最多发生 \(\log a\) 次变化,时间复杂度 \(O(n\log n\log a)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[100005];
struct qry{
	int l,r,id;
	inline bool operator <(const qry &b)const{
		return r<b.r;
	}
}q[300005];
vector<int> vec[400005];
int ans[300005];
int tree[400005],res=1e9;
void build(int l=1,int r=n,int i=1){
	for(int j=l;j<=r;j++)vec[i].push_back(a[j]);
	sort(vec[i].begin(),vec[i].end());
	tree[i]=1e9;
	if(l==r)return ;
	int mid=(l+r)>>1;
	build(l,mid,i<<1);
	build(mid+1,r,i<<1|1);
}
void update(int loc,int id,int l=1,int r=n,int i=1){
	if(loc>r){
    	vector<int>::iterator it=lower_bound(vec[i].begin(),vec[i].end(),a[id]);
    	if(it!=vec[i].end()){
	    	tree[i]=min(tree[i],abs(*it-a[id]));
    	}
    	if(it!=vec[i].begin()){
	    	it--;
    		tree[i]=min(tree[i],abs(*it-a[id]));
	    }
//    	printf("%d %d %d\n",l,r,tree[i]);
	}
	if(loc<=l)return ;
	if(tree[i]>=res||l==r)return ;
	int mid=(l+r)>>1;
	update(loc,id,mid+1,r,i<<1|1);
	update(loc,id,l,mid,i<<1);
	tree[i]=min(tree[i<<1],tree[i<<1|1]);
	res=min(res,tree[i]);
}
int query(int fr,int l=1,int r=n,int i=1){
	if(r<fr)return 1e9;
	if(l>=fr)return tree[i];
	int mid=(l+r)>>1;
	return min(query(fr,l,mid,i<<1),query(fr,mid+1,r,i<<1|1));
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	build();
	scanf("%d",&m);
	for(int i=0;i<m;i++){
		scanf("%d%d",&q[i].l,&q[i].r);q[i].id=i;
	}
	sort(q,q+m);
	for(int i=1,j=0;i<=n;i++){
		res=2e9;
//		cout<<i<<"\n";
		update(i,i);
//		puts("");
		while(q[j].r<=i&&j<m){
			ans[q[j].id]=query(q[j].l);j++;
		}
	}
	for(int i=0;i<m;i++)printf("%d\n",ans[i]);

	return 0;
}

CF1458D Flip and Reverse

考虑记 \(0\)\(-1\)\(1\)\(+1\),这样可以得到一个长度为 \(|s|\) 的由 \(+1\)\(-1\) 组成的序列。

然后对这个序列做一遍前缀和,并连一条 \(s_i\to s_{i+1}\) 的有向边,这样可以得到一张图,一个欧拉回路就对应着一个字符串。

考虑题目中那个奇怪的操作的本质。假设我们对区间 \([l,r]\) 进行操作。既然 \([l,r]\) 要求 \(01\) 个数相等,那么肯定有 \(s_{l-1}=s_r\) ,而翻转+反转实际上等于将这些边反向。所以实际上该操作等价于选择一个环然后将环上所有边反向。

原图任意一条欧拉回路(起点和终点必须与初始相同)代表的都可以由原字符串进行一系列操作得到,就像 \(\text{ETT}\) 那样通过翻转交换两个区间就好了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T,n;
char s[500005];
int tot[1000005][2];
inline void solve(){
	scanf("%s",s+1);n=strlen(s+1);
	int cnt=n;
	for(int i=1;i<=n;i++){
		tot[cnt][s[i]-'0']++;
		if(s[i]=='0')cnt--;else cnt++;
	}
	cnt=n;
	for(int i=1;i<=n;i++){
		if(tot[cnt][0]&&tot[cnt-1][1])putchar('0'),tot[cnt--][0]--;
		else if(tot[cnt][1])putchar('1'),tot[cnt++][1]--;
		else putchar('0'),tot[cnt--][0]--;
	}
	puts("");
}
int main(){
	scanf("%d",&T);
	while(T--)solve();

	return 0;
}

【UR #22】月球列车

发现我们需要统计的只是每一位 \(1\) 的个数的奇偶性,容易发现对每一位排序后,每一位会发生进位的是一段后缀,可以二分做到 \(O(n\log a\log n)\) ,发现每一位只有加 \(0\)\(1\) 两种可能,可以通过分类讨论代替二分,时间复杂度 \(O(n\log a)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,t;
long long a[250005];
int nxt[64][250005][2],tmp[250005],stk[250005];
inline long long solve(long long x){
	int lim=n;long long res=0;
	for(int i=0;i<=60;i++){
		int cnt=nxt[i][lim][1]-nxt[i][lim][0];
		if((x>>i)&1)cnt=n-cnt,lim=nxt[i][lim][0];
		else lim=nxt[i][lim][1];
		if(cnt&1)res+=(1ll<<i);
	}
	return res;
}
int main(){
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)tmp[i]=i;
	for(int t=0;t<=60;t++){
		int cnt=0;
		nxt[t][0][0]=cnt;
		for(int i=1;i<=n;i++){
			if(((a[tmp[i]]>>t)&1)==0)stk[++cnt]=tmp[i];
			nxt[t][i][0]=cnt;
		}
		nxt[t][0][1]=cnt;
		for(int i=1;i<=n;i++){
			if((a[tmp[i]]>>t)&1)stk[++cnt]=tmp[i];
			nxt[t][i][1]=cnt;
		}
		for(int i=1;i<=n;i++)tmp[i]=stk[i];
	}
	long long lstans=0;
	while(m--){
		long long x;scanf("%lld",&x);
		x^=t*(lstans>>20);
		printf("%lld\n",lstans=solve(x));

	}


	return 0;
}

GYM 103371M Yet Another Range Query Problem

CF643G Choosing Ads

如果 \(p=50+\varepsilon\) 会怎么样?

这便是一个经典问题:长度为 \(n\) 的数列中存在一个出现次数大于 \(n / 2\) 的数,设计一个算法找到它。

只要每次删除两个不同的数,最后留下的那个数(或那些数,但这些数全部相同)就是要求的答案。

原理是,如果一个数出现了 \(a\) 次,其中 \(a > n - a\),则两边都减去 \(1\),仍有 \(a - 1 > n - a - 1 = (n - 2) - (a - 1)\)

对于拓展情况我们如法炮制,令 \(\displaystyle k = \left\lfloor \frac{100}{p} \right\rfloor\) 。每次删除 \(k + 1\) 个数,则对的数一定会留下来。

因为 \(k\) 最大就是 \(5\),建立一棵线段树,使用每次 \(\mathcal O (k^2)\) 的时间合并区间,不难维护答案。

时间复杂度为 \(\mathcal O (n k^2 \log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,p;
inline bool cmp(pair<int,int> x,pair<int,int> y){
	return x.second>y.second;
}
struct node : vector<pair<int,int> >{
	inline node operator +(node b){
		for(auto x:*this){
			bool is=0;
			for(auto &y:b)if(y.first==x.first)is=1,y.second+=x.second;
			if(!is)b.push_back(x);
		}
		sort(b.begin(),b.end(),cmp);
		while(b.size()>p){
			for(auto &x:b)x.second-=b.back().second;
			b.pop_back();
		}return b;
	}
}tree[600005];
int a[150005];
void build(int l=1,int r=n,int i=1){
	if(l==r){
		tree[i].push_back(make_pair(a[l],1));
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,i<<1);build(mid+1,r,i<<1|1);
	tree[i]=tree[i<<1]+tree[i<<1|1];
}
int lazy[600005];
inline void solve(int i,int l,int r,int x){
	tree[i].clear();tree[i].push_back(make_pair(x,r-l+1));
	lazy[i]=x;
}
inline void push(int i,int l,int r){
	int mid=(l+r)>>1;
	solve(i<<1,l,mid,lazy[i]);solve(i<<1|1,mid+1,r,lazy[i]);
	lazy[i]=0;
}
void update(int fr,int to,int v,int l=1,int r=n,int i=1){
	if(fr>r||to<l)return ;
	if(fr<=l&&to>=r){
		solve(i,l,r,v);return ;
	}
	int mid=(l+r)>>1;
	if(lazy[i])push(i,l,r);
	update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
	tree[i]=tree[i<<1]+tree[i<<1|1];
}
node query(int fr,int to,int l=1,int r=n,int i=1){
	if(fr>r||to<l)return node();
	if(fr<=l&&to>=r)return tree[i];
	int mid=(l+r)>>1;
	if(lazy[i])push(i,l,r);
	return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int main(){
	scanf("%d%d%d",&n,&m,&p);p=100/p;
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	build();
	while(m--){
		int op,l,r,x;
		scanf("%d%d%d",&op,&l,&r);
		if(op==1){
			scanf("%d",&x);update(l,r,x);
		}
		else {
			node tmp=query(l,r);
			printf("%ld\n",tmp.size());
			for(auto x:tmp)printf("%d ",x.first);puts("");
		}
	}

	return 0;
}


CF1209G2 Into Blocks (hard version)

考虑把一个颜色 \(c\) 的出现区间化成左开右闭的形式 \([s_c,t_c)\) ,意思是对于区间中的每个 \(i\) ,要把 \(i,i+1\) 涂成一个颜色。那么可以发现如果对所有 \(c\) 的该区间进行区间 \(+1\) ,那么最后可以保留的颜色数就是相邻两个 \(0\) 之间出现最多的颜色的数量和。

考虑用线段树快速维护这个东西。考虑需要维护什么信息:

  1. 需要维护分界点。即区间中被覆盖次数为 \(0\) 的点。

  2. 需要维护颜色数。即区间中出现次数最多的颜色。

  3. 但是 \(2\) 中有要求,要求必须是连续段中出现次数最多的颜色。而这个连续段的「连续」对应于一段连续的、覆盖次数 \(>0\) 的段。

  4. 注意到要维护区间中出现次数的最大值,还要求连续且只有全局询问的话,有个小技巧。考虑让颜色 \(c\) 的出现次数 \(e(c)\) 放到 \(s_c\) 处维护,记作 \(w_{s_c}\) ,这样就只需要维护一个 \(\max \{w_i,w_{i+1}\}\) 状物。

发现难以维护分界点?考虑到只有全局询问,于是可以用维护「区间最小值」来代替维护「区间分界点」。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int tag[200005];
set<int> col[200005];
int mx[800005],mn[800005],lx[800005],rx[800005],tree[800005];
inline void pushup(int i){
	mn[i]=min(mn[i<<1],mn[i<<1|1]);mx[i]=max(mx[i<<1],mx[i<<1|1]);
	if(mn[i<<1]<mn[i<<1|1]){
		lx[i]=lx[i<<1];rx[i]=max(rx[i<<1],mx[i<<1|1]);
		tree[i]=tree[i<<1];
	}
	else if(mn[i<<1]>mn[i<<1|1]){
		lx[i]=max(lx[i<<1|1],mx[i<<1]);rx[i]=rx[i<<1|1];
		tree[i]=tree[i<<1|1];
	}
	else {
		lx[i]=lx[i<<1];rx[i]=rx[i<<1|1];
		tree[i]=tree[i<<1]+max(rx[i<<1],lx[i<<1|1])+tree[i<<1|1];
	}
}
int lazy[800005];
inline void push(int i){
	mn[i<<1]+=lazy[i];lazy[i<<1]+=lazy[i];
	mn[i<<1|1]+=lazy[i];lazy[i<<1|1]+=lazy[i];
	lazy[i]=0;
}
void update(int fr,int to,int v,int l=1,int r=n,int i=1){
	if(fr>r||to<l)return ;
	if(fr<=l&&to>=r){
		mn[i]+=v;lazy[i]+=v;
		return ;
	}
	if(lazy[i])push(i);
	int mid=(l+r)>>1;
	update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
	pushup(i);
}
void insert(int loc,int v,int l=1,int r=n,int i=1){
	if(loc<l||loc>r)return ;
	if(l==r){
		mx[i]=lx[i]=v;
		return ;
	}
	if(lazy[i])push(i);
	int mid=(l+r)>>1;
	insert(loc,v,l,mid,i<<1);insert(loc,v,mid+1,r,i<<1|1);
	pushup(i);
}
inline void add(int x){
	if(col[x].empty())return ;
	update(*col[x].begin(),*col[x].rbegin()-1,1);
	insert(*col[x].begin(),col[x].size());
}
inline void del(int x){
	if(col[x].empty())return ;
	update(*col[x].begin(),*col[x].rbegin()-1,-1);
	insert(*col[x].begin(),0);
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&tag[i]);
		col[tag[i]].insert(i);
	}
	for(int i=1;i<=2e5;i++)add(i);
	printf("%d\n",n-lx[1]-tree[1]-rx[1]);
	while(q--){
		int x,y;scanf("%d%d",&x,&y);
		del(tag[x]);col[tag[x]].erase(x);add(tag[x]);
		tag[x]=y;
		del(tag[x]);col[tag[x]].insert(x);add(tag[x]);
		printf("%d\n",n-lx[1]-tree[1]-rx[1]);
	}

	return 0;
}


CF1270H Number of Components

考虑枚举分界点,将序列转化成 \(01\) 序列,一个分界点对应着一个前缀全是 \(1\) 后缀全是 \(0\) 的划分,考虑对于每个值维护 \(10\) 交界处的数量,维护有多少个值可以使得这个序列只有一个 \(10\) 即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int tree[4000005],tot[4000005],lazy[4000005];
inline void pushup(int i){
	tree[i]=min(tree[i<<1],tree[i<<1|1]);
	if(tree[i<<1]<tree[i<<1|1])tot[i]=tot[i<<1];
	else if(tree[i<<1]>tree[i<<1|1])tot[i]=tot[i<<1|1];
	else tot[i]=tot[i<<1]+tot[i<<1|1];
}
inline void push(int i){
	tree[i<<1]+=lazy[i];lazy[i<<1]+=lazy[i];
	tree[i<<1|1]+=lazy[i];lazy[i<<1|1]+=lazy[i];
	lazy[i]=0;
}
void insert(int loc,int v,int l=0,int r=1e6+1,int i=1){
	if(loc<l||loc>r)return ;
	if(l==r){
		tot[i]+=v;
		return ;
	}
	if(lazy[i])push(i);
	int mid=(l+r)>>1;
	insert(loc,v,l,mid,i<<1);insert(loc,v,mid+1,r,i<<1|1);
	pushup(i);
}
void update(int fr,int to,int v,int l=0,int r=1e6+1,int i=1){
	if(fr>r||to<l)return ;
	if(fr<=l&&to>=r){
		tree[i]+=v;lazy[i]+=v;
		return ;
	}
	if(lazy[i])push(i);
	int mid=(l+r)>>1;
	update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
	pushup(i);
}
int query(int fr,int to,int l=0,int r=1e6+1,int i=1){
	if(fr>r||to<l)return 0;
	if(fr<=l&&to>=r)return tree[i]==1?tot[i]:0;
	if(lazy[i])push(i);
	int mid=(l+r)>>1;
	return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int a[500005];
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	a[0]=1e6+1;for(int i=0;i<=n;i++)update(a[i+1],a[i]-1,1);
	for(int i=0;i<=n+1;i++)insert(a[i],1);
	while(q--){
		int x,y;scanf("%d%d",&x,&y);
		update(a[x+1],a[x]-1,-1);update(a[x],a[x-1]-1,-1);insert(a[x],-1);
		a[x]=y;
		update(a[x+1],a[x]-1,1);update(a[x],a[x-1]-1,1);insert(a[x],1);
		printf("%d\n",query(1,1e6));
	}


	return 0;
}



【UR #19】前进四

容易想到一种类似于楼房重建的方式做到 \(O(n\log^2n)\) ,但是这是不够的,考虑单 \(\log\) 做法。

发现询问都为后缀,以时间为轴开线段树,倒序扫描序列,对于每个位置,每个数出现的时间都是一段区间,吉如一线段树维护每个位置被取 \(\min\) 的次数即可,时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
struct data{
	int x,l,r;
	data(int _x,int _l,int _r){
		x=_x;l=_l;r=_r;
	}
};
vector<data> vec[1000005];
vector<int> qry[1000005];
struct node{
	int mx1,mx2;
	node(){}
	node(int _mx1,int _mx2){
		mx1=_mx1;mx2=_mx2;
	}
	inline node operator +(const node &b)const{
		if(mx1>b.mx1)return node(mx1,max(mx2,b.mx1));
		if(mx1<b.mx1)return node(b.mx1,max(b.mx2,mx1));
		return node(mx1,max(mx2,b.mx2));
	}
}tree[4000005];
int lazy[4000005];
inline void push(int i){
	tree[i<<1].mx1=min(tree[i<<1].mx1,tree[i].mx1);
	tree[i<<1|1].mx1=min(tree[i<<1|1].mx1,tree[i].mx1);
}
void build(int l=0,int r=q,int i=1){
	tree[i]=node(1e9,0);
	if(l==r)return ;
	int mid=(l+r)>>1;
	build(l,mid,i<<1);build(mid+1,r,i<<1|1);
}
void update(int fr,int to,int v,int l=0,int r=q,int i=1){
	if(fr>r||to<l||tree[i].mx1<=v)return ;
	if(fr<=l&&to>=r&&tree[i].mx2<v){
		lazy[i]++;tree[i].mx1=v;
		return ;
	}
	int mid=(l+r)>>1;push(i);
	update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
	tree[i]=tree[i<<1]+tree[i<<1|1];
}
int query(int loc,int l=0,int r=q,int i=1){
	if(loc<l||loc>r)return 0;
	if(l==r)return lazy[i];
	int mid=(l+r)>>1;push(i);
	return lazy[i]+query(loc,l,mid,i<<1)+query(loc,mid+1,r,i<<1|1);
}
int ans[1000005];
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		int x;scanf("%d",&x);
		vec[i].push_back(data(x,0,q));
	}
	for(int i=1;i<=q;i++){
		int op,x,y;
		scanf("%d",&op);
		if(op==1){
			scanf("%d%d",&x,&y);
			vec[x].back().r=i-1;vec[x].push_back(data(y,i,q));
		}
		else if(op==2){
			scanf("%d",&x);qry[x].push_back(i);
		}
	}
	build();
	for(int i=n;i;i--){
		for(auto it:vec[i])update(it.l,it.r,it.x);
		for(auto it:qry[i])ans[it]=query(it);
	}
	for(int i=1;i<=q;i++)if(ans[i])printf("%d\n",ans[i]);


	return 0;
}



CF1558F Strange Sort

CF1137F Matches Are Not a Child's Play

不妨令权值最大的点为根,并设 \(a_x\)\(x\) 子树中权值最大的点的权值。那么,容易发现删点顺序就是以 \(a_x\) 递增为第一关键字,\(x\) 的深度递减为第二关键字。

考虑一次 \(up(x)\) 操作的影响,由于 \(x\) 将变成最大的点,所以要以 \(x\) 作为新的根。设原来的根是 \(rt\),那么显然,只有 \(x\)\(rt\) 这条链上面的点的 \(a_i\) 发生了变化,且对路径上除 \(x\) 外任意一点 \(y\)\(a_y\leftarrow a_{rt}\) ,而 \(a_x \leftarrow a_{rt}+1\)

我们注意到操作相当于链赋值,并且 \(a_i\) 相等的点构成的连通块一定是链,所以每个 \(a_i\) 相等的链可以用 \(\text{LCT}\) 的实链维护。上述 \(up\) 过程则可以魔改一下 \(\text{access}\) 函数,一边跳虚边一边在维护每个权值出现次数的树状数组上修改即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int ver[400005],ne[400005],head[200005],tot;
inline void link(int x,int y){
	ver[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
}
struct BIT{
	int tree[400005];
	inline void add(int x,int v){
		while(x<=n+m){
			tree[x]+=v;
			x+=(x&-x);
		}
	}
	inline int query(int x){
		int res=0;
		while(x){
			res+=tree[x];
			x&=(x-1);
		}
		return res;
	}
}B;
namespace LCT{
	int fa[200005],son[2][200005],siz[200005],lazy[200005],col[200005],rt,now;
	bool rev[200005];
	inline void pushup(int x){
		siz[x]=siz[son[0][x]]+1+siz[son[1][x]];
	}
	inline bool isroot(int x){
		return son[1][fa[x]]!=x&&son[0][fa[x]]!=x;
	}
	inline void reverse(int x){
		rev[x]^=1;swap(son[0][x],son[1][x]);
	}
	inline void push(int x){
		if(rev[x]){
			reverse(son[0][x]);
			reverse(son[1][x]);
		}rev[x]=0;
		if(lazy[x]){
			col[son[0][x]]=lazy[son[0][x]]=lazy[x];
			col[son[1][x]]=lazy[son[1][x]]=lazy[x];
		}lazy[x]=0;
	}
	inline void rotate(int x){
		int y=fa[x],z=fa[y];
		if(!isroot(y))son[son[1][z]==y][z]=x;
		bool is=(son[1][y]==x);
		son[is][y]=son[!is][x];fa[son[is][y]]=y;
		son[!is][x]=y;fa[y]=x;fa[x]=z;pushup(y);pushup(x);
	}
	int stk[200005],top;
	inline void splay(int x){
		stk[++top]=x;
		for(int i=x;!isroot(i);i=fa[i])stk[++top]=fa[i];
		while(top)push(stk[top--]);
		while(!isroot(x)){
			int y=fa[x],z=fa[y];
			if(!isroot(y)){
				if((son[1][y]==x)^(son[1][z]==y))rotate(x);
				else rotate(y);
			}rotate(x);
		}
	}
	inline void access(int x,int v){
		int i=0;
		while(x){
			splay(x);B.add(col[x],-siz[son[0][x]]-1);son[1][x]=i;pushup(x);
			i=x;x=fa[x];
		}
		B.add(v,siz[i]);col[i]=lazy[i]=v;
	}
	inline void makert(int x){
		access(x,col[rt]);B.add(col[rt],-1);splay(x);
		reverse(x);col[x]=++now;B.add(now,1);rt=x;push(x);son[1][x]=0;
	}
	inline int query(int x){
		splay(x);
		return B.query(col[x]-1)+siz[son[1][x]]+1;
	}
	void dfs(int x,int fi){
		fa[x]=fi;
		for(int i=head[x];i;i=ne[i]){
			int u=ver[i];
			if(u==fi)continue;
			dfs(u,x);
		}
	}
	int vis[200005];
	inline void init(){
		rt=now=n;
		for(int i=n;i>=1;i--){
			int x=i,y=0,cnt=0;
			while(x&&!vis[x])col[x]=i,vis[x]=1,son[1][x]=y,pushup(x),cnt++,y=x,x=fa[x];
			B.add(i,cnt);
		}
	}
}
char op[10];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		int x,y;scanf("%d%d",&x,&y);
		link(x,y);link(y,x);
	}
	LCT::dfs(n,0);LCT::init();
	for(int i=1;i<=m;i++){
		int x,y;scanf("%s%d",op,&x);
		if(op[0]=='u')LCT::makert(x);
		if(op[0]=='w')printf("%d\n",LCT::query(x));
		if(op[0]=='c'){
			scanf("%d",&y);
			printf("%d\n",LCT::query(x)<LCT::query(y)?x:y);
		}
	}


	return 0;
}

CF1034D Intervals of Intervals

先考虑弱化版,给一些区间,多次查询一些连续区间的并的长度。

把询问离线,按右端点排序。当右端点 \(R\) 不断右移(加入新区间),维护每个 \(L\)\(R\) 的区间的并的长度 \(f[L]\)

不妨认为新加入的区间覆盖了先前的区间。那么新加入区间的贡献是什么呢?对于 \([L,R-1]\) ,加入 \(R\) 后,新增的被覆盖的是原本空的地方,就等于 \(|R|\) 减 原被覆盖的区间的长度。所以可以考虑加上 \(R\) 的长,再减去原本在这个区间中的那些区间的长。

以上可以使用 \(\text{ODT}\) 和树状数组维护,复杂度是 \(O(n \log n)\)

找前 \(k\) 大的区间并。不妨考虑求第 \(k\) 大的区间并,设其长为 \(len\) ,然后对于每个 \(R\),求 \(\sum_{[f(L)\ge len]}{f(L)}\) 。由于长度大于等于 \(len\) 的区间并的数量可能大于 \(k\) ,还需要计算数量并且减去多的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
struct node{
    int l,r;
    mutable int v;
    node(){}
    node(int _l,int _r,int _v){
        l=_l,r=_r,v=_v;
    }
    inline bool operator <(const node a)const{
        return l<a.l;
    }
};
set<node> odt;
inline auto split(int x){
    if(x>1e9)return odt.end();
    auto it=--odt.upper_bound(node(x,0,0));
    if(it->l==x)return it;
    node v=*it;odt.erase(it);
    odt.insert(node(v.l,x-1,v.v));
    return odt.insert(node(x,v.r,v.v)).first;
}
vector<pair<int, int> > seg[300005];
inline void assign(int l,int r,int v){
    auto ir=split(r+1),il=split(l);
    for(auto it=il;it!=ir;it++){
        if(it->v)seg[v].emplace_back(it->v,it->r-it->l+1);
    }
    odt.erase(il,ir);
    odt.insert(node(l,r,v));
    sort(seg[v].begin(),seg[v].end());
}
pair<int,int> a[300005];
long long ans;
int d[300005];
inline int check(int len){
    int val=0;
    for(int i=0;i<=n+1;i++)d[i]=0;
    long long sum=0,res=0,cnt=0;
    int l=1;
    for(int r=1;r<=n;r++){
        val+=a[r].second-a[r].first+1;
        sum+=1ll*(a[r].second-a[r].first+1)*l;
        d[r+1]-=a[r].second-a[r].first+1;
        for(auto it:seg[r]){
            if(it.first<l)sum-=1ll*it.first*it.second;
            else {
                val-=it.second;
                sum-=1ll*l*it.second;
                d[it.first+1]+=it.second;
            }
        }
        while(val>=len){
            ++l;
            val+=d[l];
            sum+=val;
        }
        cnt+=l-1;
        res+=sum-val;
    }
    if(cnt<k)return 0;
    ans=res-1ll*(cnt-k)*len;
    return 1;
}
int main(){
	scanf("%d%d",&n,&k);
    odt.insert(node(0,1e9+2,0));
    for(int i=1;i<=n;i++){
		scanf("%d%d",&a[i].first,&a[i].second);
        --a[i].second;
        assign(a[i].first,a[i].second,i);
    }
    int l=0,r=1e9+1;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
	printf("%lld\n",ans);


    return 0;
}

【UNR #5】诡异操作

首先可以想到一个 \(O(128\times n+128\times q\log n )\) 的做法,空间开不下,时间也过不去。

考虑这一做法在计算机中的储存方式,每一个节点维护区间内 \(128\) 个数位每位 \(1\) 的数量,每个数量用 \(1\)\(32\) 位的 \(\text{int}\) 来储存,实际上有用的不超过 \(\log {(r-l+1)}\) 位。

容易发现这是一个 \(128\times \log {(r-l+1)}\)\(01\) 表,我们考虑沿对角线翻转它,复杂度就变成了 \(O(n\log n+q\log^2n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
inline int read(){
	int res=0;char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9')res=10*res+c-'0',c=getchar();
	return res;
}
typedef __uint128_t u128;
inline u128 read_u(){
    static char buf[200];
    scanf("%s",buf);u128 res=0;
    for(int i=0;buf[i];i++)res=res<<4|(buf[i]<='9'?buf[i]-'0':buf[i]-'a'+10);
    return res;
}
inline void output(u128 res){
    if(res>=16)output(res/16);
    putchar(res%16>=10?'a'+res%16-10:'0'+res%16);
}
u128 *vec[1200005],pool[2000005],*lim=pool;
int empty[1200005],cnt[1200005];
u128 a[300005],lazy[1200005];
inline void pushup(int x){
	u128 flag=0;
	for(int i=0;i<cnt[x];i++){
		u128 a=(cnt[x<<1]>i?vec[x<<1][i]:0),b=(cnt[x<<1|1]>i?vec[x<<1|1][i]:0);
		vec[x][i]=a^b^flag;
		flag=((a^b)&flag)|(a&b);
	}
	empty[x]=(empty[x<<1]&empty[x<<1|1]);
}
inline void And(int x,u128 v){
	if(empty[x])return ;
	for(int i=0;i<cnt[x];i++){
		vec[x][i]&=v;
	}
	lazy[x]&=v;
}
inline void pushdown(int i){
	if(~lazy[i])And(i<<1,lazy[i]),And(i<<1|1,lazy[i]);
	lazy[i]=-1;
}
void build(int l=1,int r=n,int i=1){
	while((1<<cnt[i])<=r-l+1)cnt[i]++;
	vec[i]=lim;lim+=cnt[i];lazy[i]=-1;
	if(l==r){
		vec[i][0]=a[l];empty[i]=!a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,i<<1);build(mid+1,r,i<<1|1);
	pushup(i);
}
void Div(int fr,int to,u128 v,int l=1,int r=n,int i=1){
	if(fr>r||to<l||empty[i])return ;
	if(l==r){
		vec[i][0]/=v;empty[i]=(!vec[i][0]);
		return ;
	}
	int mid=(l+r)>>1;pushdown(i);
	Div(fr,to,v,l,mid,i<<1);Div(fr,to,v,mid+1,r,i<<1|1);
	pushup(i);
}
void update(int fr,int to,u128 v,int l=1,int r=n,int i=1){
	if(fr>r||to<l||empty[i])return ;
	if(fr<=l&&to>=r){
		And(i,v);return ;
	}
	int mid=(l+r)>>1;pushdown(i);
	update(fr,to,v,l,mid,i<<1);update(fr,to,v,mid+1,r,i<<1|1);
	pushup(i);
}
u128 query(int fr,int to,int l=1,int r=n,int i=1){
	if(fr>r||to<l||empty[i])return 0;
	if(fr<=l&&to>=r){
		u128 res=0;
		for(int j=0;j<cnt[i];j++)res+=(vec[i][j]<<j);
		return res;
	}
	int mid=(l+r)>>1;pushdown(i);
	return query(fr,to,l,mid,i<<1)+query(fr,to,mid+1,r,i<<1|1);
}
int main(){
	n=read();q=read();
	for(int i=1;i<=n;i++)a[i]=read_u();
	build();
	while(q--){
		int op,l,r;u128 v;
		op=read();l=read();r=read();
		if(op==1){
			v=read_u();
			if(v!=1)Div(l,r,v);
		}
		if(op==2){
			v=read_u();
			update(l,r,v);
		}
		if(op==3)output(query(l,r)),puts("");
	}

	return 0;
}
posted @ 2022-05-25 21:51  一粒夸克  阅读(100)  评论(0编辑  收藏  举报