【比赛记录】2025CSP+NOIP 冲刺模拟赛合集V

11.13 HZOJ NOIP2025模拟7

A B C D Sum Rank
100 100 - - 200 6/27

别笑,你来你也只能过第二关。

—— Dyk

A. Imbalance

理论上是2025CSP-S模拟赛57 C 的原,但完全忘了。

对于双 \(\log\) 做法,路径计数考虑点分治,对于当前子树的两条根链,相当于要满足一个二维偏序,容斥+二维数点即可。理论 \(70pts\),实则 \(100pts\)

考虑如果在链上怎么做,建出小根笛卡尔树和大根笛卡尔树,相当于要求两个点在两棵树上分别满足「祖先-后代」/「后代-祖先」关系,在一棵树上跑出 dfn,再在另一棵树上用树状数组数点即可。

而如果在树上,那建 kruskal 重构树即可。

放个点分治代码吧实则是懒得写重构树

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
namespace IO{
	const int bufsz=1<<20;
	char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
	#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
	il int read(){
		char ch=getchar();
		while(ch<'0'||ch>'9'){
			ch=getchar();
		}
		int x=0;
		while(ch>='0'&&ch<='9'){
			x=(x<<1)+(x<<3)+(ch^48);
			ch=getchar();
		}
		return x;
	}
	#undef getchar
}
using IO::read;
const int maxn=5e5+5;
int n,rt,tot,sz[maxn],mxs[maxn],cmx,cmn;
ll ans;
bool ban[maxn];
pii vmx[maxn],vmn[maxn];
vector<int> e[maxn];
il void dfs1(int u,int fa){
	sz[u]=1,mxs[u]=0;
	for(int v:e[u]){
		if(v==fa||ban[v]){
			continue;
		}
		dfs1(v,u);
		sz[u]+=sz[v];
		mxs[u]=max(mxs[u],sz[v]);
	}
	if(!rt||max(mxs[u],tot-sz[u])<max(mxs[rt],tot-sz[rt])){
		rt=u;
	}
}
il void dfs2(int u,int fa,int mx,int mn){
	if(u>mx){
		mx=u;
		vmx[++cmx]=mp(mx,mn);
	}
	if(u<mn){
		mn=u;
		vmn[++cmn]=mp(mx,mn);
	}
	for(int v:e[u]){
		if(v==fa||ban[v]){
			continue;
		}
		dfs2(v,u,mx,mn);
	}
}
struct{
	#define lowbit(x) (x&-x)
	int tr[maxn];
	il void add(int p){
		for(;p<=n;p+=lowbit(p)){
			tr[p]++;
		}
	}
	il int query(int p){
		int res=0;
		for(;p;p-=lowbit(p)){
			res+=tr[p];
		}
		return res;
	}
	il void clear(int p){
		for(;p<=n;p+=lowbit(p)){
			tr[p]=0;
		}
	}
	#undef lowbit
}F;
il void solve(int u){
//	cout<<u<<' ';
	rt=0,dfs1(u,0),u=rt,dfs1(u,0);
//	cout<<u<<' '<<ans<<' ';
	ban[u]=1,cmx=cmn=0;
	for(int v:e[u]){
		if(ban[v]){
			continue;
		}
		int smx=cmx+1,smn=cmn+1;
		dfs2(v,0,u,u);
		int tmx=cmx,tmn=cmn;
		sort(vmx+smx,vmx+tmx+1);
		sort(vmn+smn,vmn+tmn+1);
		int p=smn;
		for(int i=smx;i<=tmx;i++){
			while(p<=tmn&&vmn[p].fir<vmx[i].fir){
				F.add(vmn[p++].sec);
			}
			ans-=F.query(vmx[i].sec-1);
		}
		for(int i=smn;i<=tmn;i++){
			F.clear(vmn[i].sec);
		}
	}
	int tmx=cmx,tmn=cmn;
	sort(vmx+1,vmx+tmx+1);
	sort(vmn+1,vmn+tmn+1);
	int p=1;
	for(int i=1;i<=tmx;i++){
		if(vmx[i].sec==u){
			ans++;
		}
		while(p<=tmn&&vmn[p].fir<vmx[i].fir){
			F.add(vmn[p++].sec);
		}
		ans+=F.query(vmx[i].sec-1);
	}
	for(int i=1;i<=tmn;i++){
		if(vmn[i].fir==u){
			ans++;
		}
		F.clear(vmn[i].sec);
	}
//	cout<<ans<<'\n';
	for(int v:e[u]){
		if(!ban[v]){
			tot=sz[v],solve(v);
		}
	}
}
int main(){
	freopen("imbalance.in","r",stdin);
	freopen("imbalance.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	n=read();
	for(int i=1,u,v;i<n;i++){
		u=read(),v=read();
		e[u].pb(v),e[v].pb(u);
	}
	tot=n,solve(1);
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 原子

一个合法构造是,枚举步长,再枚举步长的剩余系作为开头,一直跳到不能跳为止。此时的答案为 \(\lfloor\frac{n}{2}\rfloor\cdot\lceil\frac{n}{2}\rceil\),考虑证明。

显然若 \([a,b)\cap[c,d)\ne\varnothing\),则不可能在一轮内解决。

考虑找出一堆两两有非空交的区间,设它们组成集合 \(S\),最大化 \(|S|\)。显然 \(S=\{[l,r)\mid l\le a,r\ge b,a<b\}\)。于是 \(|S|_{\max}=\lfloor\frac{n}{2}\rfloor\cdot\lceil\frac{n}{2}\rceil\)。于是答案至少为这个值。于是构造正确。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e3+5;
int n;
struct node{
	int s,d;
};
vector<node> ans;
int main(){
	freopen("atom.in","r",stdin);
	freopen("atom.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			if(j+i>n){
				break;
			}
			ans.pb((node){j,i});
		}
	}
	cout<<ans.size()<<'\n';
	for(node x:ans){
		int s=x.s,d=x.d;
		cout<<(n-s)/d+(s>1)<<' ';
		if(s>1){
			cout<<s-1<<' ';
		}
		while(s+d<=n){
			cout<<d<<' ';
			s+=d;
		}
		cout<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 旅行计划

考虑树形 DP,在路径的 LCA 处对答案进行贡献。

设 LCA 为 \(u\),考虑路径的形态,必然是从 \(u\) 的子树中的某个点 \(v\) 开始走到 \(u\),然后在子树内绕一大圈,再走到 \(u\) 子树内的某个点 \(v'\)

于是设 \(f_{u,0/1/2}\)

  • \(f_{u,0}\):从 \(u\) 开始走出去的一条链的最大长度。
  • \(f_{u,1}\):从 \(u\) 开始走一圈回到 \(u\) 的最大长度。
  • \(f_{u,2}\):以 \(u\) 为 LCA 的最长路径。

于是对于 \(u\) 的每个儿子,我们可以转移:

\[f_{u,1}\gets f_{v,1}+\lfloor\frac{w}{2}\rfloor\hspace{3mm}(w>1)\\[3mm] f_{u,0/2}\gets f_{u,1}\\[3mm] f_{u,0/2}\gets\begin{cases} \begin{aligned} &f_{v,0}+\lfloor\frac{w-1}{2}\rfloor+1&w>1\\ &f_{v,0}+1&w=1 \end{aligned} \end{cases} \]

但这样并没有转移完整。考虑从起点 \(x\) 开始走,走到它的某个祖先 \(v\)(注意不是 \(u\),而是它的某个后代),再一直往上走到 \(u\),再走回到 \(v\),最后走到 \(v\) 子树中的某个点 \(x'\)。这样 LCA 也是 \(u\),但上面的转移并未考虑到。于是还有转移:

\[f_{u,2}\gets f_{v,2}+(f_{u,1}-f_{v,1})\hspace{3mm}(w>1) \]

答案即为 \(\max\limits_{u=1}^{n}\{f_{u,2}\}\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define fir first
#define sec second
#define pii pair<int,int>
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int T,n;
ll f[maxn][3],ans;
vector<pii> e[maxn];
il void dfs(int u,int fa){
	f[u][0]=f[u][1]=f[u][2]=0;
	ll mx1=0,mx2=0;
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa){
			continue;
		}
		dfs(v,u);
		if(w>1){
			f[u][1]+=f[v][1]+w/2*2;
			ll x=f[v][0]-f[v][1]+(w&1?1:-1);
			if(x>mx1){
				mx2=mx1,mx1=x;
			}else if(x>mx2){
				mx2=x;
			}
		}else{
			ll x=f[v][0]+1;
			if(x>mx1){
				mx2=mx1,mx1=x;
			}else if(x>mx2){
				mx2=x;
			}
		}
	}
	f[u][0]=f[u][1]+mx1,f[u][2]=f[u][0]+mx2;
	for(pii i:e[u]){
		int v=i.fir,w=i.sec;
		if(v==fa){
			continue;
		}
		if(w>1){
			f[u][2]=max(f[u][2],f[v][2]+f[u][1]-f[v][1]);
		}
	}
	ans=max(ans,f[u][2]);
}
il void solve(){
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	ans=0,dfs(1,0);
	cout<<ans<<'\n';
	for(int i=1;i<=n;i++){
		e[i].clear();
	}
}
int main(){
	freopen("plan.in","r",stdin);
	freopen("plan.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 简单题

11.15 2025noip模拟赛76(HZOJ NOIP2025模拟8)

A B C D Sum Rank
100 40(20) 9 8 157(137) 7/16(11/33)

A. 王哥与荷塘(fish)

因为要求最值,转成切比雪夫距离。发现切比雪夫坐标旋转和曼哈顿坐标是同步的,都是 \((x,y)\to(-y,x)\)。于是用线段树维护区间横纵坐标的最大最小值,区间旋转直接打标记即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,m,xx[maxn],yy[maxn],tag[maxn<<2];
struct node{
	int bx,sx,by,sy,ans;
	il node(int bx=0,int sx=0,int by=0,int sy=0,int ans=0)
		:bx(bx),sx(sx),by(by),sy(sy),ans(ans){}
	il node operator+(const node &x)const{
		return node(max(bx,x.bx),min(sx,x.sx),max(by,x.by),min(sy,x.sy),max({ans,x.ans,bx-x.sx,x.bx-sx,by-x.sy,x.by-sy}));
	}
}tr[maxn<<2];
#define lid id<<1
#define rid id<<1|1
#define bx(id) tr[id].bx
#define sx(id) tr[id].sx
#define by(id) tr[id].by
#define sy(id) tr[id].sy
#define ans(id) tr[id].ans
il void pushup(int id){
	tr[id]=tr[lid]+tr[rid];
}
il void pushtag(int id,int x){
	(tag[id]+=x)&=3;
	while(x--){
		tr[id]=node(-sy(id),-by(id),bx(id),sx(id),ans(id));
	}
}
il void pushdown(int id){
	if(tag[id]){
		pushtag(lid,tag[id]);
		pushtag(rid,tag[id]);
		tag[id]=0;
	}
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=node(xx[l],xx[l],yy[l],yy[l],0);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void add(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		pushtag(id,1);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r);
	}
	pushup(id);
}
il node query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return tr[id];
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(r<=mid){
		return query(lid,L,mid,l,r);
	}
	if(l>mid){
		return query(rid,mid+1,R,l,r);
	}
	return query(lid,L,mid,l,r)+query(rid,mid+1,R,l,r);
}
int main(){
//	system("fc fish6.ans my.out");
	freopen("fish.in","r",stdin);
	freopen("fish.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,x,y;i<=n;i++){
		cin>>x>>y;
		xx[i]=x-y,yy[i]=x+y;
	}
	build(1,1,n);
//	cout<<ans(1);
	while(m--){
		int opt,l,r;
		cin>>opt>>l>>r;
		if(opt==1){
			add(1,1,n,l,r);
		}else{
			cout<<query(1,1,n,l,r).ans<<'\n';
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 王哥与演出(oblivious)

考虑对于每个 \(i\) 连边 \(x_i\operatorname*{\longleftrightarrow}\limits^{a_i}y_i+r\)。考虑每次选择将边定向,第一轮选指向 \(y_i+r\),第二轮选指向 \(x_i\),于是每个点的出度 \(\le1\),于是就是一堆生成树和内向基环树,类似 kruskal 跑一下即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e6+5;
int n,m,r,c,fa[maxn];
bool tag[maxn];
struct edge{
	int u,v,w;
	il bool operator<(const edge &x)const{
		return w>x.w;
	}
}e[maxn];
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
int main(){
	freopen("oblivious.in","r",stdin);
	freopen("oblivious.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>m>>r>>c;
	n=r+c;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		e[i]={u,v+r,w};
	}
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	sort(e+1,e+m+1);
	ll ans=0;
	for(int i=1;i<=m;i++){
		int u=find(e[i].u),v=find(e[i].v);
		if(u!=v&&!(tag[u]&&tag[v])){
			ans+=e[i].w;
			fa[u]=v,tag[v]|=tag[u];
		}else if(!tag[u]){
			tag[u]=1,ans+=e[i].w;
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 王哥与序列(array)

D. 王哥与数字(digit)

11.17 2025noip模拟赛77

A B C D Sum Rank
105 30 - - 135 8/12

我说这考一半拉下去体测是什么操作啊……

A. 秋秋飞车

每次移动相当于在一行或一列上的区间查询,动态开点线段树即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,m,tot,rt[2][maxn],ls[maxn*40],rs[maxn*40];
ll tr[maxn*40];
il void pushup(int id){
	tr[id]=tr[ls[id]]+tr[rs[id]];
}
il void add(int &id,int l,int r,int p,int x){
	if(!id){
		id=++tot;
	}
	if(l==r){
		tr[id]+=x;
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		add(ls[id],l,mid,p,x);
	}else{
		add(rs[id],mid+1,r,p,x);
	}
	pushup(id);
}
il ll query(int id,int L,int R,int l,int r){
	if(!id){
		return 0;
	}
	if(L>=l&&R<=r){
		return tr[id];
	}
	int mid=(L+R)>>1;
	ll res=0;
	if(l<=mid){
		res+=query(ls[id],L,mid,l,r);
	}
	if(r>mid){
		res+=query(rs[id],mid+1,R,l,r);
	}
	return res;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,x,y,w;i<=n;i++){
		cin>>x>>y>>w;
		add(rt[0][x],1,2e5,y,w);
		add(rt[1][y],1,2e5,x,w);
	}
//	cerr<<tot<<'\n';
	int cx=1,cy=1;
	ll ans=0;
	while(m--){
		int d,v;
		cin>>d>>v;
		switch(d){
			case 0:{
				ans+=query(rt[1][cy],1,2e5,cx+1,cx+v);
				cx+=v;
				break;
			}case 1:{
				ans+=query(rt[0][cx],1,2e5,cy+1,cy+v);
				cy+=v;
				break;
			}case 2:{
				ans+=query(rt[1][cy],1,2e5,cx-v,cx-1);
				cx-=v;
				break;
			}default:{
				ans+=query(rt[0][cx],1,2e5,cy-v,cy-1);
				cy-=v;
				break;
			}
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 操作序列

考虑枚举最终奇数位和偶数位分别是什么。对于每个位置 \(i\),若其符合最终的要求,则不用动;否则会对答案造成 \(1\) 的贡献。然而还有情况,假设奇数位为 \(A\),偶数位为 \(B\),那么一段与最终形态相反的极长串(即例如开头是奇数位,这一段长 \(BABABAB\) 这样)无法一次性改掉,需要先引入一个 \(C\),例如上面那个就先把所有 \(A\) 改成 \(C\),再把 \(B\) 改成 \(A\),最后再把 \(C\) 改成 \(B\)。设这一段长度为 \(p\),奇数位有 \(cnt_{1,A}\)\(A\),偶数位有 \(cnt_{0,B}\)\(B\),则答案为 \(\min\limits_{A,B}n-cnt_{1,A}-cnt_{0,B}+\sum\lfloor\frac{p}{2}\rfloor\)。于是我们可以得到一个 \(O(nV^2)\) 算法。

考虑优化。注意到 \(BABABAB\) 这种段只有 \(O(n)\) 个,于是我们可以先对有这些段的 \((A,B)\) 计算一遍答案,再对剩下的 \((A,B)\) 计算答案。剩下的 \((A,B)\) 的答案用线段树维护一下 \(n-cnt_{0,B}\) 的最小值即可。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define lid id<<1
#define rid id<<1|1
#define pii pair<int,int>
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=2e5+5,inf=1e9;
int T,n,a[maxn],cnt[2][maxn],tr[maxn<<2];
map<int,int> mp[maxn];
il void pushup(int id){
	tr[id]=min(tr[lid],tr[rid]);
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=n-cnt[0][l];
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void upd(int id,int l,int r,int p,int x){
	if(l==r){
		tr[id]=x;
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		upd(lid,l,mid,p,x);
	}else{
		upd(rid,mid+1,r,p,x);
	}
	pushup(id);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cnt[0][i]=cnt[1][i]=0,mp[i].clear();
		}
		for(int i=1;i<=n;i++){
			cin>>a[i];
			cnt[i&1][a[i]]++;
		}
//		for(int i=1;i<=n;i++){
//			cout<<cnt[0][i]<<' '<<cnt[1][i]<<'\n';
//		}
		int ans=inf;
		for(int i=1;i<n;i++){
			int j=i;
			while(j<n&&a[j+1]==((j-i)&1?a[i]:a[i+1])){
				j++;
			}
			int A=a[i],B=a[i+1];
			if(i%2==0){
				swap(A,B);
			}
			mp[B][A]+=(j-i+1)>>1;
//			cout<<i<<' '<<j<<'\n';
			i=j-1;
		}
//		puts("---------");
		build(1,1,n);
		for(int i=1;i<=n;i++){
			upd(1,1,n,i,inf);
			for(pii x:mp[i]){
				int j=x.fir;
//				cout<<i<<' '<<j<<' '<<n-cnt[1][i]-cnt[0][j]+x.sec<<'\n';
				ans=min(ans,n-cnt[1][i]-cnt[0][j]+x.sec);
				upd(1,1,n,j,inf);
			}
//			cout<<i<<' '<<tr[1]-cnt[1][i]<<'\n';
			ans=min(ans,tr[1]-cnt[1][i]);
			upd(1,1,n,i,n-cnt[0][i]);
			for(pii x:mp[i]){
				int j=x.fir;
				upd(1,1,n,j,n-cnt[0][j]);
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 子矩阵

考虑折半搜索。首先 \(O(2^n)\) 枚举删除哪些行,然后用折半搜索搜出删除哪些列即可。时间复杂度 \(O(2^{n+\frac{m}{2}}m)\)

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
int n,m,kk,a[20][20],b[20];
map<int,int> mp;
il void dfs1(int x,int S,int sum){
	if(x>m>>1){
		mp[sum]=S;
//		cout<<bitset<3>(S)<<' '<<sum<<'\n';
		return ;
	}
	dfs1(x+1,S|1<<(x-1),sum);
	dfs1(x+1,S,sum+b[x]);
}
il void dfs2(int x,int S,int sum,int sta){
	if(x>m){
//		cout<<bitset<3>(S)<<' '<<sum<<'\n';
		if(mp.count(kk-sum)){
			int ss=mp[kk-sum];
			cout<<"YES\n"<<__builtin_popcount(sta)+__builtin_popcount(ss)+__builtin_popcount(S)<<'\n';
			for(int i=1;i<=n;i++){
				if(sta>>(i-1)&1){
					cout<<1<<' '<<i<<'\n';
				}
			}
			for(int i=1;i<=m>>1;i++){
				if(ss>>(i-1)&1){
					cout<<2<<' '<<i<<'\n';
				}
			}
			for(int i=(m>>1)+1;i<=m;i++){
				if(S>>(i-1)&1){
					cout<<2<<' '<<i<<'\n';
				}
			}
			exit(0);
		}
		return ;
	}
	dfs2(x+1,S|1<<(x-1),sum,sta);
	dfs2(x+1,S,sum+b[x],sta);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	cin>>kk;
	for(int S=0;S<1<<n;S++){
//		cout<<bitset<3>(S)<<'\n';
		for(int i=1;i<=m;i++){
			b[i]=0;
		}
		for(int i=1;i<=n;i++){
			if(S>>(i-1)&1){
				continue;
			}
			for(int j=1;j<=m;j++){
				b[j]+=a[i][j];
			}
		}
		mp.clear();
		dfs1(1,0,0),dfs2((m>>1)+1,0,0,S);
	}
	cout<<"NO\n";
	return 0;
}
}
signed main(){return asbt::main();}

D. 树上穿越

首先考虑 \(t=0\)。显然二分,每次找到最远的点,贪心地在能正好使其距离变成 \(mid\) 的祖先处连边。用线段树能做到 \(O(k\log^2n)\)

然后我们实际上可以在换根过程中重复这个过程,只需要分讨祖先关系即可,时间复杂度 \(O(nk\log^2n)\),但由于常数比较大过不了(或许卡卡能过?)。进一步优化,考虑两个相邻的点,其答案必然最多差 \(1\),于是除了 \(1\) 之外的二分区间就是 \(O(1)\) 的。时间复杂度 \(O(k\log^2n+nk\log n)\),但因为 \(k<n\),故有效的 \(k\) 最大是 \(\sqrt{n}\) 级别的。

然后你就调吧你就。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define lid id<<1
#define rid id<<1|1
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=2e5+5,inf=1e9;
int n,m,T,dfn[maxn],sz[maxn],cnt,oula[maxn],top;
int dep[maxn],ans[maxn],tag[maxn<<2],anc[maxn][20];
pii tr[maxn<<2],stk[maxn<<1];
vector<int> e[maxn];
namespace LCA{
	int dfn[maxn],cnt,st[maxn][20];
	il int get(int x,int y){
		return dep[x]<dep[y]?x:y;
	}
	il void dfs(int u,int fa){
		dfn[u]=++cnt;
		st[cnt][0]=fa;
		for(int v:e[u]){
			if(v==fa){
				continue;
			}
			dfs(v,u);
		}
	}
	il void build(){
		dfs(1,0);
		for(int j=1;j<=18;j++){
			for(int i=1;i+(1<<j)-1<=n;i++){
				st[i][j]=get(st[i][j-1],st[i+(1<<(j-1))][j-1]);
			}
		}
	}
	il int lca(int u,int v){
		if(u==v){
			return u;
		}
		u=dfn[u],v=dfn[v];
		if(u>v){
			swap(u,v);
		}
		u++;
		int p=__lg(v-u+1);
		return get(st[u][p],st[v-(1<<p)+1][p]);
	}
}
using LCA::lca;
il void dfs1(int u,int fa){
	oula[++cnt]=u;
	dfn[u]=cnt,sz[u]=1;
	anc[u][0]=fa;
	for(int i=1;i<=18;i++){
		anc[u][i]=anc[anc[u][i-1]][i-1];
	}
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
		dep[v]=dep[u]+1;
		dfs1(v,u);
		sz[u]+=sz[v];
	}
}
il int kth(int u,int k){
	int d=0;
	while(k){
		if(k&1){
			u=anc[u][d];
		}
		k>>=1,d++;
	}
	return u;
}
il void pushup(int id){
	tr[id]=max(tr[lid],tr[rid]);
}
il void pushtag(int id,int x){
	tag[id]+=x,tr[id].fir+=x;
}
il void pushdown(int id){
	if(tag[id]){
		pushtag(lid,tag[id]);
		pushtag(rid,tag[id]);
		tag[id]=0;
	}
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id]=mp(dep[oula[l]],l);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void add(int id,int L,int R,int l,int r,int x){
	if(l>r){
		return ;
	}
	if(L>=l&&R<=r){
		pushtag(id,x);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r,x);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r,x);
	}
	pushup(id);
}
il bool check(int u,int k){
//	cout<<u<<' '<<k<<":\n";
	int cnt=0;
	while(tr[1].fir>k){
		if(cnt==m){
			return 0;
		}
		cnt++;
		int v=oula[tr[1].sec];
//		cout<<tr[1].fir<<' '<<v<<'\n';
		if(dfn[v]>=dfn[u]&&dfn[v]<dfn[u]+sz[u]){
//			puts("666");
			int x=kth(v,k-1);
			add(1,1,n,dfn[x],dfn[x]+sz[x]-1,-inf);
			stk[++top]=mp(dfn[x],dfn[x]+sz[x]-1);
		}else if(dfn[u]>=dfn[v]&&dfn[u]<dfn[v]+sz[v]){
//			puts("777");
			int x=kth(u,tr[1].fir-k+1);
			x=kth(u,dep[u]-dep[x]-1);
			add(1,1,n,1,dfn[x]-1,-inf);
			add(1,1,n,dfn[x]+sz[x],n,-inf);
			stk[++top]=mp(1,dfn[x]-1);
			stk[++top]=mp(dfn[x]+sz[x],n);
		}else{
			int y=lca(u,v);
			int dv=dep[v]-dep[y],du=dep[u]-dep[y];
			if(dv>k-1){
//				puts("888");
				int x=kth(v,k-1);
//				cout<<x<<'\n';
				add(1,1,n,dfn[x],dfn[x]+sz[x]-1,-inf);
				stk[++top]=mp(dfn[x],dfn[x]+sz[x]-1);
			}else{
//				puts("999");
				int x=kth(u,du-k+1+dv);
				x=kth(u,dep[u]-dep[x]-1);
				add(1,1,n,1,dfn[x]-1,-inf);
				add(1,1,n,dfn[x]+sz[x],n,-inf);
				stk[++top]=mp(1,dfn[x]-1);
				stk[++top]=mp(dfn[x]+sz[x],n);
			}
		}
	}
	return 1;
}
il void solve(int u,int l,int r){
	while(l<r){
		int mid=(l+r)>>1;
//		cout<<mid<<' ';
		if(check(u,mid)){
			r=mid;
//			cout<<1<<'\n';
		}else{
			l=mid+1;
//			cout<<0<<'\n';
		}
		while(top){
			add(1,1,n,stk[top].fir,stk[top].sec,inf);
			top--;
		}
	}
	ans[u]=l;
}
il void debug(int id,int l,int r){
	cout<<id<<' '<<l<<' '<<r<<' '<<tr[id].fir<<' '<<tr[id].sec<<'\n';
	if(l==r){
		return ;
	}
	pushdown(id);
	int mid=(l+r)>>1;
	debug(lid,l,mid);
	debug(rid,mid+1,r);
}
il void dfs2(int u,int fa){
	for(int v:e[u]){
		if(v==fa){
			continue;
		}
//		cout<<v<<'\n';
		add(1,1,n,dfn[v],dfn[v]+sz[v]-1,-1);
		add(1,1,n,1,dfn[v]-1,1);
		add(1,1,n,dfn[v]+sz[v],n,1);
//		debug(1,1,n);
		solve(v,max(ans[u]-1,1ll),ans[u]+1);
		dfs2(v,u);
		add(1,1,n,dfn[v],dfn[v]+sz[v]-1,1);
		add(1,1,n,1,dfn[v]-1,-1);
		add(1,1,n,dfn[v]+sz[v],n,-1);
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>T;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	dfs1(1,0),build(1,1,n),LCA::build();
//	for(int i=1;i<=n;i++){
//		cout<<oula[i]<<' ';
//	}
//	cout<<'\n';
	solve(1,1,n);
	if(!T){
		cout<<ans[1];
		return 0;
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	return 0;
}
}
signed main(){return asbt::main();}
/*
16 1 1
7 2
13 3
14 1
14 5
2 12
15 6
8 4
4 10
14 13
14 15
14 11
12 16
8 14
12 8
3 9
4 4 4 4 4 5 5 3 5 5 4 4 4 3 4 4
*/

11.18 HZOJ NOIP2025模拟赛10

A B C D Sum Rank
70 - 0 - 70 13/26

A. 春江潮水连海平

首先考虑题目到底说的啥。观察题面,注意到样例 2 中答案为 \(12.25\),这启示我们这个过程中一份水会被分成非常多份。而这些排水口所在位置不同,因此每个时刻,所有还在水中的排水口实际上是在同时排同一份水。我在场上理解成排水口会将水分成很多段了……

于是就很好做了,扫描时间,给当前所有排水口存在一个大根堆里,不断模拟水流到堆顶所在位置的过程即可。

观察。注意。启示。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5,inf=1e18;
int m,n,lsh[maxn];
struct{
	int t,p;
}a[maxn];
vector<int> vc[maxn];
priority_queue<int> q;
int main(){
	freopen("water.in","r",stdin);
	freopen("water.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>m>>n;
	int tot=0;
	for(int i=1;i<=n;i++){
		cin>>a[i].t>>a[i].p;
		lsh[++tot]=a[i].t;
	}
	sort(lsh+1,lsh+tot+1);
	tot=unique(lsh+1,lsh+tot+1)-lsh-1;
	for(int i=1;i<=n;i++){
		a[i].t=lwrb(lsh+1,lsh+tot+1,a[i].t)-lsh;
		vc[a[i].t].pb(a[i].p);
	}
	lsh[tot+1]=inf;
	double cur=m;
	for(int i=1;i<=tot;i++){
		for(int p:vc[i]){
			if(p<cur){
				q.push(p);
			}
		}
		double t=lsh[i];
		while(q.size()){
			double x=cur-q.top();
			x/=q.size();
			if(x>lsh[i+1]-t){
				cur-=q.size()*(lsh[i+1]-t);
				break;
			}
			t+=x,cur=q.top(),q.pop();
		}
		if(cur==0){
			cout<<fixed<<setprecision(6)<<t;
			return 0;
		}
	}
	cout<<-1;
	return 0;
}
}
signed main(){return asbt::main();}

B. 落月摇情满江树

考虑可行性 DP。设:

  • \(f_{u,0}\)\(u\) 的子树可以表示为若干个环和一条从根走到最右边叶子的路径。
  • \(f_{u,1}\)\(u\) 的子树可以表示为若干个环和一条从根走到最左边叶子的路径。
  • \(f_{u,2}\)\(u\) 的子树可以表示为若干个环和一条从最左边叶子走到最右边叶子的路径。
  • \(f_{u,3}\)\(u\) 的子树可以表示为若干个环。

这里我们只讨论 \(f_{u,0}\)\(f_{u,2}\) 的转移。

对于 \(f_{u,0}\),我们显然希望儿子的一段前缀满足 \(f_{v,3}=1\),这段前缀后面那个儿子满足 \(f_{v,0}=1\),这个儿子后面那段后缀满足 \(f_{v,2}=1\)。给 \(f_{v,2}\)\(f_{v,3}\) 做前后缀和,对每个儿子判断一遍即可。

对于 \(f_{u,2}\),我们希望儿子序列形如这个样子:\(22222133333022222\)。直接扫描线,打一个标记记录当前扫到的前缀是否能满足 \(222221333\) 这样的形态即可。

另外两种转移同理,时间复杂度线性。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int T,n,a[maxn],f[maxn][4];
int pre[maxn][4],suf[maxn][4];
vector<int> e[maxn];
il void dfs(int u){
	if(!a[u]){
		f[u][0]=f[u][1]=f[u][2]=1,f[u][3]=0;
		return ;
	}
	for(int i=1;i<=a[u];i++){
		dfs(e[u][i]);
	}
	pre[0][0]=pre[0][1]=pre[0][2]=pre[0][3]=0;
	suf[a[u]+1][0]=suf[a[u]+1][1]=suf[a[u]+1][2]=suf[a[u]+1][3]=0;
	for(int i=1;i<=a[u];i++){
		pre[i][0]=pre[i-1][0]+f[e[u][i]][0];
		pre[i][1]=pre[i-1][1]+f[e[u][i]][1];
		pre[i][2]=pre[i-1][2]+f[e[u][i]][2];
		pre[i][3]=pre[i-1][3]+f[e[u][i]][3];
	}
	for(int i=a[u];i;i--){
		suf[i][0]=suf[i+1][0]+f[e[u][i]][0];
		suf[i][1]=suf[i+1][1]+f[e[u][i]][1];
		suf[i][2]=suf[i+1][2]+f[e[u][i]][2];
		suf[i][3]=suf[i+1][3]+f[e[u][i]][3];
	}
	f[u][0]=f[u][1]=f[u][2]=f[u][3]=0;
	for(int i=1;i<=a[u];i++){
		if(pre[i-1][3]==i-1&&f[e[u][i]][0]&&suf[i+1][2]==a[u]-i){
			f[u][0]=1;
		}
	}
	for(int i=1;i<=a[u];i++){
		if(pre[i-1][2]==i-1&&f[e[u][i]][1]&&suf[i+1][3]==a[u]-i){
			f[u][1]=1;
		}
	}
	bool flag=0;
	for(int i=1;i<=a[u];i++){
		if(flag&&f[e[u][i]][0]&&suf[i+1][2]==a[u]-i){
			f[u][2]=1;
			break;
		}
		if(!f[e[u][i]][3]){
			flag=0;
		}
		if(f[e[u][i]][1]&&pre[i-1][2]==i-1){
			flag=1;
		}
	}
	flag=0;
	for(int i=1;i<=a[u];i++){
		if(flag&&f[e[u][i]][1]&&suf[i+1][3]==a[u]-i){
			f[u][3]=1;
			break;
		}
		if(!f[e[u][i]][2]){
			flag=0;
		}
		if(f[e[u][i]][0]&&pre[i-1][3]==i-1){
			flag=1;
		}
	}
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			e[i].pb(0);
			for(int j=1,x;j<=a[i];j++){
				cin>>x;
				e[i].pb(x);
			}
		}
		dfs(1);
		cout<<(f[1][3]?"YES":"NO")<<'\n';
		for(int i=1;i<=n;i++){
			e[i].clear();
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 禁止套娃

普通的本质不同子序列计数运用贪心思想,在每个本质不同子序列第一次出现的位置计算贡献。

回到本题,设外层子序列选择的下标集合为 \(I\),内层选择的为 \(J\),考虑类似贪心,于是有以下限制:

  • \(I\) 中相邻的下标 \(i,i'\)\(a_{i+1\sim i'-1}\) 中不存在 \(=a_{i'}\) 的值。
  • \(J\) 中相邻的下标 \(j,j'\)\(a_{I\cap(j,j')}\) 中不存在 \(=a_{j'}\) 的值。

\(f_i\) 表示考虑到 \(i\) 且内外层都选 \(i\) 的答案,考虑从 \(j\) 转移,需要考虑 \((j,i)\) 这一段怎么选择外层子序列。设选择的集合为 \(K\),有如下限制:

  1. \(K\) 中相邻的两个下标 \(k,k'\)\(a_{k+1\sim k'-1}\) 中不存在 \(=a_{k'}\) 的值。

  2. \(K\) 的最后一个位置 \(k_r\) 满足 \(a_{k_r+1\sim i-1}\) 中不存在 \(=a_i\) 的值。

  3. \(\forall k\in K,a_k\ne a_i\)

于是我们可以再设 \(g_{i,j}\) 表示考虑 \((j,i)\),满足 1、3 条件的本质不同子序列数,有转移:

\[f_i\gets f_j\times(g_{i,j}-g_{pre_i,j}) \]

考虑 \(g\) 的转移,显然得倒过来 DP,设 \(h_j\) 表示以 \(j\) 开头,考虑 \([j,i)\) 的本质不同子序列数,于是有:

\[h_j=\begin{cases} \begin{aligned} &0&a_j=a_i\\ &g_{i,j}&a_j\ne a_i \end{aligned} \end{cases}\\ g_{i,j}=\sum_{k=j+1}^{i}h_k-h_{nxt_k} \]

时间复杂度 \(O(n^2)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5,mod=1e9+7;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,a[maxn],pos[maxn],pre[maxn],suf[maxn];
int f[maxn],g[maxn][maxn],h[maxn];
int main(){
	freopen("nest.in","r",stdin);
	freopen("nest.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pre[i]=pos[a[i]];
		suf[pos[a[i]]]=i;
		suf[i]=n+1;
		pos[a[i]]=i;
	}
	for(int i=1;i<=n+1;i++){
		int sum=1;
		for(int j=i-1;~j;j--){
			g[i][j]=sum;
			h[j]=a[i]==a[j]?0:sum;
			add(sum,mns(h[j],h[suf[j]]));
		}
	}
	f[0]=1;
	for(int i=1;i<=n+1;i++){
		for(int j=0;j<i;j++){
			f[i]=(f[i]+mns(g[i][j],g[pre[i]][j])*1ll*f[j])%mod;
		}
	}
	cout<<f[n+1];
	return 0;
}
}
int main(){return asbt::main();}

D. presuffix

11.19 HZOJ NOIP2025模拟赛11

A B C D Sum Rank
100 13 16 - 129 13/35

A. 白露澈明之泉

考虑整个过程,必然是先将一行填满,再用这一行把所有列都填满。假设是第 \(i\) 行,则如果第 \(i\) 列有 \(1\),就用这个 \(1\) 所在的这一行将第 \(i\) 列填满;否则就先随便用一行在第 \(i\) 列产生出 \(1\),然后再重复上面的操作。考虑将第 \(i\) 行填满后,有 \(0\) 的列数应该是不变的,否则选择这一行一定不优。于是可以 \(O(n^2)\) 模拟。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e3+5,inf=1e9;
int n;
char a[maxn][maxn];
il bool chk1(int x){
	for(int i=1;i<=n;i++){
		if(a[i][x]=='1'||a[x][i]=='1'){
			return 1;
		}
	}
	return 0;
}
int main(){
	freopen("fountain.in","r",stdin);
	freopen("fountain.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>(a[i]+1);
	}
	int cnt=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[j][i]=='0'){
				cnt++;
				break;
			}
		}
	}
	int ans=inf;
	for(int i=1;i<=n;i++){
		if(!chk1(i)){
			continue;
		}
		bool flag=0;
		for(int j=1;j<=n;j++){
			if(a[j][i]=='1'){
				flag=1;
				break;
			}
		}
		int tmp=0;
		for(int j=1;j<=n;j++){
			if(a[i][j]=='0'){
				tmp++;
			}
		}
		if(flag){
			ans=min(ans,tmp+cnt);
		}else{
			ans=min(ans,tmp+cnt+1);
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 在空无一物的时光深处

正难则反,考虑倒着做,于是变为每次将当前要覆盖的区间中还没覆盖的部分覆盖掉。

显然任何时刻被染色的区域是一段连续区间。此时可以发现,有效的状态必然是这一次染出的当前的区间范围,故所处位置必然是区间左/右端点。

\(f_{i,j,0/1}\) 表示当前考虑了 \(n\sim i\) 这些颜色,染出来的区间长度为 \(j\),当前在左/右端点的答案。于是有转移(以左端点为例):

  • \(f_{i,j,0}\to f_{k,x,0}\),向左扩展了一部分。
  • \(f_{i,j,0}\to f_{k,x,1}\),向右扩展了一部分。

考虑转移的限制,即要求 \(i-1\sim k+1\) 这些颜色都没有超过 \(j\) 这段区间,而到 \(k\) 就直接超出去了。于是再做一个可行性 DP,\(g_{k,x}\) 表示从\(1\) 开始,从 \(i-1\) 染到 \(k\) 能否在不超过 \([1,j]\) 这段区间的前提下走到 \(x\)

然后考虑一些细节问题。首先是统计答案,实际上是容易的,对于 \((i,j)\),如果 \(\exist x,g_{1,x}=1\),则有 \((f_{i,j,0}+f_{i,j,1})\times(m-j+1)\to ans\)

然后考虑初始状态,为了以后能讨论转移,应该为 \(f_{n,c_n,0}=f_{n,c_n,1}=1\)。但是这样会出现问题,这两个状态实际上是重复计算了同一种情况。于是在 \(i=n\) 的时候特判一下,同一个 \(f_{k,x,0/1}\) 只转移一次就好了。

时间复杂度 \(O(n^4)\)\(n,m\) 同阶)。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int mod=998244353;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,m,a[155],f[155][155][2];
bool g[155][155],vis[155][155][2];
int main(){
	freopen("stare.in","r",stdin);
	freopen("stare.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int ans=0;
	f[n][a[n]][0]=f[n][a[n]][1]=1;
	for(int i=n;i;i--){
		for(int j=1;j<=m;j++){
			for(int x=i;x;x--){
				for(int y=1;y<=j;y++){
					g[x][y]=0;
				}
			}
			g[i][1]=1;
			for(int x=i-1;x;x--){
				for(int y=1;y<=j;y++){
					if(!g[x+1][y]){
						continue;
					}
					if(y>=a[x]){
						g[x][y-a[x]+1]=1;
					}
					if(y+a[x]-1<=j){
						g[x][y+a[x]-1]=1;
					}
				}
			}
			if(i==n){
				if(j==a[n]){
					for(int x=i-1;x;x--){
						for(int y=1;y<=j;y++){
							if(g[x+1][y]){
								if(a[x]>y&&a[x]-y+j<=m&&!vis[x][a[x]-y+j][0]){
									add(f[x][a[x]-y+j][0],f[i][j][0]);
									vis[x][a[x]-y+j][0]=1;
								}
								if(y+a[x]-1>j&&y+a[x]-1<=m&&!vis[x][y+a[x]-1][1]){
									add(f[x][y+a[x]-1][1],f[i][j][0]);
									vis[x][y+a[x]-1][1]=1;
								}
								if(y+a[x]-1>j&&y+a[x]-1<=m&&!vis[x][y+a[x]-1][0]){
									add(f[x][y+a[x]-1][0],f[i][j][1]);
									vis[x][y+a[x]-1][0]=1;
								}
								if(a[x]>y&&a[x]-y+j<=m&&!vis[x][a[x]-y+j][1]){
									add(f[x][a[x]-y+j][1],f[i][j][1]);
									vis[x][a[x]-y+j][1]=1;
								}
							}
						}
					}
					bool flag=0;
					for(int y=1;y<=j;y++){
						if(g[1][y]){
							flag=1;
							break;
						}
					}
					if(flag){
						ans=(ans+f[i][j][0]*1ll*(m-j+1))%mod;
					}
				}
			}else{
				for(int x=i-1;x;x--){
					for(int y=1;y<=j;y++){
						if(g[x+1][y]){
							if(a[x]>y&&a[x]-y+j<=m){
								add(f[x][a[x]-y+j][0],f[i][j][0]);
							}
							if(y+a[x]-1>j&&y+a[x]-1<=m){
								add(f[x][y+a[x]-1][1],f[i][j][0]);
							}
							if(y+a[x]-1>j&&y+a[x]-1<=m){
								add(f[x][y+a[x]-1][0],f[i][j][1]);
							}
							if(a[x]>y&&a[x]-y+j<=m){
								add(f[x][a[x]-y+j][1],f[i][j][1]);
							}
						}
					}
				}
				bool flag=0;
				for(int y=1;y<=j;y++){
					if(g[1][y]){
						flag=1;
						break;
					}
				}
				if(flag){
					ans=(ans+(f[i][j][0]+f[i][j][1])*1ll*(m-j+1))%mod;
				}
			}
//			cout<<f[i][j][0]<<' '<<f[i][j][1]<<'\n';
//			cout<<i<<' '<<j<<":\n";
//			for(int x=i;x;x--){
//				for(int y=1;y<=j;y++){
//					cout<<g[x][y]<<' ';
//				}
//				cout<<'\n';
//			}
		}
	}
//	for(int i=n;i;i--){
//		for(int j=1;j<=m;j++){
//			cout<<f[i][j][0]<<' ';
//		}
//		cout<<'\n';
//	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 最小字典序

考虑 DP。设 \(dp_i\) 表示 \(i\) 的后缀能构造出的字典序最小的序列,有:

\[dp_i=\min_{j=i}^{n}\{\operatorname*{bitor}_{k=i}^{j}\{a_k\}+dp_j\} \]

其中加法表示拼接。

首先这个东西空间就炸了。考虑倍增,对于每个位置 \(i\) 维护 \(nxt_{i,j}\)\(ha_{i,j}\) 表示 \(dp_i\) 的第 \(2^j\) 位的位置和这一段前缀的哈希值。因为对于 \(x<y\)\(\operatorname{bitor}_{k=i}^{x}\{a_k\}\) 必然 \(\le\operatorname{bitor}_{k=i}^{y}\{a_k\}\),所以维护一个 DP 值递增的单调栈,转移到 \(i\) 时在单调栈上二分出区间或等于 \(a_i\) 的最大位置即可。时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
using namespace std;
namespace asbt{
const int maxn=(1<<18)+5;
ull bas=13331;
int T,n,a[maxn],st[maxn][20],nxt[maxn][20],stk[maxn],top;
ull pw[maxn],ha[maxn][20];
il int query(int l,int r){
	int p=__lg(r-l+1);
	return st[l][p]|st[r-(1<<p)+1][p];
}
il int find(int i){
	int l=1,r=top;
	while(l<r){
		int mid=(l+r)>>1;
		if(query(i,stk[mid]-1)==a[i]){
			r=mid;
		}else{
			l=mid+1;
		}
	}
	return stk[l];
}
il bool cmp(int x,int y){
	for(int i=18;~i;i--){
		if(ha[x][i]==ha[y][i]){
			x=nxt[x][i],y=nxt[y][i];
		}
	}
	return ha[x][0]<=ha[y][0];
}
int main(){
	freopen("min.in","r",stdin);
	freopen("min.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	pw[0]=1;
	for(int i=1;i<=1<<18;i++){
		pw[i]=pw[i-1]*bas;
	}
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			st[i][0]=a[i];
		}
		for(int j=1;j<=18;j++){
			for(int i=1;i+(1<<j)-1<=n;i++){
				st[i][j]=st[i][j-1]|st[i+(1<<(j-1))][j-1];
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=0;j<=18;j++){
				nxt[i][j]=ha[i][j]=0;
			}
		}
		for(int i=0;i<=18;i++){
			nxt[n+1][i]=n+1,ha[n+1][i]=0;
		}
		top=0,stk[++top]=n+1;
		for(int i=n;i;i--){
			int x=find(i);
			nxt[i][0]=x;
			ha[i][0]=query(i,x-1)+1;
			for(int j=1;j<=18;j++){
				nxt[i][j]=nxt[nxt[i][j-1]][j-1];
				ha[i][j]=ha[i][j-1]*pw[1<<(j-1)]+ha[nxt[i][j-1]][j-1];
			}
			while(top&&cmp(i,stk[top])){
				top--;
			}
			stk[++top]=i;
		}
		int p=1;
		while(p<=n){
			cout<<query(p,nxt[p][0]-1)<<' ';
			p=nxt[p][0];
		}
		cout<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

D. /swɒp/

首先考虑如何计算 \(f\),手玩可以发现,每个位置最多交换一次。于是可以 DP,设 \(dp_{i,0/1}\) 表示考虑 \(1\sim i\)\(a_i\) 最终在/不在原位置被删除的答案,于是有:

\[dp_{i,0}=\min(dp_{i-1,0}+[a_i\ne a_{i-1}],dp_{i-1,1}+[a_i\ne a_{i-2}])\\[3mm] dp_{i,1}=\min(dp_{i-2,0}+[a_i\ne a_{i-1}]+[a_i\ne a_{i-2}],dp_{i-2,1}+[a_i\ne a_{i-1}]+[a_i\ne a_{i-3}])+1 \]

于是可以 DDP,用线段树维护广义矩阵乘。具体地,有:

\[\begin{bmatrix} dp_{i,0}&dp_{i,1}&dp_{i-1,0}&dp_{i-1,1} \end{bmatrix}=\begin{bmatrix} dp_{i-1,0}&dp_{i-1,1}&dp_{i-2,0}&dp_{i-2,1} \end{bmatrix}\times\begin{bmatrix} [a_i\ne a_{i-1}]&+\infty&0&+\infty\\ [a_i\ne a_{i-2}]&+\infty&+\infty&0\\ +\infty&[a_i\ne a_{i-1}]+[a_i\ne a_{i-2}]+1&+\infty&+\infty\\ +\infty&[a_i\ne a_{i-1}]+[a_i\ne a_{i-3}]+1&+\infty&+\infty \end{bmatrix} \]

考虑修改,区间加时,\([l+3,r]\) 的转移矩阵不用变,只用对 \(l,l+1,l+2,r+1,r+2,r+3\) 进行修改即可;区间赋值时,上面那些位置依然会改变,同时 \([l+3,r]\) 的转移矩阵会变成同一个矩阵,可以预处理出来它的幂次。因此我们需要开两棵线段树,一棵维护 \(a\) 数组,另一棵维护矩阵。

考虑查询,我们不能直接去查 \([l,r]\),因为 \([l,l+2]\) 的转移矩阵在这里显然是不适用的,所以我们直接将它们的 \(dp\) 值 DP 出来即可。

然后你就调吧你就。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5,inf=1e9;
int n,m,f[maxn][2];
struct{
	ll tr[maxn<<2],tag[maxn<<2];
	bool fp[maxn<<2];
	il void pushdown(int id){
		if(fp[id]){
			tag[lid]=tag[rid]=0;
			tr[lid]=tr[rid]=tr[id];
			fp[lid]=fp[rid]=1;
			fp[id]=0;
		}else if(tag[id]){
			tr[lid]+=tag[id],tr[rid]+=tag[id];
			if(!fp[lid]){
				tag[lid]+=tag[id];
			}
			if(!fp[rid]){
				tag[rid]+=tag[id];
			}
			tag[id]=0;
		}
	}
	il void build(int id,int l,int r){
		if(l==r){
			cin>>tr[id];
			return ;
		}
		int mid=(l+r)>>1;
		build(lid,l,mid);
		build(rid,mid+1,r);
	}
	il void add(int id,int L,int R,int l,int r,int x){
		if(L>=l&&R<=r){
			tr[id]+=x;
			if(!fp[id]){
				tag[id]+=x;
			}
			return ;
		}
		pushdown(id);
		int mid=(L+R)>>1;
		if(l<=mid){
			add(lid,L,mid,l,r,x);
		}
		if(r>mid){
			add(rid,mid+1,R,l,r,x);
		}
	}
	il void upd(int id,int L,int R,int l,int r,int x){
		if(L>=l&&R<=r){
			tag[id]=0,fp[id]=1,tr[id]=x;
			return ;
		}
		pushdown(id);
		int mid=(L+R)>>1;
		if(l<=mid){
			upd(lid,L,mid,l,r,x);
		}
		if(r>mid){
			upd(rid,mid+1,R,l,r,x);
		}
	}
	il ll query(int id,int l,int r,int p){
		if(l==r){
			return tr[id];
		}
		pushdown(id);
		int mid=(l+r)>>1;
		if(p<=mid){
			return query(lid,l,mid,p);
		}else{
			return query(rid,mid+1,r,p);
		}
	}
	il ll operator[](int x){
		return query(1,1,n,x);
	}
	il void debug(int id,int l,int r){
		cout<<id<<' '<<l<<' '<<r<<' '<<tr[id]<<' '<<tag[id]<<' '<<fp[id]<<'\n';
		if(l==r){
//			cout<<tr[id]<<' ';
			return ;
		}
//		pushdown(id);
		int mid=(l+r)>>1;
		debug(lid,l,mid);
		debug(rid,mid+1,r);
	}
}a;
il void dp(int l,int r){
	f[l-1][0]=0,f[l-1][1]=inf;
	for(int i=l;i<=r;i++){
		f[i][0]=min(f[i-1][0]+(i==l||a[i]!=a[i-1]),f[i-1][1]+(i<=l+1||a[i]!=a[i-2]));
		f[i][1]=i==l?inf:min(f[i-2][0]+(i==l||a[i]!=a[i-1])+(i<=l+1||a[i]!=a[i-2]),f[i-2][1]+(i==l||a[i]!=a[i-1])+(i<=l+2||a[i]!=a[i-3]))+1;
	}
}
struct juz{
	int a[4][4];
	juz(){
		memset(a,0x3f,sizeof(a));
	}
	il int*operator[](int x){
		return a[x];
	}
	il juz operator*(juz b)const{
		juz c;
		for(int i=0;i<=3;i++){
			for(int j=0;j<=3;j++){
				for(int k=0;k<=3;k++){
					c[i][j]=min(c[i][j],a[i][k]+b[k][j]);
				}
			}
		}
		return c;
	}
}pw[maxn],tr[maxn<<2];
bool tag[maxn<<2];
il void pushup(int id){
	tr[id]=tr[lid]*tr[rid];
}
il void pushtag(int id,int l,int r){
	tag[id]=1,tr[id]=pw[r-l+1];
}
il void pushdown(int id,int l,int r){
	if(tag[id]){
		int mid=(l+r)>>1;
		pushtag(lid,l,mid);
		pushtag(rid,mid+1,r);
		tag[id]=0;
	}
}
il void get(int id,int i){
	tr[id]=juz();
	ll a0=a[i],a1=a[i-1],a2=a[i-2],a3=a[i-3];
	tr[id][0][2]=tr[id][1][3]=0;
	tr[id][0][0]=(a0!=a1);
	tr[id][1][0]=(a0!=a2);
	tr[id][2][1]=(a0!=a1)+(a0!=a2)+1;
	tr[id][3][1]=(a0!=a1)+(a0!=a3)+1;
}
il void build(int id,int l,int r){
	if(l==r){
		get(id,l);
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
	pushup(id);
}
il void upd(int id,int l,int r,int p){
	if(l==r){
		get(id,p);
		return ;
	}
	pushdown(id,l,r);
	int mid=(l+r)>>1;
	if(p<=mid){
		upd(lid,l,mid,p);
	}else{
		upd(rid,mid+1,r,p);
	}
	pushup(id);
}
il void upd(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		pushtag(id,L,R);
		return ;
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(l<=mid){
		upd(lid,L,mid,l,r);
	}
	if(r>mid){
		upd(rid,mid+1,R,l,r);
	}
	pushup(id);
}
il juz query(int id,int L,int R,int l,int r){
	if(L>=l&&R<=r){
		return tr[id];
	}
	pushdown(id,L,R);
	int mid=(L+R)>>1;
	if(r<=mid){
		return query(lid,L,mid,l,r);
	}
	if(l>mid){
		return query(rid,mid+1,R,l,r);
	}
	return query(lid,L,mid,l,r)*query(rid,mid+1,R,l,r);
}
int main(){
	freopen("swap.in","r",stdin);
	freopen("swap.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	a.build(1,1,n);
	if(n<=3){
		while(m--){
			int opt,l,r,x;
			cin>>opt>>l>>r;
			if(opt==1){
				cin>>x;
				a.add(1,1,n,l,r,x);
			}else if(opt==2){
				cin>>x;
				a.upd(1,1,n,l,r,x);
			}else{
				dp(l,r);
				cout<<min(f[r][0],f[r][1])<<'\n';
			}
		}
		return 0;
	}
	pw[0][0][0]=pw[0][1][1]=pw[0][2][2]=pw[0][3][3]=0;
	pw[1][0][0]=pw[1][0][2]=pw[1][1][0]=pw[1][1][3]=0,pw[1][2][1]=pw[1][3][1]=1;
	for(int i=2;i<=n;i++){
		pw[i]=pw[i-1]*pw[1];
	}
	build(1,4,n);
	while(m--){
		int opt,l,r,x;
		cin>>opt>>l>>r;
		if(opt==1){
			cin>>x;
			a.add(1,1,n,l,r,x);
			if(r<=l+1){
				for(int i=max(l,4);i<=min(n,r+3);i++){
					upd(1,4,n,i);
				}
			}else{
				for(int i=max(l,4);i<=l+2;i++){
					upd(1,4,n,i);
				}
				for(int i=r+1;i<=min(n,r+3);i++){
					upd(1,4,n,i);
				}
			}
		}else if(opt==2){
			cin>>x;
			a.upd(1,1,n,l,r,x);
			if(r<l+3){
				for(int i=max(l,4);i<=min(n,r+3);i++){
					upd(1,4,n,i);
				}
			}else{
				upd(1,4,n,l+3,r);
				for(int i=max(l,4);i<=l+2;i++){
					upd(1,4,n,i);
				}
				for(int i=r+1;i<=min(n,r+3);i++){
					upd(1,4,n,i);
				}
			}
		}else{
			if(r<=l+2){
				dp(l,r);
				cout<<min(f[r][0],f[r][1])<<'\n';
			}else{
				dp(l,l+2);
				juz F;
				F[0][0]=f[l+2][0],F[0][1]=f[l+2][1];
				F[0][2]=f[l+1][0],F[0][3]=f[l+1][1];
				F=F*query(1,4,n,l+3,r);
				cout<<min(F[0][0],F[0][1])<<'\n';
			}
		}
//		a.debug(1,1,n);
//		if(m%3==0){
//			for(int i=1;i<=n;i++){
//				cout<<a[i]<<' ';
//			}
//			cout<<'\n';
//		}
	}
	return 0;
}
}
int main(){return asbt::main();}
/*
10 4939
1 1 4 5 1 4 5 2 3 9
1 1 10 0
1 2 5 1
2 3 5 2
1 2 6 1
*/
posted @ 2025-11-13 17:57  zhangxy__hp  阅读(55)  评论(0)    收藏  举报