2024 Noip 做题记录(六)

个人训练赛题解(六)


By DaiRuiChen007



Round #21 - 2024.10.15

A. [ARC112E] Move

Problem Link

题目大意

给定序列 x1xn,初始 xi=i,每次操作可以把其中一个元素放到开头或末尾,求有多少种长度为 m 的操作序列能够使得最终 xi=ai

数据范围:n,m3000

思路分析

显然一个数如果被操作多次,只有最后一次有用。

那么每个元素有三种情况:最后一次在放在开头,最后一次放到末尾,未操作过。

前两种操作对应 a 的前缀后缀,中间剩余的元素必须是升序的。

假设前缀有 x 个元素,后缀有 y 个元素,计数时先给每个操作划分到元素中,非最后一次的操作可以选方向,前缀操作和后缀操作内部有序,总的方案数就是:

2mxy(x+yx){mx+y}

预处理后直接 O(1) 计算。

时间复杂度 O(n2+m2)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=3005,MOD=998244353;
int n,a[MAXN],m;
ll S[MAXN][MAXN],C[MAXN][MAXN],pw[MAXN],ans=0;
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	S[0][0]=1;
	for(int i=1;i<=m;++i) for(int j=1;j<=i;++j) S[i][j]=(S[i-1][j]*j+S[i-1][j-1])%MOD;
	for(int i=0;i<=m;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	for(int i=pw[0]=1;i<=m;++i) pw[i]=pw[i-1]*2%MOD;
	for(int i=0;i<=n;++i) {
		for(int j=i+1;j<=n+1;++j) {
			if(j-i-1>=2&&a[j-2]>a[j-1]) break;
			int l=i,r=n+1-j;
			if(l+r<=m) ans=(ans+S[m][l+r]*pw[m-l-r]%MOD*C[l+r][l])%MOD;
		}
	}
	printf("%lld\n",ans);
	return 0;
}



B. [ARC116F] Pop

Problem Link

题目大意

给定 k 个序列,总长为 n,A 和 B 轮流操作,每次可以删除一个序列的开头或结尾(不能把序列删空)。

A 要最大化剩余元素之和,B 要最小化剩余元素之和,求最后剩余元素之和。

数据范围:n,k2×105

思路分析

先考虑 k=1 的情况。

如果序列 a 总长 m 为偶数,那么先后手可以不断操作使得最终剩余的元素为序列中点。

先手可以选择第一步操作开头,那么结果是 am/2+1,否则结果是 am/2

那么 A 先手结果是 max(am/2,am/2+1),B 先手结果是 min(am/2,am/2+1)

如果 m 为奇数,那么枚举先手的第一步操作,剩余的情况就是 m 为偶数,设 i=m+12

此时 A 先手结果是 max(min(ai,ai1),min(ai,ai+1)),B 先手结果是 min(max(ai,ai1),max(ai,ai+1))

我们发现 m 为偶数的局面对先手有利,m 为奇数的局面对后手有利。

因此所有人会轮流在长度为偶数的序列中取一个元素,直到所有序列长度为奇数。

此时如果先手操作了一步,当前序列长度为偶数,那么后手必然会操作当前序列。

因此所有偶数序列操作之后,接下来的一个人会依次在每个序列当先手。

因此长度为偶数的序列第一次取开头或末尾对答案的贡献是一定的,按差排序即可。

时间复杂度 O(n+klogk)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
long long s=0;
int f(vector<int>&a,int l,int r,int op) {
	if(l==r) return a[l];
	int i=(l+r)>>1;
	if(op) return min(max(a[i],a[i-1]),max(a[i],a[i+1]));
	return max(min(a[i],a[i-1]),min(a[i],a[i+1]));
}
vector <int> a[MAXN];
signed main() {
	int n,op=0;
	scanf("%d",&n);
	for(int t=1,m;t<=n;++t) {
		scanf("%d",&m),a[t].resize(m);
		for(int &i:a[t]) scanf("%d",&i);
		op^=!(m&1);
	}
	vector <int> q;
	for(int i=1;i<=n;++i) {
		int m=a[i].size();
		if(m&1) s+=f(a[i],0,m-1,op);
		else {
			int x=f(a[i],0,m-2,op),y=f(a[i],1,m-1,op);
			s+=min(x,y),q.push_back(abs(x-y));
		}
	}
	sort(q.begin(),q.end(),greater<int>());
	for(int i=0;i<(int)q.size();i+=2) s+=q[i];
	printf("%lld\n",s);
	return 0;
}



*C. [ARC112F] Carry

Problem Link

题目大意

给定 a1an,以及 m 个长度为 n 的序列 c1cm,可以进行如下两种操作任意多次:

  • 选择 j[1,m],令所有 ai 加上 cj,i
  • 选择 i[1,n],将 ai 减去 2i,将 aimodn+1 加一(要求 ai>2i)。

最小化最终的 ai

数据范围:n16,m50

思路分析

如果只能进行第二类操作,那么最优策略肯定是把每个能操作的 ai 都操作,并且这个过程和操作顺序无关。

定义一个序列最终的结果是不断进行二操作最终剩余的元素个数。

我们发现我们实际上可以进行逆操作,即把一个 ai+1 换成 2iai

容易发现进行若干逆操作后并不影响最终的结果,在任何时候进行操作一也不影响最终的结果。

因此我们可以先进行所有一操作,然后全部逆操作到 a1 上,最后进行二操作。

那么我们实际上可以把每个 ci 提前逆操作到 ci,1 上,这样一个局面只和 a1 的值有关。

容易发现一个 ai 会逆操作变成 2i1(i1)!a1,特别地,2nn!a1 会变成一个 a1

因此我们可以求出每个局面对应的 ci,1,那么 a1 就可以变成 a1+kici,1mod(2nn!1)

其中 ki 表示的是 ci 一共被选了多少次,根据裴蜀定理,最终的 a1 只要在模 gcd(ci,1,2nn!1) 意义下和原式 a1 同余即可。

p=gcd(ci,1,2nn!1),d=2nn!1,那么我们只要枚举 0p+a1,p+a1(d/p1)p+a1 作为最终的 a1,并且暴力计算每个 a1 对应的实际答案即可。

但这样的做法在 p 很小的时候复杂度会退化。

对于 p 较小的情况,直接考虑最终的序列,我们放一个 ai 就会让 a1 增加 2i1(i1)!

可以看成从 0 出发在 modp 意义下加上若干个 2(i1)(i1)! 得到 a1,最小化加上的 ai 个数。

直接同余最短路,但要钦定不能一个元素都不放。

O(d) 作为 p 的根号分治界即可。

时间复杂度 O(n2nn!)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MAXP=1.3e6+5,inf=1e18;
int n,m;
ll b[18],f[MAXP];
ll read() {
	ll s=0;
	for(int i=0,x;i<n;++i) scanf("%d",&x),s+=b[i]*x;
	return s;
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=b[0]=1;i<=n;++i) b[i]=b[i-1]*2*i;
	ll a=read(),p=b[n]-1;
	for(int i=1;i<=m;++i) p=__gcd(p,read());
	if(p<b[n]/p) {
		memset(f,0x3f,sizeof(f));
		queue <int> q;
		for(int i=0;i<n;++i) f[b[i]%p]=1,q.push(b[i]%p);
		while(q.size()) {
			int u=q.front(); q.pop();
			for(int i=0;i<n;++i) {
				int v=(u+b[i])%p;
				if(f[v]>f[u]+1) q.push(v),f[v]=f[u]+1;
			}
		}
		printf("%lld\n",f[a%p]);
	} else {
		ll ans=inf;
		for(ll u=a%p;u<b[n];u+=p) if(u) {
			ll x=u,s=0;
			for(int i=1;i<=n;++i) s+=x%(2*i),x/=2*i;
			ans=min(ans,s);
		}
		printf("%lld\n",ans);
	}
	return 0;
}



*D. [ARC114F] Shuffle

Problem Link

题目大意

给定 1n 排列,求一种把排列划分成 k 段的方法,使得段间重排得到的最大字典序排列字典序最小。

数据范围:n2×105

思路分析

很显然段间重排一定是按每个段的段首倒序排列。

我们只要最小化最大的段首,a1k 时最大段首一定是 k,取 1k 分段即可。

对于剩余的情况,重排后字典序不会变小,只需要最大化两个排列的 lcp,很显然 lcp 长度 1,因此所有段首 a1

设 lcp 为 m,那么在 a[1,m] 中要选出一个 LDS b1bx 作为段首,然后在 a[m+1,n] 中选 kx<bx 的元素。

对每个 x 维护 a[1,m] 中长度为 x 的 LDS 结尾最大是多少,再计算 a[m+1,n]<bx 的元素是否超过 kx 个。

对于构造方案,很显然 a[1,m] 中的段首相对顺序不变,可以忽略,我们只需要重排 a1 以及 a[m+1,n] 中的段首。

容易发现最优解只需要最小化 am+1,即剩余段首中的最大值,这只和 am+1an 分出的段首有关,二分检验的时候最大化 x 即可。

时间复杂度 O(nlog2n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN],pos[MAXN],st[MAXN],cnt[MAXN];
bool vis[MAXN];
int chk(int x) {
	int tp=0;
	for(int i=1;i<=x;++i) if(a[i]<=a[1]) {
		int j=lower_bound(st+1,st+tp+1,a[i],greater<int>())-st;
		st[j]=a[i],tp=max(tp,j);
	}
	memset(cnt,0,sizeof(cnt));
	for(int i=x+1;i<=n;++i) ++cnt[a[i]];
	for(int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
	for(int i=tp;i;--i) if(i+cnt[st[i]]>=m) return i;
	return 0;
}
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]),pos[a[i]]=i;
	if(a[1]<=m) {
		for(int i=m;i;--i) {
			printf("%d ",i);
			for(int j=pos[i]+1;j<=n&&a[j]>m;++j) printf("%d ",a[j]);
		}
		return puts(""),0;
	}
	int l=1,r=n,z=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(chk(mid)) z=mid,l=mid+1;
		else r=mid-1;
	}
	vector <int> vi{1};
	for(int i=1,p=m-chk(z);p&&i<a[1];++i) if(pos[i]>z) vi.push_back(pos[i]),--p;
	sort(vi.begin(),vi.end(),[&](int x,int y){ return a[x]>a[y]; });
	for(int i:vi) vis[i]=true;
	for(int i:vi) {
		printf("%d ",a[i]);
		for(int j=i+1;j<=n&&!vis[j];++j) printf("%d ",a[j]);
	}
	puts("");
	return 0;
}




Round #22 - 2024.10.16

*A. [P9337] Cold

Problem Link

题目大意

给定长度为 n 的序列 a1an,以及 n 的排列 b1bn,回答 m 个询问:

给定 l,r,查询:

i=1nj=i+1n[ai=aj]k=ij[lbkr]

数据范围:n,m2×105

思路分析

考虑对 a 中的颜色根号分治。

对于出现次数较小的颜色,枚举每一对同色的 ai,aj,然后对 (minb[i,j],maxb[i,j]) 二维数点,用 O(1) 修改的 Sqrt-Tree 平衡复杂度。

但直接离线所有点的空间复杂度是 O(nn) 的,需要优化。

注意到对于三个同颜色的点 ai,aj,ak(i,k) 的信息实际上可以由 (i,j),(j,k) 合并而来。

因此我们只要记录所有相邻同色点对之间的贡献,并把这些点对离线,对值域扫描线的时候只考虑最小值为 l 的所有点对,这可以直接从这些相邻点对出发拓展左右端点得到。

然后考虑出现次数较多的颜色 c,对单一颜色解决此问题是容易的,我们对值域进行莫队。

对于一组相邻同色对 ai=aj=c,如果当前莫队区间 [l,r] 包含 minb[i,j],maxb[i,j],那么就连边 i,j,我们要数的就是每个连通块大小的平方。

由于我们只会在相邻的点之间连边,因此用回滚莫队配合链表即可维护答案。

对单种颜色求解的复杂度是 O(m+nm) 的,难以接受。

但实际上我们的值域不需要到 O(n),只需要包含所有 minb[i,j],maxb[i,j] 并离散化一下再莫队,这样对于所有颜色的 n 也是 O(n) 级别的。

时间复杂度 O(nm+(n+m)n)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5e5+5,B=3000;
int n,m,a[MAXN],b[MAXN],L[MAXN],R[MAXN];
ll ans[MAXN];
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
	int x=0; char c=gc();
	while(!isdigit(c)) c=gc();
	while(isdigit(c)) x=x*10+(c^48),c=gc();
	return x;
}
void flush() {
	fwrite(obuf,ow,1,stdout),ow=0;
}
void write(int x) {
	if(!x) obuf[ow++]='0';
	else {
		int t=ow;
		for(;x;x/=10) obuf[ow++]=(x%10)^48;
		reverse(obuf+t,obuf+ow);
	}
	if(ow>=olim) flush();
}
void putc(char c) {
	obuf[ow++]=c;
	if(ow>=olim) flush();
}
#undef gc
}
struct Seg {
	int l,r;
	inline friend Seg operator +(const Seg &u,const Seg &v) { return {min(u.l,v.l),max(u.r,v.r)}; }
};
struct STtable {
	Seg f[MAXN][20];
	int bit(int x) { return 1<<x; }
	void init() {
		for(int i=1;i<=n;++i) f[i][0]={b[i],b[i]};
		for(int k=1;k<20;++k) for(int i=1;i+bit(k)-1<=n;++i) {
			f[i][k]=f[i][k-1]+f[i+bit(k-1)][k-1];
		}
	}
	Seg qry(int l,int r) {
		int k=__lg(r-l+1);
		return f[l][k]+f[r-bit(k)+1][k];
	}
}	ST;
struct SqrtTree {
	ll s1[MAXN],s2[MAXN],s3[MAXN];
	void add(int x,ll y) { s1[x]+=y,s2[x>>7]+=y,s3[x>>14]+=y; }
	ll qry(int x) {
		ll s=0;
		for(int i=((x>>7)<<7);i<=x;++i) s+=s1[i];
		for(int i=((x>>14)<<7);i<(x>>7);++i) s+=s2[i];
		for(int i=0;i<(x>>14);++i) s+=s3[i];
		return s;
	}
}	TR;
vector <int> id[MAXN],ql[MAXN],las[MAXN],ord;
vector <Seg> cur[MAXN];
int rk[MAXN];
namespace tsk {
Seg st[MAXN];
int pr[MAXN],sf[MAXN];
array <int,2> ml[MAXN],mr[MAXN];
vector <int> qrys[MAXN];
bool w[MAXN];
int wl[MAXN],wr[MAXN],vl[MAXN],tl[MAXN],tr[MAXN],tid[MAXN];
int stk[MAXN];
void solve(int x) {
	int s=id[x].size();
	for(int i=1;i<s;++i) {
		st[i]=ST.qry(id[x][i-1],id[x][i]);
		w[st[i].l]=true,w[st[i].r]=true;
	}
	int N=0;
	for(int i=1;i<=n;++i) if(w[i]) vl[++N]=i,wl[i]=wr[i]=N;
	for(int i=1;i<=n;++i) if(!wl[i]) wl[i]=wl[i-1];
	for(int i=n;i>=1;--i) if(!wr[i]) wr[i]=wr[i+1];
	for(int i=1;i<s;++i) {
		int l=st[i].l=wl[st[i].l],r=st[i].r=wr[st[i].r];
		ml[l][ml[l][0]>0]=i,mr[r][mr[r][0]>0]=i;
	}
	int Q=0;
	for(int i:ord) if(L[i]<=vl[N]&&vl[1]<=R[i]&&wr[L[i]]<=wl[R[i]]) {
		++Q,tl[Q]=wr[L[i]],tr[Q]=wl[R[i]],tid[Q]=i;
	}
	if(!Q) return ;
	int blk=N/sqrt(Q)+1;
	for(int i=1;i<=Q;++i) qrys[(tl[i]-1)/blk+1].push_back(i);
	for(int b=1;b<=(N-1)/blk+1;++b) if(qrys[b].size()) {
		int o=min(b*blk,N),rp=o+1;
		for(int i=1;i<=s;++i) pr[i]=sf[i]=i;
		ll sum=0;
		auto link=[&](int e) {
			sum+=(sf[e]-pr[e]+1)*(sf[e+1]-pr[e+1]+1);
			sf[pr[e]]=sf[e+1],pr[sf[e+1]]=pr[e];
		};
		for(auto q:qrys[b]) {
			if(tr[q]<=o) {
				int tp=0;
				for(int i=tl[q];i<=tr[q];++i) for(int e:ml[i]) if(e&&st[e].r<=tr[q]) {
					link(e),stk[++tp]=e;
				}
				ans[tid[q]]+=sum,sum=0;
				for(int i=1;i<=tp;++i) sf[pr[stk[i]]]=stk[i],pr[sf[stk[i]+1]]=stk[i]+1;
				continue;
			}
			for(;rp<=tr[q];++rp) for(int e:mr[rp]) if(st[e].l>o) link(e);
			int tp=0; ll rem=sum;
			for(int lp=o;lp>=tl[q];--lp) for(int e:ml[lp]) if(e&&st[e].r<=tr[q]) {
				link(e),stk[++tp]=e;
			}
			ans[tid[q]]+=sum,sum=rem;
			for(int i=1;i<=tp;++i) sf[pr[stk[i]]]=stk[i],pr[sf[stk[i]+1]]=stk[i]+1;
		}
		qrys[b].clear();
	}
	memset(w,0,sizeof(w));
	memset(wl,0,sizeof(wl));
	memset(wr,0,sizeof(wr));
	for(int i=1;i<=N;++i) ml[i]=mr[i]={0,0};
}
}
signed main() {
	ios::sync_with_stdio(false);
	n=IO::read(),m=IO::read();
	for(int i=1;i<=n;++i) a[i]=IO::read(),id[a[i]].push_back(i);
	for(int i=1;i<=n;++i) b[i]=IO::read();
	for(int i=1;i<=m;++i) L[i]=IO::read(),R[i]=IO::read(),ql[L[i]].push_back(i),ord.push_back(i);
	sort(ord.begin(),ord.end(),[&](int x,int y){ return R[x]<R[y]; });
	ST.init();
	for(int i=1;i<=n;++i) if((int)id[i].size()<=B) {
		int s=id[i].size();
		for(int x=0;x<s;++x) rk[id[i][x]]=x;
		for(int x=0;x<s-1;++x) {
			Seg o=ST.qry(id[i][x],id[i][x+1]);
			cur[i].push_back(o);
			las[o.l].push_back(id[i][x]);
		}
	} else tsk::solve(i);
	for(int i=n;i>=1;--i) {
		for(int o:las[i]) {
			int c=a[o],x=rk[o],s=id[c].size();
			vector<Seg> &sg=cur[c];
			for(int l=x,mx=sg[x].r;l>=0;--l) {
				if(l<x&&sg[l].l<=i) break;
				mx=max(mx,sg[l].r);
				for(int r=x+1,vr=mx;r<s;++r) {
					if(sg[r-1].l<i) break;
					vr=max(vr,sg[r-1].r);
					TR.add(vr,1);
				}
			}
		}
		for(int o:ql[i]) ans[o]+=TR.qry(R[o]);
	}
	for(int i=1;i<=m;++i) IO::write(ans[i]),IO::putc('\n');
	IO::flush();
	cerr<<"time = "<<clock()<<"\n";
	return 0;
}



B. [P5313] Include

Problem Link

题目大意

给你一个长为 n 的序列,有 m 次查询操作。

每次查询操作给定参数 l,r,b,需输出最大的 x,使得存在一个 a,满足 0a<b,使得 a,a+b,a+2b,,a+(x1)b 都在区间 [l,r] 内至少出现过一次。

数据范围:n,m,V105

思路分析

从全局询问开始,可以用 bitset 维护每个值的出现次数,然后按 b 分裂成 nbbitset,求出前缀 AND 第一次变为全 0 的时刻。

这部分的复杂度是 O(Vb×bω) 的,用莫队维护区间莫队即可。

但这个做法在 b 特别小(b<ω)的时刻会退化。

此时我们可以暴力枚举每个 modb 的余数,然后要求的可以看做是区间 mex,离线用线段树二分维护即可,可以对同一个 b 把所有余数一次解决。

时间复杂度 O(nm+Vmω+nωlogV)

代码呈现

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int MAXN=1e5+105,LIM=100;
struct Qry {
	int l,r,b,id;
};
int n,a[MAXN],m,ans[MAXN];
namespace b1 {
int cnt[MAXN];
ull f[MAXN],g[MAXN];
void ins(int x) { if(!cnt[x]++) f[x>>6]^=1ull<<(x&63); }
void ers(int x) { if(!--cnt[x]) f[x>>6]^=1ull<<(x&63); }
ull qry(int i) {
	int q=i>>6,r=i&63;
	if(!r) return f[q];
	return f[q]>>r|f[q+1]<<(64-r);
}
void solve(vector <Qry> &qy) {
	if(qy.empty()) return ;
	int B=n/sqrt(qy.size())+1;
	sort(qy.begin(),qy.end(),[&](auto i,auto j){
		if(i.l/B!=j.l/B) return i.l<j.l;
		return (i.l/B)&1?i.r>j.r:i.r<j.r;
	});
	int l=1,r=0;
	for(auto q:qy) {
		while(r<q.r) ins(a[++r]);
		while(l>q.l) ins(a[--l]);
		while(l<q.l) ers(a[l++]);
		while(r>q.r) ers(a[r--]);
		int b=q.b,k=(b-1)>>6;
		for(int i=0;i<=k;++i) g[i]=-1;
		if(b&63) g[k]=(1ull<<(b&63))-1;
		for(int &x=ans[q.id]=0;;++x) {
			bool flg=0;
			for(int i=0;i<=k;++i) g[i]&=qry(x*b+(i<<6)),flg|=g[i];
			if(!flg) break;
		}
	}
}
}
namespace b2 {
int id[MAXN];
struct SegmentTree {
	static const int N=1<<17;
	int tr[N<<1];
	void init() { memset(tr,0,sizeof(tr)); }
	void upd(int x,int v) { for(tr[x+=N]=v,x>>=1;x;x>>=1) tr[x]=min(tr[x<<1],tr[x<<1|1]); }
	int qsuf(int x,int v) {
		for(x+=N-1;x^1;x>>=1) if((~x&1)&&tr[x^1]<v) {
			for(x^=1;x<=N;x=x<<1|(tr[x<<1]>=v));
			return x-N;
		}
		return N+1;
	}
}	T;
void solve(int B,vector <Qry> &qy) {
	if(qy.empty()) return ;
	for(int i=0,x=0;i<B;++i) for(int j=i;j<MAXN;j+=B) id[j]=++x;
	sort(qy.begin(),qy.end(),[&](auto i,auto j){ return i.r<j.r; });
	T.init();
	auto it=qy.begin();
	for(int i=1;i<=n;++i) {
		T.upd(id[a[i]],i);
		for(;it!=qy.end()&&it->r==i;++it) {
			for(int r=0;r<B;++r) {
				ans[it->id]=max(ans[it->id],T.qsuf(id[r],it->l)-id[r]);
			}
		}
	}
}
}
vector <Qry> qys[LIM];
signed main() {
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i];
	cin>>m;
	for(int i=1,l,r,b;i<=m;++i) {
		cin>>l>>r>>b;
		qys[b<LIM?b:0].push_back({l,r,b,i});
	}
	b1::solve(qys[0]);
	for(int i=1;i<LIM;++i) b2::solve(i,qys[i]);
	for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
	return 0;
}



*C. [P9061] Push

Problem Link

题目大意

给定 n 个点 (xi,yi)i=1n,你需要按顺序处理 m 次操作。每次操作给出 o,x,y,X,Y

  • 首先进行修改:
    • o=1 则将满足 xix,yiy 的点的 yi 修改为 y
    • o=2 则将满足 xix,yiy 的点的 xi 修改为 x
  • 然后进行查询,询问满足 xiX,yiY 的点数。

数据范围:n,m106

思路分析

我们发现所有被修改过至少一次的点会构成一条从左上到右下的折线,称为轮廓线。

并且每次操作对轮廓线的影响是有限的,我们会发现每个在轮廓线上的相对顺序是不会改变的,即任何时候轮廓线上的点的 xi 是递增的,yi 是单调递减的,修改和询问直接用平衡树维护。

维护轮廓线还要知道每个点第一次进入轮廓线的操作,即第一个 xix,yiy 的操作,树状数组维护二维偏序计算。

然后就要处理轮廓线外的点,直接计算的话还要处理时间一维变成三维偏序。

特判掉 X,Y 在轮廓线内部的情况,我们发现如果 (X,Y) 在轮廓线外部,那么 xi>X,yi>Y 的点一定不在轮廓线内。

因此这部分的点不需要考虑进入轮廓线的时间这一维,又变成二维偏序,再简单计算所有未被插入的点中分别满足 xi>Xyi>Y 的点。

时间复杂度 O((n+q)logn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
mt19937 rnd(5437578);
namespace IO {
int ow,olim=(1<<23)-100;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<23];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read() {
	int x=0; char c=gc();
	while(!isdigit(c)) c=gc();
	while(isdigit(c)) x=x*10+(c^48),c=gc();
	return x;
}
void flush() {
	fwrite(obuf,ow,1,stdout),ow=0;
}
void write(int x) {
	if(!x) obuf[ow++]='0';
	else {
		int t=ow;
		for(;x;x/=10) obuf[ow++]=(x%10)^48;
		reverse(obuf+t,obuf+ow);
	}
}
void putc(char c) {
	obuf[ow++]=c;
}
#undef gc
}
const int MAXN=1e6+5;
inline void chkmin(int &x,const int &y) { x=x<y?x:y; }
inline void chkmax(int &x,const int &y) { x=x>y?x:y; }
struct Treap {
	struct Node {
		int x,y,tx,ty,pri,ls,rs,siz;
	}	tr[MAXN];
	inline void adt(Node &p,const int &x,const int &y) {
		if(x) p.x=p.tx=x;
		if(y) p.y=p.ty=y;
	}
	inline void psd(Node &p) {
		adt(tr[p.ls],p.tx,p.ty),adt(tr[p.rs],p.tx,p.ty),p.tx=p.ty=0;
	}
	void splix(int p,const int &k,int &u,int &v) {
		if(!p) return u=v=0,void();
		Node &o=tr[p]; psd(o);
		if(o.x<=k) u=p,splix(o.rs,k,o.rs,v);
		else v=p,splix(o.ls,k,u,o.ls);
		o.siz=tr[o.ls].siz+tr[o.rs].siz+1;
	}
	void spliy(int p,const int &k,int &u,int &v) {
		if(!p) return u=v=0,void();
		Node &o=tr[p]; psd(o);
		if(o.y>=k) u=p,spliy(o.rs,k,o.rs,v);
		else v=p,spliy(o.ls,k,u,o.ls);
		o.siz=tr[o.ls].siz+tr[o.rs].siz+1;
	}
	void spliz(int p,const int &k,int &u,int &v) {
		if(!p) return u=v=0,void();
		Node &o=tr[p]; psd(o);
		if(o.x-o.y<=k) u=p,spliz(o.rs,k,o.rs,v);
		else v=p,spliz(o.ls,k,u,o.ls);
		o.siz=tr[o.ls].siz+tr[o.rs].siz+1;
	}
	int merge(int u,int v) {
		if(!u||!v) return u|v;
		Node &p=tr[u],&q=tr[v]; psd(p),psd(q);
		if(p.pri<q.pri) return p.siz+=q.siz,p.rs=merge(p.rs,v),u;
		else return q.siz+=p.siz,q.ls=merge(u,q.ls),v;
	}
	inline int szx(int p,const int &k) {
		int ans=0;
		while(p) {
			psd(tr[p]);
			if(tr[p].x>k) p=tr[p].ls;
			else ans+=tr[tr[p].ls].siz+1,p=tr[p].rs;
		}
		return ans;
	}
	inline int szy(int p,const int &k) {
		int ans=0;
		while(p) {
			psd(tr[p]);
			if(tr[p].y<k) p=tr[p].ls;
			else ans+=tr[tr[p].ls].siz+1,p=tr[p].rs;
		}
		return ans;
	}
}	T;
int n,m,ax[MAXN],ay[MAXN],ux[MAXN],uy[MAXN],qx[MAXN],qy[MAXN],op[MAXN],ans[MAXN];
struct poi {
	int x,y,id;
	inline friend bool operator <(const poi &u,const poi &v) {
		return u.x^v.x?u.x>v.x:u.id>v.id;
	}
};
vector <int> ins[MAXN];
struct Bit1 {
	int tr[MAXN],s;
	void init() { fill(tr+1,tr+n+1,m+1); }
	inline void upd(int x,const int &v) { for(;x;x&=x-1) chkmin(tr[x],v); }
	inline int qry(int x) { for(s=m+1;x<=n;x+=x&-x) chkmin(s,tr[x]); return s; }
}	Tmn;
struct Bit2 {
	int tr[MAXN],s;
	inline void upd(int x,const int &v) { for(;x;x&=x-1) chkmax(tr[x],v); }
	inline int qry(int x) { for(s=0;x<=n;x+=x&-x) chkmax(s,tr[x]); return s; }
}	Tmx;
struct Bit3 {
	int tr[MAXN],s;
	inline void add(int x,const int &v) { for(;x;x&=x-1) tr[x]+=v; }
	inline int qry(int x) { for(s=0;x<=n;x+=x&-x) s+=tr[x]; return s; }
}	To,Tx,Ty;
signed main() {
	n=IO::read(),m=IO::read();
	vector <poi> Q;
	for(int i=1;i<=n;++i) ax[i]=IO::read(),ay[i]=IO::read(),Q.push_back({ax[i],ay[i],i});
	for(int i=1;i<=m;++i) {
		op[i]=IO::read(),ux[i]=IO::read(),uy[i]=IO::read();
		qx[i]=IO::read(),qy[i]=IO::read();
		Q.push_back({ux[i],uy[i],i+n});
	}
	sort(Q.begin(),Q.end());
	Tmn.init();
	for(auto o:Q) {
		if(o.id>n) Tmn.upd(o.y,o.id-n);
		else ins[Tmn.qry(o.y)].push_back(o.id);
	}
	Q.clear();
	for(int i=1;i<=n;++i) Q.push_back({ax[i],ay[i],i});
	for(int i=1;i<=m;++i) Q.push_back({qx[i],qy[i],i+n});
	sort(Q.begin(),Q.end());
	for(auto o:Q) {
		if(o.id>n) ans[o.id-n]=To.qry(o.y+1);
		else To.add(o.y,1);
	}
	for(int i=1;i<=n;++i) Tx.add(ax[i],1),Ty.add(ay[i],1);
	int rt=0;
	for(int i=1,ot=n;i<=m;++i) {
		int u,o1,o2,o3;
		T.splix(rt,ux[i],o1,o2);
		T.spliy(o1,uy[i]+1,o3,u);
		if(op[i]==2) T.adt(T.tr[u],ux[i],0);
		else T.adt(T.tr[u],0,uy[i]);
		rt=T.merge(T.merge(o3,u),o2);
		for(int j:ins[i]) {
			Tx.add(ax[j],-1),Ty.add(ay[j],-1),--ot;
			op[i]==2?ax[j]=ux[i]:ay[j]=uy[i];
			T.spliz(rt,ax[j]-ay[j],o1,o2);
			T.tr[j]={ax[j],ay[j],0,0,(int)rnd(),0,0,1};
			rt=T.merge(T.merge(o1,j),o2);
		}
		Tmx.upd(ux[i],uy[i]);
		if(Tmx.qry(qx[i]+1)>qy[i]) { IO::putc('0'),IO::putc('\n'); continue; }
		ans[i]+=ot+T.szx(rt,qx[i])-T.szy(rt,qy[i]+1);
		ans[i]-=Tx.qry(qx[i]+1)+Ty.qry(qy[i]+1);
		IO::write(ans[i]),IO::putc('\n');
	}
	IO::flush();
	return 0;
}



*D. [P7447] Minus

Problem Link

题目大意

给定一个长为 n 的序列 a,需要实现 m 次操作:

  • 将区间 [l,r] 中所有 >x 的元素减去 x

  • 询问区间 [l,r] 的和,最小值,最大值。

数据范围:n,m5×105,ai109

思路分析

考虑值域分块,对于每个 k,将元素在 [2k,2k+1) 范围内的元素分入第 k 块。

考虑一次操作对每个块的影响,设 x 在值域块 q,那么 k>q 的块的元素全部都要减去 x,我们用线段树维护这个过程,直接打区间减法标记。

但这种情况可能把一个元素减到 <2k,这样会产生块间的“跌落”,但由于块数只有 O(logV) 个,因此“跌落”次数也只有 O(nlogV) 次,用线段树维护产生“跌落”的点即可。

然后考虑第 q 块的元素,每个元素如果能操作也一定会产生块间的“跌落”,用线段树跳过 <x 的节点即可。

时间复杂度 O((n+m)lognlogV)

但此时空间复杂度 O(nlogV),难以接受。

首先我们可以改变值域分块的底层块长,按 [bk,bk+1) 分块后,空间复杂度变为 O(nlogbV),跌落次数依然是 O(nlogbV) 的,但是对于和 x 同块的元素,导致块间跌落需要 O(b) 次操作,因此复杂度变为 O(nblogbVlogn)

但此时的空间复杂度依然难以接受,可以对线段树采用底层分块优化,即将线段树底层 S=O(logbV) 个元素分为一块,此时空间为近似 O(n) 的,可以接受。

时间复杂度 O((nb+m)logbVlogn),取 b=8,S=32 较优。

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline void chkmin(int &x,const int &y) { x=x<y?x:y; }
inline void chkmax(int &x,const int &y) { x=x>y?x:y; }
const int MAXN=5e5+5,inf=2e9,MOD=(1<<20)-1;
const int MAXS=32775,B=32;
const int H=10,pw[]={1,8,64,512,4096,32768,262144,2097152,16777216,134217728};
namespace IO {
int ow;
const int olim=(1<<21)-30;
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<21];
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
	int x=0; char c=gc();
	while(!isdigit(c)) c=gc();
	while(isdigit(c)) x=x*10+(c^48),c=gc();
	return x;
}
inline void flush() {
	fwrite(obuf,ow,1,stdout),ow=0;
}
inline void write(ll x) {
	if(!x) obuf[ow++]='0';
	else {
		int t=ow;
		for(;x;x/=10) obuf[ow++]=(x%10)^48;
		reverse(obuf+t,obuf+ow);
	}
	if(ow>=olim) flush();
}
inline void putc(const char &c) {
	obuf[ow++]=c;
	if(ow>=olim) flush();
}
#undef gc
}
inline int vblk(int x) { return upper_bound(pw,pw+H,x)-pw-1; }
int n,m,K,a[MAXN],b[MAXN],lp[MAXS],rp[MAXS],bel[MAXN];
struct Node {
	int id;
	array<int,H> mn,mx,tg,cnt;
	array<ll,H> sum;
}	tr[MAXS];
inline void blk_psu(const int &o,Node &p,const int &x) {
	p.mn[x]=inf,p.mx[x]=p.cnt[x]=p.sum[x]=0;
	for(int i=lp[o];i<=rp[o];++i) if(b[i]==x) {
		chkmin(p.mn[x],a[i]);
		chkmax(p.mx[x],a[i]);
		p.sum[x]+=a[i],++p.cnt[x];
	}
}
inline void blk_psu(const int &o,Node &p) {
	p.mn.fill(inf),p.mx.fill(0),p.cnt.fill(0),p.sum.fill(0);
	for(int i=lp[o];i<=rp[o];++i) {
		chkmin(p.mn[b[i]],a[i]);
		chkmax(p.mx[b[i]],a[i]);
		p.sum[b[i]]+=a[i],++p.cnt[b[i]];
	}
}
inline void psu(Node &p,const Node &ls,const Node &rs,const int &x) {
	p.mn[x]=min(ls.mn[x],rs.mn[x]);
	p.mx[x]=max(ls.mx[x],rs.mx[x]);
	p.sum[x]=ls.sum[x]+rs.sum[x];
	p.cnt[x]=ls.cnt[x]+rs.cnt[x];
}
inline void psu(const int &p,const int &x) { psu(tr[p],tr[p<<1],tr[p<<1|1],x); }
inline void psu(const int &p) { for(int x=0;x<H;++x) psu(p,x); }
inline void blk_psd(const int &o,Node &p,const int &x) {
	for(int i=lp[o];i<=rp[o];++i) if(b[i]==x) a[i]-=p.tg[x];
	p.tg[x]=0;
}
inline void blk_psd(const int &o,Node &p) {
	for(int i=lp[o];i<=rp[o];++i) a[i]-=p.tg[b[i]];
	p.tg.fill(0);
}
inline void adt(Node &p,const int &x,const int &k) {
	if(!p.cnt[x]) return ;
	p.tg[x]+=k,p.mn[x]-=k,p.mx[x]-=k,p.sum[x]-=1ll*p.cnt[x]*k;
}
inline void psd(int p,const int &x) {
	if(tr[p].tg[x]) adt(tr[p<<1],x,tr[p].tg[x]),adt(tr[p<<1|1],x,tr[p].tg[x]),tr[p].tg[x]=0;
}
inline void psd(const int &p) { for(int x=0;x<H;++x) psd(p,x); }
void init(int l=1,int r=K,int p=1) {
	if(l==r) return tr[p].id=l,blk_psu(l,tr[p]);
	int mid=(l+r)>>1;
	init(l,mid,p<<1),init(mid+1,r,p<<1|1);
	psu(p);
}
int qmn,qmx,ul,ur,cl,cr; ll qsum;
inline void blk_qry(const int &o) {
	for(int i=max(lp[o],ul);i<=min(rp[o],ur);++i) qsum+=a[i],chkmin(qmn,a[i]),chkmax(qmx,a[i]);
}
void qry(int l=1,int r=K,int p=1) {
	if(ul<=lp[l]&&rp[r]<=ur) {
		for(int x=0;x<H;++x) qsum+=tr[p].sum[x],chkmin(qmn,tr[p].mn[x]),chkmax(qmx,tr[p].mx[x]);
		return ;
	}
	if(l==r) return blk_psd(l,tr[p]),blk_qry(l);
	psd(p);
	int mid=(l+r)>>1;
	if(cl<=mid) qry(l,mid,p<<1);
	if(mid<cr) qry(mid+1,r,p<<1|1);
}
inline void leaf_psd(const int &x,const int &p) {
	for(int d=__lg(p);d;--d) psd(p>>d,x);
	blk_psd(tr[p].id,tr[p],x);
}
inline void leaf_psu(const int &x,const int &p) {
	blk_psu(tr[p].id,tr[p],x);
	for(int q=p>>1;q;q>>=1) psu(q,x);
}
inline void blk_upd(const int &x,const int &k,const int &o,const int &p) {
	for(int i=max(lp[o],ul);i<=min(rp[o],ur);++i) if(b[i]==x&&a[i]>k) {
		a[i]-=k;
		if(a[i]<pw[x]) {
			int y=vblk(a[i]);
			leaf_psd(y,p),b[i]=y,leaf_psu(y,p);
		}
	}
}
void upd(const int &x,const int &k,int l=1,int r=K,int p=1) {
	if(tr[p].mx[x]<=k) return ;
	if(ul<=lp[l]&&rp[r]<=ur&&tr[p].mn[x]-k>=pw[x]) return adt(tr[p],x,k);
	if(l==r) return blk_psd(l,tr[p],x),blk_upd(x,k,l,p),blk_psu(l,tr[p],x);
	int mid=(l+r)>>1; psd(p,x);
	if(cl<=mid) upd(x,k,l,mid,p<<1);
	if(mid<cr) upd(x,k,mid+1,r,p<<1|1);
	psu(p,x);
}
signed main() {
	n=IO::read(),m=IO::read();
	for(int i=1;i<=n;++i) a[i]=IO::read(),b[i]=vblk(a[i]);
	K=(n-1)/B+1;
	for(int i=1;i<=K;++i) {
		lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
		fill(bel+lp[i],bel+rp[i]+1,i);
	}
	init();
	int lst=0;
	for(int op,k,i=1;i<=m;++i) {
		op=IO::read(),ul=IO::read()^lst,ur=IO::read()^lst,cl=bel[ul],cr=bel[ur];
		if(op==1) {
			k=IO::read()^lst;
			for(int x=vblk(k);x<H;++x) upd(x,k);
		} else {
			qmn=inf,qmx=qsum=0,qry();
			IO::write(qsum),IO::putc(' ');
			IO::write(qmn),IO::putc(' ');
			IO::write(qmx),IO::putc('\n');
			lst=qsum&MOD;
		}
	}
	IO::flush();
	return 0;
}



E. [P8528] Virtual

Problem Link

题目大意

给定 n 个点的树,m 次询问 [l,r] 中有多少个子区间构成虚树。

数据范围:n,m2×105

思路分析

对于三个点 LCA(u,v)=w

  • 如果 u<w<v 没有影响。
  • 如果 u<v<w 那么会把 lu,r[v,w) 的区间删除。
  • 如果 w<u<v 那么会删除 w<l,rv 的区间删除。

很显然对于 LCA 相同的若干点对,如果 [u,v] 构成包含关系,那么只保留被包含的一个一定不劣。

那么根据第一类支配点对的结论,dsu on tree 合并时,只插入每个点的前驱后继线段,可以得到 O(nlogn) 对点。

然后就是一个形如数矩形面积并的问题,线段树维护区间删除次数 ±1,删除次数最小值权值 +1,求区间权值和。

时间复杂度 O(nlog2n+mlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5;
int n,m,a[MAXN];
ll ans[MAXN];
vector <int> G[MAXN];
set <int> F[MAXN];
vector <array<int,3>> op[MAXN];
vector <array<int,2>> qy[MAXN];
void ins(int u,int v,int r) {
	if(r<u) op[v].push_back({r+1,u,1});
	if(v<r) op[v].push_back({1,u,1}),op[r].push_back({1,u,-1});
}
void dfs(int u) {
	for(int v:G[u]) {
		dfs(v);
		if(F[v].size()>F[u].size()) swap(F[u],F[v]);
		for(int x:F[v]) {
			auto it=F[u].upper_bound(x);
			if(it!=F[u].end()) ins(x,*it,a[u]);
			if(it!=F[u].begin()) ins(*--it,x,a[u]);
		}
		for(int x:F[v]) F[u].insert(x);
	}
	F[u].insert(a[u]);
}
struct SegmentTree {
	ll sum[MAXN<<2];
	int btg[MAXN<<2],stg[MAXN<<2],mn[MAXN<<2],mc[MAXN<<2];
	void psu(int p) {
		mn[p]=min(mn[p<<1],mn[p<<1|1]),sum[p]=sum[p<<1]+sum[p<<1|1];
		mc[p]=(mn[p<<1]==mn[p]?mc[p<<1]:0)+(mn[p<<1|1]==mn[p]?mc[p<<1|1]:0);
	}
	void adtb(int p,int k) { btg[p]+=k,mn[p]+=k; }
	void adts(int p,int k) { stg[p]+=k,sum[p]+=1ll*k*mc[p]; }
	void psd(int p) {
		if(btg[p]) adtb(p<<1,btg[p]),adtb(p<<1|1,btg[p]),btg[p]=0;
		if(stg[p]) {
			if(mn[p<<1]==mn[p]) adts(p<<1,stg[p]);
			if(mn[p<<1|1]==mn[p]) adts(p<<1|1,stg[p]);
			stg[p]=0;
		}
	}
	void init(int l=1,int r=n,int p=1) {
		mc[p]=r-l+1;
		if(l==r) return ;
		int mid=(l+r)>>1;
		init(l,mid,p<<1),init(mid+1,r,p<<1|1);
		psu(p);
	}
	void add(int ul,int ur,int z,int l=1,int r=n,int p=1) {
		if(ul<=l&&r<=ur) {
			if(z) adtb(p,z);
			else if(!mn[p]) adts(p,1);
			return ;
		}
		int mid=(l+r)>>1; psd(p);
		if(ul<=mid) add(ul,ur,z,l,mid,p<<1);
		if(mid<ur) add(ul,ur,z,mid+1,r,p<<1|1);
		psu(p);
	}
	ll qry(int ul,int ur,int l=1,int r=n,int p=1) {
		if(ul<=l&&r<=ur) return sum[p];
		int mid=(l+r)>>1; ll s=0; psd(p);
		if(ul<=mid) s+=qry(ul,ur,l,mid,p<<1);
		if(mid<ur) s+=qry(ul,ur,mid+1,r,p<<1|1);
		return s;
	}
}	T;
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=2,fz;i<=n;++i) cin>>fz,G[fz].push_back(i);
	dfs(1);
	for(int i=1,l,r;i<=m;++i) cin>>l>>r,qy[r].push_back({l,i});
	T.init();
	for(int i=1;i<=n;++i) {
		for(auto o:op[i]) T.add(o[0],o[1],o[2]);
		T.add(1,i,0);
		for(auto o:qy[i]) ans[o[1]]=T.qry(o[0],i);
	}
	for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
	return 0;
}



*F. [P7710] Distance

Problem Link

题目大意

给你一棵 n 个点的树,需要支持两种操作 m 次:

  • a 子树中所有与 a 的距离模 x 等于 y 的节点权值加 z

  • 查询 a 节点的权值。

数据范围:n,m3×105

思路分析

首先用 dfs 序转成区间加单点查询。

先对序列分块,散块暴力修改,对于整块打标记考虑对 x 根号分治,对于 x 较小的情况,对每个块 O(1) 维护每对 modx=y 上的增量。

如果 x 较大,只有 O(n/x) 个可能的深度会修改答案,我们枚举这些深度,然后对每个块打标记,这样复杂度难以接受,但我们可以差分维护每个块上的标记,查询时再前缀和就行。

时空复杂度 O(nn),但空间过大,需要调整块长。

时间复杂度 O(nn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+5,B=1505,LIM=200;
int n,m,d[MAXN],fa[MAXN],dfl[MAXN],dfr[MAXN],dcnt;
vector <int> G[MAXN];
int a[MAXN],bel[MAXN],lp[MAXN],rp[MAXN];
int t1[205][205][205],t2[205][MAXN];
void dfs(int u,int fz) {
	dfl[u]=++dcnt,d[dfl[u]]=d[dfl[fz]]+1;
	for(int v:G[u]) dfs(v,u);
	dfr[u]=dcnt;
}
int qry(int x) {
	int s=a[x];
	for(int i=1;i<=LIM;++i) s+=t1[bel[x]][i][d[x]%i];
	for(int i=1;i<=bel[x];++i) s+=t2[i][d[x]];
	return s;
}
void upd(int l,int r,int x,int y,int z) {
	if(bel[l]==bel[r]) {
		for(int i=l;i<=r;++i) if(d[i]%x==y) a[i]+=z;
		return ;
	}
	for(int i=l;i<=rp[bel[l]];++i) if(d[i]%x==y) a[i]+=z;
	for(int i=lp[bel[r]];i<=r;++i) if(d[i]%x==y) a[i]+=z;
	if(bel[r]-bel[l]>1) {
		if(x<=LIM) {
			for(int i=bel[l]+1;i<=bel[r]-1;++i) t1[i][x][y]+=z;
		} else {
			for(;y<=n;y+=x) t2[bel[l]+1][y]+=z,t2[bel[r]][y]-=z;
		}
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=2,fz;i<=n;++i) cin>>fz,G[fz].push_back(i);
	dfs(1,0);
	for(int i=1;i<=(n-1)/B+1;++i) {
		lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
		fill(bel+lp[i],bel+rp[i]+1,i);
	}
	for(int op,u,x,y,z;m--;) {
		cin>>op>>u;
		if(op==1) cin>>x>>y>>z,upd(dfl[u],dfr[u],x,(y+d[dfl[u]])%x,z);
		else cout<<qry(dfl[u])<<"\n";
	}
	return 0;
}



*G. [P6579] Pair

Problem Link

题目大意

给定 1n 排列,m 次询问 [l,r] 区间,值域在 [x,y] 中的顺序对个数。

数据范围:n105,m2×105

思路分析

先对序列分块,拆成包含散块的贡献,以及整块内部的贡献。

先考虑一个散块对一个区间的贡献,可以差分成前缀 a1ai 有多少个值域在 [x,ap] 中的元素,扫描线配合 Sqrt-Tree 维护。

然后要计算单个整块内和若干整块间 [x,y] 的贡献。

先考虑如何分别计算 n 个整块内 [x,y] 的贡献,发现这相当于值域在一个区间中,下标的顺序对数。

逐块处理,由于每个块只有 n 个元素,因此我们可以把值域离散化,然后 O(n) 处理出区间下标顺序对数量 即可 O(1) 回答。

然后考虑两个整块间的顺序对贡献,对值域差分,分别计算值域 [1,x1],[1,y] 中的顺序对个数,再减去元素落在 [1,x1],[x,y] 中的顺序对个数。

第一部分可以对值域扫描线,每次加入一个元素,动态计算每对块间的逆序对个数。

维护 si,j 表示左边元素在 i 的块中,右边元素在第 j 块的顺序对个数,修改时枚举 i,查询时枚举 j,都可以做到 O(n)

然后要计算第一个元素 <x,第二个元素 [x,y] 的数对个数,枚举 [x,y] 的元素属于哪个块,然后减去前面的块中 <x 的元素个数,对块扫描线用 SqrtTree 维护。

时间复杂度 O((n+m)n)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MAXQ=2e5+5,B=320,S=325;
struct SqrtTree {
	int s1[MAXN],s2[MAXN],s3[MAXN];
	inline void init() {
		memset(s1,0,sizeof(s1)),memset(s2,0,sizeof(s2)),memset(s3,0,sizeof(s3));
	}
	inline void add(const int &x) {
		for(int i=((x>>6)<<6);i<=x;++i) ++s1[i];
		for(int i=((x>>12)<<6);i<(x>>6);++i) ++s2[i];
		for(int i=0;i<(x>>12);++i) ++s3[i];
	}
	inline int q(const int &x) { return s1[x]+s2[x>>6]+s3[x>>12]; }
	inline int qry(const int &l,const int &r) { return q(l)-q(r+1); }
}	TR;
struct info { int l,r,x,y,i; } q[MAXQ];
int n,m,a[MAXN],p[MAXN],bl[MAXN],lp[S],rp[S];
ll ans[MAXQ];
namespace SL { //left in small
vector <info> f[MAXN];
vector <int> g[S];
void main() {
	TR.init();
	for(int i=1;i<=n;++i) {
		TR.add(a[i]);
		for(int x:g[bl[i]]) if(q[x].l<=i&&i<=q[x].r&&q[x].x<=a[i]&&a[i]<=q[x].y) {
			ans[x]-=TR.qry(a[i],q[x].y);
		}
		for(auto o:f[i]) for(int x=o.l;x<=o.r;++x) if(o.x<=a[x]&&a[x]<=o.y) {
			ans[o.i]+=TR.qry(a[x],o.y);
		}
	}
}
}
namespace SR { //right in small
vector <info> f[MAXN];
vector <int> g[S];
void main() {
	TR.init();
	for(int i=n;i>=1;--i) {
		TR.add(a[i]);
		for(int x:g[bl[i]]) if(q[x].l<=i&&i<=q[x].r&&q[x].x<=a[i]&&a[i]<=q[x].y) {
			ans[x]-=TR.qry(q[x].x,a[i]);
		}
		for(auto o:f[i]) for(int x=o.l;x<=o.r;++x) if(o.x<=a[x]&&a[x]<=o.y) {
			ans[o.i]+=TR.qry(o.x,a[x]);
		}
	}
}
}
namespace B2 { //cross block
vector <info> f[MAXN];
int c[S];
ll w[S][S];
void main() {
	for(int i=1;i<=n;++i) {
		int b=bl[p[i]];
		++c[b];
		for(int j=b-1,s=0;j;--j) s+=c[j],w[j][b]+=s;
		for(auto x:f[i]) {
			ll s=0;
			for(int j=x.r;j>x.l;--j) s+=w[x.l][j];
			ans[x.i]+=x.x*s;
		}
	}
}
}
namespace B1 { //in same block
vector <int> g[S];
int vl[B+5],rk[MAXN],pr[MAXN],sf[MAXN],c[MAXN],lst[MAXQ];
ll w[B+5][B+5];
void solve(int b) {
	const int L=lp[b],R=rp[b];
	int k=0;
	for(int i=L;i<=R;++i) vl[++k]=a[i];
	sort(vl+1,vl+k+1);
	memset(c,0,sizeof(c));
	memset(pr,0,sizeof(pr));
	memset(sf,0,sizeof(sf));
	for(int i=L;i<=R;++i) {
		rk[i]=lower_bound(vl+1,vl+k+1,a[i])-vl;
		pr[a[i]]=sf[a[i]]=rk[i],++c[a[i]];
	}
	for(int i=1;i<=n;++i) c[i]+=c[i-1];
	for(int i=1;i<=n;++i) if(!pr[i]) pr[i]=pr[i-1];
	for(int i=n;i>=1;--i) if(!sf[i]) sf[i]=sf[i+1];
	memset(w,0,sizeof(w));
	for(int i=L;i<=R;++i) for(int j=i+1;j<=R;++j) if(a[i]<a[j]) ++w[rk[i]][rk[j]];
	for(int i=1;i<=k;++i) for(int j=i+1;j<=k;++j) w[i][j]+=w[i][j-1];
	for(int j=1;j<=k;++j) for(int i=j-1;i>=1;--i) w[i][j]+=w[i+1][j];
	for(int i=1;i<=m;++i) {
		int x=q[i].x,y=q[i].y,lx=bl[q[i].l],rx=bl[q[i].r];
		if(lx<b&&b<rx) {
			ans[i]+=w[sf[x]][pr[y]];
			if(lx+1<b) { //erase [1,a) + [a,b] between block
				ans[i]-=(c[y]-c[x-1])*(TR.qry(1,x-1)-lst[i]);
			}
		}
	}
	for(int i=L;i<=R;++i) TR.add(a[i]);
	for(int x:g[b]) lst[x]=TR.qry(1,q[x].x-1);
}
void main() {
	TR.init();
	for(int i=1;i<=bl[n];++i) solve(i);
}
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i],p[a[i]]=i;
	for(int i=1;i<=(n-1)/B+1;++i) {
		lp[i]=(i-1)*B+1,rp[i]=min(i*B,n);
		fill(bl+lp[i],bl+rp[i]+1,i);
	}
	for(int i=1,l,r,x,y;i<=m;++i) {
		cin>>l>>r>>x>>y,q[i]={l,r,x,y,i};
		if(bl[l]==bl[r]) {
			SL::f[r].push_back(q[i]);
			SL::g[bl[l]].push_back(i);
			continue;
		}
		int lx=bl[l],rx=bl[r];
		SL::f[r].push_back({l,rp[lx],x,y,i});
		SL::g[lx].push_back(i);
		SR::f[rp[lx]+1].push_back({lp[rx],r,x,y,i});
		SR::g[rx].push_back(i);
		if(rx-lx>1) B1::g[lx].push_back(i);
		if(rx-lx>2) {
			B2::f[x-1].push_back({lx+1,rx-1,-1,0,i});
			B2::f[y].push_back({lx+1,rx-1,1,0,i});
		}
	}
	SL::main(),SR::main(),B2::main(),B1::main();
	for(int i=1;i<=m;++i) cout<<ans[i]<<"\n";
	return 0;
}




Round #23 - 2024.10.21

A. [AGC003E] Repeat

Problem Link

题目大意

给定序列 a,初始 a=[1,2,,n]q 次操作,每次操作给定 s,将 a 变成 a[1,s],求最终每种元素出现次数。

数据范围:n,q105,s1018

思路分析

对于相邻的两次操作 x,y,如果 x>y 那么操作 x 没有意义,最终有效的操作是递增的单调栈上元素。

设最终的操作为 s1sm,查询 a[1,x] 可以找到第一个 si<x,分成 x/sia[1,si]1a[1,xmodsi]

停止递归时 x<ai,此时 1x 各出现一次,维护这个 a[1,x] 在答案中出现了几次,然后只需对 1x 出现次数前缀加。

暴力模拟,用 map 把本质相同操作合并,此时会访问 s1sn,以及若干 xmodsi,容易发现每次 xxmodsi 至少减半,因此这些点只会访问 O(nlogV) 个。

时间复杂度 O(nlogVlogn)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5;
int n=1,N,q;
ll a[MAXN],s[MAXN];
signed main() {
	scanf("%lld%d",&a[1],&q),N=a[1];
	for(int i=1;i<=q;++i) {
		ll x; scanf("%lld",&x);
		while(n&&a[n]>=x) --n;
		a[++n]=x;
	}
	map <ll,ll> o;
	o[a[n]]=1;
	while(o.size()) {
		ll x=o.rbegin()->first,k=o.rbegin()->second;
		o.erase(--o.end());
		if(x<=a[1]) s[x]+=k;
		else {
			int i=lower_bound(a+1,a+n+1,x)-a-1;
			o[a[i]]+=(x/a[i])*k;
			if(x%a[i]) o[x%a[i]]+=k;
		}
	}
	for(int i=N;i>=1;--i) s[i]+=s[i+1];
	for(int i=1;i<=N;++i) printf("%lld\n",s[i]);
	return 0;
}



B. [AGC004E] Robot

Problem Link

题目大意

n×m 矩阵上有若干机器人和一个出口,每次操作可以让所有机器人同时朝一个方向动一步。

机器人越过边界或到出口时消失,求最多能让多少个机器人到出口。

数据范围:n,m100

思路分析

看成出口和边界在无穷大网格上移动,边界范围外的点全部删除。

容易发现出口运动的范围一定是一个矩形,设这个矩形四个边界距离出口长度为 u,d,l,r

那么前 d 行,后 u 行,左 r 列,右 l 列机器人全部被删除,其他机器人保留。

dp 维护 fl,r,u,d,每次拓展 1 长度,前缀和优化新区域内未被删除的机器人个数。

时间复杂度 O(n2m2)

代码呈现

#include<bits/stdc++.h>
#define si short
using namespace std;
inline void chkmax(si &x,const si &y) { x=x>y?x:y; }
char s[105][105];
int n,m,x,y;
si R[105][105],C[105][105],f[105][105][105][105],ans;
signed main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
		R[i][j]=R[i][j-1]+(s[i][j]=='o');
		C[i][j]=C[i-1][j]+(s[i][j]=='o');
		if(s[i][j]=='E') x=i,y=j;
	}
	for(int u=0;u<x;++u) for(int d=0;d<=n-x;++d) for(int l=0;l<y;++l) for(int r=0;r<=m-y;++r) {
		si z=f[u][d][l][r]; ans=max(ans,z);
		if(u+d<x-1) chkmax(f[u+1][d][l][r],z+R[x-u-1][min(y+r,m-l)]-R[x-u-1][max(y-l-1,r)]);
		if(u+d<n-x) chkmax(f[u][d+1][l][r],z+R[x+d+1][min(y+r,m-l)]-R[x+d+1][max(y-l-1,r)]);
		if(l+r<y-1) chkmax(f[u][d][l+1][r],z+C[min(x+d,n-u)][y-l-1]-C[max(x-u-1,d)][y-l-1]);
		if(l+r<m-y) chkmax(f[u][d][l][r+1],z+C[min(x+d,n-u)][y+r+1]-C[max(x-u-1,d)][y+r+1]);
	}
	printf("%d",(int)ans);
	return 0;
}



C. [AGC003F] Fraction

Problem Link

题目大意

给定 n×m 黑白网格 A,从一个黑格开始进行 k 次迭代,每次迭代把所有黑格换成 A,白格换成 n×m 全白矩阵。

保证初始 A 中黑格连通,求最终黑格连通块数量。

数据范围:n,m1000,k1018

思路分析

考虑从任意矩阵开始进行一次递归:

  • 如果 A 的第一列和最后一列连通,第一行和最后一行连通,那么连通块数量不变,最终答案为 1

  • 如果行列都不连通,那么连通块数变为黑格数量,最终答案为 ck1,其中 cA 黑格数量。

  • 如果只有行连通(只有列连通同理),那么连通块数变为每行极长连续段数量,即黑格数量减去左右都是黑格的点对数量。

    左右都是黑格的点对数量容易维护:一次迭代会把每个黑格加上 A 内部点对数量,且原网格的一对点对会变成 A 中第一列和最后一列的点对数量,容易用矩阵表示转移。

时间复杂度 O(nm+δ3logk),其中 δ=2

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<array<ll,2>,2> Mat;
const int MAXN=1005,MOD=1e9+7;
Mat operator *(const Mat &u,const Mat &v) {
	Mat w{0,0,0,0};
	for(int i:{0,1}) for(int j:{0,1}) for(int k:{0,1}) w[i][j]=(w[i][j]+u[i][k]*v[k][j])%MOD;
	return w;
}
Mat A,B;
ll ksm(ll a,ll b) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,m; ll K,z=0;
char s[MAXN][MAXN];
signed main() {
	scanf("%d%d%lld",&n,&m,&K);
	for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
	int t1=0,t2=0;
	for(int i=1;i<=m;++i) t1+=(s[1][i]=='#'&&s[n][i]=='#');
	for(int i=1;i<=n;++i) t2+=(s[i][1]=='#'&&s[i][m]=='#');
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) z+=s[i][j]=='#';
	if(t1&&t2) puts("1");
	else if(t1) {
		int c=0;
		for(int i=1;i<n;++i) for(int j=1;j<=m;++j) c+=(s[i][j]=='#'&&s[i+1][j]=='#');
		A={z,c,0,t1},B={1,0,0,0};
		for(--K;K;K>>=1,A=A*A) if(K&1) B=B*A;
		printf("%lld\n",(B[0][0]+MOD-B[0][1])%MOD);
	}
	else if(t2) {
		int c=0;
		for(int i=1;i<=n;++i) for(int j=1;j<m;++j) c+=(s[i][j]=='#'&&s[i][j+1]=='#');
		A={z,c,0,t2},B={1,0,0,0};
		for(--K;K;K>>=1,A=A*A) if(K&1) B=B*A;
		printf("%lld\n",(B[0][0]+MOD-B[0][1])%MOD);
	}
	else printf("%lld\n",ksm(z,K-1));
	return 0;
}



D. [AGC004F] Reverse

Problem Link

题目大意

给定 n 个点的树或基环树,每个点初始是白色,每次操作可以把相邻的两个同色点反转颜色,求把所有点变成黑色的最小操作次数。

数据范围:n105

思路分析

先考虑树的情况,对树黑白染色,把黑色位置上的点颜色取反,此时相当于交换相邻的两个点颜色,最终交换所有点颜色。

首先要求每个点颜色相等,然后可以把黑点看成棋子,白点看成空位,可以移动棋子到空位,目标是把所有棋子放到空位上。

分析每条边的贡献,容易发现最小值就是每个子树内棋子与空位个数差的绝对值之和。

构造时先尽可能进行内部匹配,剩余的棋子 / 空位就向外界移动。

然后考虑基环树,如果是偶环,依然不能改变黑白棋子个数,假设额外边是返祖边,且有 x 个棋子从这里向上,那么相当于对链上的每个节点权值 x,最小化权值绝对值和,直接取中位数。

如果是奇环,只能给某种颜色的棋子个数 ±2,操作次数是唯一确定的。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h> 
using namespace std;
const int MAXN=1e5+5;
vector <int> G[MAXN];
int n,m,dsu[MAXN],f[MAXN],d[MAXN],fa[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
void dfs(int u,int fz) {
	d[u]=d[fz]+1,f[u]=(d[u]&1?-1:1),fa[u]=fz;
	for(int v:G[u]) if(v^fz) dfs(v,u),f[u]+=f[v];
}
signed main() {
	scanf("%d%d",&n,&m);
	int rt=1,o=0;
	iota(dsu+1,dsu+n+1,1);
	for(int i=1,u,v;i<=m;++i) {
		scanf("%d%d",&u,&v);
		if(find(u)!=find(v)) dsu[u]=v,G[u].push_back(v),G[v].push_back(u);
		else rt=u,o=v;
	}
	dfs(rt,0);
	if(n==m) {
		if(d[o]&1) {
			if(f[rt]&1) return puts("-1"),0;
			for(int x=o;x;x=fa[x]) f[x]-=f[rt]>>1;
		} else {
			if(f[rt]) return puts("-1"),0;
			vector <int> w;
			for(int x=o;x;x=fa[x]) w.push_back(f[x]);
			sort(w.begin(),w.end());
			int z=w[(d[o]-1)/2];
			for(int x=o;x;x=fa[x]) f[x]-=z;
		}
	} else if(f[rt]) return puts("-1"),0;
	long long ans=0;
	for(int i=1;i<=n;++i) ans+=abs(f[i]);
	printf("%lld\n",ans);
	return 0;
}



*E. [AGC001F] Swap

Problem Link

题目大意

给定 1n 排列 p,如果 |pipj|=1|ij|k,可以交换 pi,pj,求能得到的最小字典序排列。

数据范围:n5×105

思路分析

考虑逆排列 q,相当于 |qiqi+1|k 时可以交换 qi,qi+1,那么如果 |qiqj|<k,说明 qi,qj 不能交换,即元素 qi,qj 的相对位置顺序不能改变。

可以证明这样的每一组 q 都能构造出来。

把限制放回到 p 上,如果 |ij|<k,那么 pi,pj 的值的大小关系不能改变。

那么把 p 看成拓扑序,i 向所有 |ij|<kpj>pi 的点连边,表示这些点最终的 p 一定比 pi 大。

然后满足字典序限制,每次找到最大的没有出度的点放在序列末尾。

一个点 i 没有出度当且仅当 pip[ik+1,i+k1] 中的最大值,可以直接判定。

并且取出一个 i 后,可能合法的点一定是 p[ik+1,i1],p[i+1,i+k1] 中的最大值。

用线段树求最大值,堆取出每次最大的 i 即可。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int n,K,a[MAXN];
struct zkw {
	static const int N=1<<19;
	int tr[N<<1];
	int cmp(int x,int y) { return a[x]>a[y]?x:y; }
	void init() {
		for(int i=1;i<=n;++i) tr[i+N]=i;
		for(int i=N-1;i;--i) tr[i]=cmp(tr[i<<1],tr[i<<1|1]);
	}
	void del(int x) {
		for(tr[x+=N]=0,x>>=1;x;x>>=1) tr[x]=cmp(tr[x<<1],tr[x<<1|1]);
	}
	int qry(int l,int r) {
		l=max(l,1),r=min(r,n);
		int s=0;
		for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) {
			if(~l&1) s=cmp(s,tr[l^1]);
			if(r&1) s=cmp(s,tr[r^1]);
		}
		return s;
	}
}	T;
int w[MAXN];
bool inq[MAXN];
priority_queue <int> q;
void ins(int x) {
	if(x&&!inq[x]&&T.qry(x-K+1,x+K-1)==x) q.push(x),inq[x]=true;
}
signed main() {
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	T.init();
	for(int i=1;i<=n;++i) ins(i);
	for(int i=n;i;--i) {
		int u=q.top(); q.pop(),w[u]=i,T.del(u);
		ins(T.qry(u-K+1,u));
		ins(T.qry(u,u+K-1));
	}
	for(int i=1;i<=n;++i) printf("%d\n",w[i]);
	return 0;
}




Round #24 - 2024.10.22

A. [AGC005D] Permutation

Problem Link

题目大意

计数有多少个 1n 排列 p 满足所有 |pii|k

数据范围:n,k2000

思路分析

建立 n×n 网格,一个排列等价于 n 个所在行列不同的点,限制为 |ij|=k 的点不能选。

考虑容斥,需要计算在这些不能选的位置中选 i 个不同行不同列的点的方案数 fi,对答案的贡献为 (1)ifi(ni)!

而这些不能选的点只有 2n 个,把同行同列的点之间连边,形成若干条链,我们要计数的就是大小为 i 的独立集数量,dpi,j 表示前 i 个点选了 j 个的方案数即可。

时间复杂度 O(n2)

代码呈现

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4005,MOD=924844033;
bool a[MAXN];
ll f[MAXN][MAXN],fac[MAXN];
signed main() {
	int n,k,p=0;
	scanf("%d%d",&n,&k);
	a[++p]=1;
	for(int i=0;i<n%k;++i) a[p+=n/k]=1,a[p+=n/k]=1;
	for(int i=0;i<k-n%k;++i) a[p+=n/k-1]=1,a[p+=n/k-1]=1;
	f[0][0]=1;
	for(int i=1;i<p;++i) {
		for(int j=0;j<=i;++j) {
			f[i][j]=f[i-1][j];
			if(j) f[i][j]=(f[i][j]+f[i-2+a[i]][j-1])%MOD;
		}
	}
	ll ans=0;
	for(int i=fac[0]=1;i<=n;++i) fac[i]=fac[i-1]*i%MOD;
	for(int i=0;i<=n;++i) ans=(ans+(i&1?-1:1)*fac[n-i]*f[p-1][i])%MOD;
	printf("%lld\n",(ans+MOD)%MOD);
	return 0;
}



B. [AGC006D] Median

Problem Link

题目大意

给定 12n1 排列,每次操作同时把所有 ai 换成 ai1,ai,ai+1 的中位数(删除开头结尾),求最终的 an

数据范围:n105

思路分析

考虑二分,只要做值域为 0,1 的情况。

我们发现如果 ai=ai+1 时,每次操作 ai,ai+1 都不会变。

因此如果 an1=anan+1=an,答案就是 an

否则序列形如 x,y,x,此时新的 an1 就是 an2,新的 an+1 就是 an+2,新的 an1an

如果此时 an2=anan+2=an 同样做完,否则原序列形如 y,x,y,x,y,下一次的 an1,an+1 就是原序列的 an3,an+3

不断重复判断 ank,an+k 即可。

时间复杂度 O(nlogn)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
int n,a[MAXN];
bool b[MAXN];
bool chk(int x) {
	for(int i=1;i<2*n;++i) b[i]=a[i]>=x;
	for(int i=1;i<n;++i) {
		if(b[n-i]==b[n]||b[n+i]==b[n]) return b[n];
		b[n]^=1;
	}
	return b[n];
}
signed main() {
	scanf("%d",&n);
	for(int i=1;i<2*n;++i) scanf("%d",&a[i]);
	int l=1,r=2*n-1,p=0;
	while(l<=r) {
		int mid=(l+r)>>1;
		if(chk(mid)) p=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d\n",p);
	return 0;
}



C. [AGC005E] Chase

Problem Link

题目大意

给定两棵树,A 和 B 分别有一棵棋子在树上轮流运动,初始在 a,b,A 可以把棋子沿第一棵树的边移动或不动,B 可以把棋子沿第二棵树的边移动或不动。

如果两人到相同点游戏结束,A 要最大化游戏轮数,B 要最小化游戏轮数,输出最终轮数。

数据范围:n2×105

思路分析

在第二棵树上考虑,如果第一棵树中有一条边在第二棵树上距离 >2,那么 A 在这条边的端点反复横跳可以让游戏无限继续,因此到达这些边的端点游戏就结束了。

否则在第二棵树以 b 为根考虑,任何时刻 a 都在当前 b 的子树中,如果某一步 a 跨越了子树,那么此时 a 的位置一定在 b 的邻居中,会直接被抓住。

因此对于一个点 x,先手最多在 depx1 步后到达 x,否则就会被 b 抓住,在第一棵树上搜索,判断能不能走到无限循环的点即可。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+5;
vector <int> A[MAXN],B[MAXN];
int fa[MAXN],d[MAXN];
void dfs0(int u) {
	for(int v:B[u]) if(v^fa[u]) fa[v]=u,d[v]=d[u]+1,dfs0(v);
}
int z=0;
bool e[MAXN];
void dfs1(int u,int fz,int s) {
	if(e[u]) puts("-1"),exit(0);
	z=max(z,d[u]*2);
	for(int v:A[u]) if(v!=fz&&s+1<d[v]) dfs1(v,u,s+1);
}
signed main() {
	int n,a,b;
	scanf("%d%d%d",&n,&a,&b);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),A[u].push_back(v),A[v].push_back(u);
	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),B[u].push_back(v),B[v].push_back(u);
	dfs0(b);
	for(int u=1;u<=n;++u) for(int v:A[u]) if(d[u]>=d[v]) {
		if((d[u]-d[v]>2)||(fa[u]!=fa[v]&&fa[u]!=v&&fa[fa[u]]!=v)) e[u]=e[v]=true;
	}
	dfs1(a,0,0);
	printf("%d",z);
	return 0;
}



*D. [AGC006E] Rotate

Problem Link

题目大意

给定 3×n 网格,每次旋转 3×3 子矩阵 180,判定是否能得到给定目标网格。

数据范围:n105

思路分析

翻转 3×3 等价于给每一列的元素反转,然后交换第一列和第三列。

因此每一列的内部元素不变,只有可能反转,那么每列都必须是 [x1,x,x+1][x+1,x,x1]

我们只关心每一列的 x 和其方向,如果方向错误看成 x

那么我们可以交换 ai1,ai+1 并取反 ai1,ai,ai+1

使得 |ai|=i 只要所有 aiimod2,看成交换同奇偶的相邻元素,一定可以构造。

只需要调整每个元素的符号,我们发现可以按如下的方式操作:

 1  2  3  4  5
-3 -2 -1  4  5 [1,3]
-3 -2 -5 -4  1 [3,5]
 5  2  3 -4  1 [1,3]
 5  2 -1  4 -3 [3,5]
 1 -2  5  4 -3 [1,3]
 1 -2  3 -4  5 [3,5]

此时可以取反距离为 2 的两个点。

并且对于 a1,an,可以按下述操作先取反 a1a4 再取反 a2,a4

 1  2  3  4
-3 -2 -1  4 [1,3]
-3 -4  1  2 [2,4]
-1  4  3  2 [1,3]
-1 -2 -3 -4 [2,4]

因此只要奇数下标和偶数下标中符号错误的元素分别有偶数个即可。

交换奇数下标的两个元素,会让偶数下标中符号错误的元素 ±1 个,因此只要求出奇数下标和偶数下标子序列的逆序对奇偶性。

我们知道排列奇偶性等于 ncmod2,其中 c 是置换环数量。

时间复杂度 O(n)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5;
int n,a[MAXN][3],p[MAXN];
bool vis[MAXN],x[2];
signed main() {
	scanf("%d",&n);
	for(int i:{0,1,2}) for(int j=1;j<=n;++j) scanf("%d",&a[j][i]);
	for(int i=1;i<=n;++i) {
		if(a[i][1]==a[i][0]+1&&a[i][2]==a[i][1]+1) p[i]=(a[i][1]+1)/3;
		else if(a[i][1]==a[i][0]-1&&a[i][2]==a[i][1]-1) p[i]=-(a[i][1]+1)/3;
		else return puts("No"),0;
		if((i-p[i])&1) return puts("No"),0;
		if(p[i]<0) x[i&1]^=1,p[i]*=-1;
	}
	for(int i=1;i<=n;++i) if(!vis[i]) {
		int k=0;
		for(int j=i;!vis[j];j=p[j]) vis[j]=true,++k;
		x[(i&1)^1]^=(k-1)&1;
	}
	puts(x[0]||x[1]?"No":"Yes");
	return 0;
}



E. [AGC010E] Coprime

Problem Link

题目大意

给定 a1an,重排 ai,使得按如下操作得到的最大字典序序列字典序最小:

  • 对于 gcd(ai,ai+1)>1,交换 ai,ai+1

数据范围:n2000

思路分析

对于一个序列,能得到的最大字典序序列可以刻画:如果 gcd(ai,aj)>1,那么 ai,aj 在最终序列相对顺序不变,可以看成 DAG 最大字典序拓扑序。

那么把 gcd(ai,aj)>1 的点取出来,他们之间一定有边,重排 a 等价于给这些边定向成 DAG,最大化最大字典序拓扑序。

对每个连通块分别构造,我们先找到最小值 u,然后要让这个点能到达其他所有点,那么这个图中包含一个 u 为根的外向 dfs 树。

然后将 u 的最小邻居尝试定为第二个搜到的点,递归构造将 u 的邻居都由 u 出发连边即可。

时间复杂度 O(n2)

代码呈现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
vector <int> G[MAXN],E[MAXN];
int n,a[MAXN],d[MAXN];
bool vis[MAXN];
void dfs(int u) {
	vis[u]=true;
	for(int v:G[u]) if(!vis[v]) E[u].push_back(v),++d[v],dfs(v);
}
signed main() {
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) if(__gcd(a[i],a[j])>1) G[i].push_back(j),G[j].push_back(i);
	for(int i=1;i<=n;++i) if(!vis[i]) dfs(i);
	priority_queue <int> q;
	for(int i=1;i<=n;++i) if(!d[i]) q.push(i);
	while(q.size()) {
		int u=q.top(); q.pop();
		printf("%d ",a[u]);
		for(int v:E[u]) if(!--d[v]) q.push(v);
	}
	puts("");
	return 0;
}
posted @   DaiRuiChen007  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示