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

image

主打一个听劝。(难蚌

2025CSP-S模拟赛64

A B C D Sum Rank
50 0 0 - 50 7/7

挂 155pts,挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂挂。

意难平,遂写场上做法。

A. 小 Z 爱计数

首先按照时间排序。对于两个数对 \((a,b)\)\((a',b')\),令 \(a<a'\),则合法当且仅当满足一下条件之一:

  • 有归零操作,\(|b'|+1\le a'-a\)
  • 无归零操作,\(|b-b'|\le a'-a\land a'-a-|b-b'|\equiv0\pmod2\)

注意 \((a_1,b_1)\) 需要与 \((0,0)\) 判断。怒挂 50pts。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int T,n,m;
struct node{
	int t,v;
	node(int t=0,int v=0):t(t),v(v){}
	il bool operator<(const node &x)const{
		return t<x.t;
	}
}a[maxn];
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>m>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i].t>>a[i].v;
		}
		a[++n]=node();
		sort(a+1,a+n+1);
		for(int i=1;i<n;i++){
			int x=a[i].v,y=a[i+1].v,z=a[i+1].t-a[i].t;
			if(abs(y)+1<=z||abs(x-y)<=z&&(z-abs(x-y))%2==0);
			else{
				cout<<"No\n";
				goto togo;
			}
		}
		cout<<"Yes\n";
		togo:;
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 小 Z 爱划分

首先考虑一个 \(O(n^2)\) 的 DP:设 \(f_i\) 表示 \(1\)\(i\) 的所有划分的和,于是有 \(f_i=\sum_{j=0}^{i-1}f_j\times(pre_i\oplus pre_j)^2\)。能拿 20pts。

对于 \(a\le255\) 的部分分,注意到值域很小,因此把所有 \(pre_j\) 全都插入字典树,在叶子节点上累加答案即可。时间复杂度 \(O(nV)\),20pts。

场上只有 T2 需要 freopen,我直接没看见,怒挂 40pts。

正解考虑对每两位计算答案,因为 \((c_1+c_2+\dots+c_k)^2=\sum_{i=1}^{k}\sum_{j=1}^{k}c_ic_j\),所以对于第 \(x\) 位和第 \(y\) 位,\(f_j\) 能对 \(f_i\) 产生贡献当且仅当这两位都不相同,贡献为 \(f_j\times2^{x+y}\)。于是记 \(sum_{j,x,k,y}\) 表示 \(pre\) 的第 \(j\) 位为 \(x\),第 \(k\) 位为 \(y\)\(f\) 之和即可线性对数方完成。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+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 T,n,a[maxn],f[maxn],sum[37][2][37][2],_2[67];
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	_2[0]=1;
	for(int i=1;i<=60;i++){
		_2[i]=pls(_2[i-1],_2[i-1]);
	}
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			a[i]^=a[i-1];
		}
		f[0]=1;
		memset(sum,0,sizeof(sum));
		for(int i=0;i<=30;i++){
			for(int j=0;j<=30;j++){
				sum[i][0][j][0]=1;
			}
		}
		for(int i=1;i<=n;i++){
			f[i]=0;
			for(int j=0;j<=30;j++){
				for(int k=0;k<=30;k++){
					int x=a[i]>>j&1,y=a[i]>>k&1;
					f[i]=(sum[j][x^1][k][y^1]*1ll*_2[j+k]+f[i])%mod;
				}
			}
			for(int j=0;j<=30;j++){
				for(int k=0;k<=30;k++){
					int x=a[i]>>j&1,y=a[i]>>k&1;
					add(sum[j][x][k][y],f[i]);
				}
			}
		}
		cout<<f[n]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 小 Z 爱优化

首先考虑值域较小的部分分,考虑可行性 DP,设 \(f_{i,j,k}\) 表示考虑了 \(1\)\(i\),最大值为 \(j\),最小值为 \(k\) 是否可行。假设现在加入了 \(t\),前面的最大值为 \(j\),于是 \(f_{i,\max(j,t)}\) 的 01 序列可以或上 \(f_{i-1/i-2,j}\) 的第 \(1\)\(t\) 位,然后如果 \(f_{i-1/i-2,j}\) 的第 \(t+1\)\(V\) 位有 \(1\) 那么 \(f_{i,\max(j,t),t}\leftarrow1\)。能拿 20pts。

在这个做法的基础上离散化一下能再拿 \(n\le30\) 的 20pts。bitset 优化一下能拿 \(n\le1000\) 的 25pts。

卡空间失败,怒挂 65pts。

考虑正解。当前这一坨东西已然难以优化,不妨换个思路(

枚举最小值 \(c\),强制要求所有组都 \(\ge c\)。设 \(f_i\) 表示考虑 \(1\)\(i\),最大值最小为多少。于是有转移:

\[f_i=\min(\max(f_{i-1},a_i),\max(f_{i-2},a_i+a_{i-1})) \]

当然 \(a_i\ge c\) 才能进行第一种转移,\(a_i+a_{i-1}\ge c\) 才能进行第二种转移。这东西写成矩阵是满足结合律的,于是考虑 DDP,有:

\[\begin{bmatrix} f_i&f_{i-1} \end{bmatrix} =\begin{bmatrix} f_{i-1}&f_{i-2} \end{bmatrix} \times\begin{bmatrix} a_i&0\\ a_i+a_{i-1}&+\infty \end{bmatrix} \]

用线段树维护,\(c\) 变化时将对应位置的矩阵更改一下即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=2e5+5,inf=1e18;
int T,n,a[maxn];
struct node{
	int val,pos;
	node(int val=0,int pos=0):val(val),pos(pos){}
	il bool operator<(const node &x)const{
		return val<x.val||val==x.val&&pos<x.pos;
	}
}b[maxn<<1];
struct juz{
	int a[2][2];
	il int*operator[](int x){
		return a[x];
	}
	il juz operator*(juz x)const{
		juz res;
		for(int i:{0,1}){
			for(int j:{0,1}){
				res[i][j]=min(max(a[i][0],x[0][j]),max(a[i][1],x[1][j]));
			}
		}
		return res;
	}
}tr[maxn<<2];
il void pushup(int id){
	tr[id]=tr[lid]*tr[rid];
}
il void build(int id,int l,int r){
	if(l==r){
		tr[id][0][0]=a[l],tr[id][0][1]=0;
		tr[id][1][0]=a[l]+a[l-1],tr[id][1][1]=inf;
		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 v){
	if(l==r){
		if(tr[id][0][0]==v){
			tr[id][0][0]=inf;
		}
		if(tr[id][1][0]==v){
			tr[id][1][0]=inf;
		}
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		upd(lid,l,mid,p,v);
	}else{
		upd(rid,mid+1,r,p,v);
	}
	pushup(id);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n;
		int tot=0;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			b[++tot]=node(a[i],i);
			b[++tot]=node(a[i]+a[i-1],i);
		}
		sort(b+1,b+tot+1);
		build(1,1,n);
		int ans=min(tr[1][0][0],tr[1][1][0])-b[1].val;
//		cout<<b[1].val<<' '<<min(tr[1][0][0],tr[1][1][0])<<'\n';
		for(int i=1;i<tot;i++){
			upd(1,1,n,b[i].pos,b[i].val);
//			cout<<"P "<<b[i].pos<<' '<<b[i].val<<'\n';
			if(b[i].val!=b[i+1].val){
				ans=min(ans,min(tr[1][0][0],tr[1][1][0])-b[i+1].val);
//				cout<<"Q "<<b[i+1].val<<' '<<min(tr[1][0][0],tr[1][1][0])<<'\n';
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

D. 小 Z 爱考试

容易发现有些点必然会加上 \(w_i\),有些点必然不会加。具体地,可将点分为三类:

  • \(a_i<a_{b_i}\),必然加上 \(w_i\),称为一类点
  • \(a_i\ge a_{b_i}+w_{b_i}\),必然不会加上 \(w_i\),称为二类点
  • \(a_{b_i}\le a_i<a_{b_i}+w_{b_i}\),需要通过 \(b_i\) 确定加不加,称为三类点

于是哥们连边 \(i\to b_i\),得到一个基环树森林。询问 \(u\) 点的答案时,就不断在树上跑,直到跑到一个一类点或二类点停下。情况分为三类:

  • 遇到了一类点。设一路上跑过的点为 \(p_1,p_2,\dots,p_k\),那么只有这些点以 \(p_k,p_{k-1},\dots,p_1\) 的顺序发生时答案为 \(a_u+w_u\)。这种情况的概率为 \(\frac{{n\choose k}(n-k)!}{n!}=\frac{1}{k!}\)
  • 遇到了二类点。因为二类点不会加,所以这一路上的点都不会加。
  • 跑到了环里,全是三类点,还是不会加。

于是哥们只需要求在基环树上从 \(u\) 开始跑到的第一个一类或二类点即可。不妨将它们统称为黑点,三类点为白点。由于需要动态修改,考虑用数据结构维护。将基环树分为树和环两部分。首先是树上的部分,考虑重链剖分,对每个重链维护一个存储所有黑点的 set,哥们要求的就是在这条链上第一个深度比 \(u\) 小的黑点,若没有则一直往上跳到根。

如果子树上没有,那么就在环上找。还是给环开一个 set,给环上的点标号,哥们要找的就是第一个编号大于 \(u\) 的黑点。可能要绕一圈绕回来,断环为链,将整个环存两遍即可。

对于修改,只会影响 \(u\) 和所有 \(b_i=u\)\(i\) 的类型。\(i\) 只有 \(3\) 个,暴力修改即可。时间复杂度 \(O(n\log^2n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lwrb lower_bound
#define uprb upper_bound
using namespace std;
namespace asbt{
const int maxn=2e5+5,mod=1e9+7;
int T,n,m,a[maxn],b[maxn],c[maxn],d[maxn];
int cid[maxn],ccnt,crn[maxn],chid[maxn],chnt;
int dep[maxn],sz[maxn],hes[maxn],top[maxn];
int inv[maxn],len[maxn],fa[maxn];
bool bla[maxn];
vector<int> e[maxn];
queue<int> q;
struct node{
	int k,u;
	node(int k=0,int u=0):k(k),u(u){}
	il bool operator<(const node &x)const{
		return k<x.k;
	}
};
set<node> cir[maxn],chn[maxn];
il void dfs1(int u){
	sz[u]=1;
	int mxs=0;
	for(int v:e[u]){
		if(d[v]){
			continue;
		}
		dep[v]=dep[u]+1;
		fa[v]=u;
		dfs1(v);
		sz[u]+=sz[v];
		if(mxs<sz[v]){
			mxs=sz[v],hes[u]=v;
		}
	}
}
il void dfs2(int u){
	if(!chid[u]){
		chid[u]=++chnt;
		top[u]=u;
	}
	if(bla[u]){
		chn[chid[u]].insert(node(dep[u],u));
	}
	if(hes[u]){
		chid[hes[u]]=chid[u];
		top[hes[u]]=top[u];
		dfs2(hes[u]);
	}
	for(int v:e[u]){
		if(d[v]||v==hes[u]){
			continue;
		}
		dfs2(v);
	}
}
il pii query(int u){
	int uu=u;
	while(dep[u]){
		auto it=chn[chid[u]].uprb(node(dep[u],u));
		if(it==chn[chid[u]].begin()){
			if(dep[top[u]]){
				u=fa[top[u]];
			}else{
				u=top[u];
			}
		}else{
			it=prev(it);
			return mp(dep[uu]-it->k+1,it->u);
		}
	}
	auto it=cir[cid[u]].lwrb(node(crn[u],u));
	if(it==cir[cid[u]].end()){
		return mp(-1,-1);
	}else{
		return mp(it->k-crn[u]+dep[uu]+1,it->u);
	}
}
il void solve(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i]>>c[i];
		d[b[i]]++;
		e[b[i]].pb(i);
	}
	for(int i=1;i<=n;i++){
		if(d[i]==0){
			q.push(i);
		}
		if(a[i]<a[b[i]]||a[i]>=a[b[i]]+c[b[i]]){
			bla[i]=1;
		}
	}
	while(q.size()){
		int u=q.front();
		q.pop();
		if(--d[b[u]]==0){
			q.push(b[u]);
		}
	}
	for(int i=1;i<=n;i++){
		if(d[i]&&!cid[i]){
			ccnt++;
			int u=i;
			do{
				cid[u]=ccnt,crn[u]=++len[ccnt];
				if(bla[u]){
					cir[ccnt].insert(node(crn[u],u));
				}
				dfs1(u),dfs2(u);
				u=b[u];
			}while(u!=i);
			do{
				if(bla[u]){
					cir[ccnt].insert(node(len[ccnt]+crn[u],u));
				}
				u=b[u];
			}while(u!=i);
		}
	}
//	for(int i=1;i<=n;i++){
//		cout<<i<<' '<<cid[i]<<' '<<crn[i]<<' '<<chid[i]<<' '<<dep[i]<<'\n';
//	}
	while(m--){
		int opt,u,cc;
		cin>>opt>>u;
		if(opt==3){
			pii x=query(u);
			int v=x.sec,len=x.fir;
//			cout<<v<<' '<<len<<'\n';
			if(~v&&a[v]<a[b[v]]){
				cout<<(inv[len]*1ll*(a[u]+c[u])+(1-inv[len]+mod)*1ll*a[u])%mod<<'\n';
			}else{
				cout<<a[u]<<'\n';
			}
		}else{
			cin>>cc;
			(opt==1?a:c)[u]=cc;
			bool t=a[u]<a[b[u]]||a[u]>=a[b[u]]+c[b[u]];
//			cout<<bla[u]<<' '<<t<<'\n';
			if(bla[u]&&!t){
				chn[chid[u]].erase(node(dep[u],u));
				if(cid[u]){
					cir[cid[u]].erase(node(crn[u],u));
					cir[cid[u]].erase(node(len[cid[u]]+crn[u],u));
				}
			}else if(!bla[u]&&t){
//				puts("666");
				chn[chid[u]].insert(node(dep[u],u));
				if(cid[u]){
					cir[cid[u]].insert(node(crn[u],u));
					cir[cid[u]].insert(node(len[cid[u]]+crn[u],u));
				}
			}
			bla[u]=t;
			for(int v:e[u]){
				bool t=a[v]<a[b[v]]||a[v]>=a[b[v]]+c[b[v]];
				if(bla[v]&&!t){
					chn[chid[v]].erase(node(dep[v],v));
					if(cid[v]){
						cir[cid[v]].erase(node(crn[v],v));
						cir[cid[v]].erase(node(len[cid[v]]+crn[v],v));
					}
				}else if(!bla[v]&&t){
					chn[chid[v]].insert(node(dep[v],v));
					if(cid[v]){
						cir[cid[v]].insert(node(crn[v],v));
						cir[cid[v]].insert(node(len[cid[v]]+crn[v],v));
					}
				}
				bla[v]=t;
			}
		}
	}
	for(int i=1;i<=n;i++){
		a[i]=b[i]=c[i]=d[i]=0;
		cid[i]=crn[i]=chid[i]=0;
		dep[i]=sz[i]=hes[i]=top[i]=0;
		len[i]=fa[i]=bla[i]=0;
		e[i].clear();
	}
	for(int i=1;i<=ccnt;i++){
		cir[i].clear();
	}
	for(int i=1;i<=chnt;i++){
		chn[i].clear();
	}
	ccnt=chnt=0;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	inv[0]=inv[1]=1;
	for(int i=2;i<=2e5;i++){
		inv[i]=(mod-mod/i)*1ll*inv[mod%i]%mod;
	}
	for(int i=2;i<=2e5;i++){
		inv[i]=inv[i-1]*1ll*inv[i]%mod;
	}
	cin>>T;
	while(T--){
		solve();
	}
	return 0;
}
}
int main(){return asbt::main();}

HZOJ CSP-S模拟33

A B C D Sum Rank
100 100 35 45 280 5/27

A. Divisors

\(m\) 很小,直接将每个 \(a\) 的贡献加给它的所有约数即可,用 map 存每个数目前的倍数个数。时间复杂度 \(O(m\sqrt{a}\log n)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int n,m,b[205];
map<int,int> c;
int main(){
	freopen("div.in","r",stdin);
	freopen("div.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>m>>n;
	b[0]=m;
	for(int i=1,x;i<=n;i++){
		cin>>x;
		for(int j=1;j<=x/j;j++){
			if(x%j==0){
				if(j<=m){
					b[c[j]]--,b[++c[j]]++;
				}
				if(j!=x/j&&x/j<=m){
					b[c[x/j]]--,b[++c[x/j]]++;
				}
			}
		}
	}
	for(int i=0;i<=n;i++){
		cout<<b[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

B. Market

首先将时间维扫描线扫掉。此时一个直观的做法是每加入一个物品就用它更新背包 DP,时间复杂度 \(O(n^2c+m)\)。注意到 \(c\) 很大而 \(v\) 很小,交换定义域和值域即可,查询时二分一下,时间复杂度 \(O(n^2v+m\log nv)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define uprb upper_bound
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,m,ans[maxn];
ll f[maxn];
struct node{
	int c,v,t,typ;
	node(int c=0,int v=0,int t=0,int typ=0):c(c),v(v),t(t),typ(typ){}
	il bool operator<(const node &x)const{
		return t<x.t||t==x.t&&typ<x.typ;
	}
}a[maxn];
int main(){
	freopen("market.in","r",stdin);
	freopen("market.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	int tot=0;
	for(int i=1,c,v,t;i<=n;i++){
		cin>>c>>v>>t;
		a[++tot]=node(c,v,t);
	}
	for(int i=1,t,v;i<=m;i++){
		cin>>t>>v;
		a[++tot]=node(i,v,t,1);
	}
	sort(a+1,a+tot+1);
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int i=1,c,v;i<=tot;i++){
		c=a[i].c,v=a[i].v;
		if(a[i].typ){
			ans[c]=uprb(f+1,f+60001,v)-f-1;
		}else{
			for(int j=6e4;j>=v;j--){
				f[j]=min(f[j],f[j-v]+c);
			}
			for(int j=6e4;j;j--){
				f[j]=min(f[j],f[j+1]);
			}
		}
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}
/*
5 2
5 5 4
1 3 1
3 4 3
6 2 2
4 3 2
3 8
5 9
*/

C. 连通性

\(1\)\(n-m\) 为蓝点,\(n-m+1\)\(n\) 为红点。

首先考虑合法的连通块的形态。不难发现只有以下两种:

  1. 若干个红点组成的一个团。
  2. 若干个蓝点组成的一个连通块,伸出去若干条边连着若干个红点,红点之间又连了若干条边。

首先考虑第一部分。设 \(g_i\) 表示 \(i\) 个点形成若干个团的方案数。枚举最后一个团有 \(j\) 个点,为了避免算重,强制选择编号最小的点,即 \(i-1\choose j-1\),有:

\[g_i=\sum_{j=1}^{i}g_j{i-1\choose j-1} \]

然后是第二部分。设 \(dp_{i,j}\) 表示由 \(i\) 个蓝点、\(j\) 个红点组成若干个连通块的方案数,于是有:

\[dp_{i,j}=\sum_{k=1}^{i}\sum_{l=0}^{j}dp_{i-k,j-l}{i-1\choose k-1}{j\choose l}f_k2^{\frac{l(l-1)}{2}}(2^k-1)^l \]

其中 \(f_i\) 表示 \(i\) 个点构成一个连通块的方案数,考虑它的转移,不妨令 \(1\)\(i-1\) 构成若干个连通块,再分别与 \(i\) 连边。记 \(h_j\) 表示此时已经有 \(j\) 个点与 \(i\) 连通,有:

\[h_j=\sum_{k=1}^{j}h_{j-k}f_k{i-j+k-2\choose k-1}(2^k-1) \]

\(f_i\) 就是 \(h_{i-1}\)

于是哥们可以求出答案:

\[ans=\sum_{i=0}^{m}{m\choose i}g_idp_{n-m,m-i} \]

时间复杂度 \(O(n^4+Tn)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int mod=1e9+7;
int T,n,m,dp[105][105],f[105],h[105],g[105];
int C[105][105],_2[10005],_2_[105][105];
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int main(){
	freopen("connect.in","r",stdin);
	freopen("connect.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	C[0][0]=1;
	for(int i=1;i<=100;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++){
			C[i][j]=pls(C[i-1][j-1],C[i-1][j]);
		}
	}
	_2[0]=1;
	for(int i=1;i<=1e4;i++){
		_2[i]=pls(_2[i-1],_2[i-1]);
	}
	for(int i=0;i<=100;i++){
		_2_[i][0]=1;
		for(int j=1;j<=100;j++){
			_2_[i][j]=_2_[i][j-1]*1ll*(_2[i]-1+mod)%mod;
		}
	}
	f[1]=1;
	for(int i=2;i<=100;i++){
		h[0]=1;
		for(int j=1;j<i;j++){
			h[j]=0;
			for(int k=1;k<=j;k++){
				h[j]=(h[j-k]*1ll*f[k]%mod*C[i-j+k-2][k-1]%mod*(_2[k]-1+mod)+h[j])%mod;
			}
		}
		f[i]=h[i-1];
	}
	g[0]=1;
	for(int i=1;i<=100;i++){
		for(int j=1;j<=i;j++){
			g[i]=(g[i-j]*1ll*C[i-1][j-1]+g[i])%mod;
		}
	}
	dp[0][0]=1;
	for(int i=1;i<=100;i++){
		for(int j=0;j<=100;j++){
			for(int k=1;k<=i;k++){
				for(int l=0;l<=j;l++){
					dp[i][j]=(dp[i-k][j-l]*1ll*C[i-1][k-1]%mod*C[j][l]%mod*f[k]%mod*_2[l*(l-1)>>1]%mod*_2_[k][l]+dp[i][j])%mod;
				}
			}
		}
	}
	cin>>T;
	while(T--){
		cin>>n>>m;
		int ans=0;
		for(int i=0;i<=m;i++){
			ans=(C[m][i]*1ll*g[i]%mod*dp[n-m][m-i]+ans)%mod;
		}
		cout<<ans<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

D. 树

首先可以想到将路径上的边全都缩到一块,最后有多少个连通块就是 \(2\) 的几次幂。这可以直接用并查集维护当前链最顶端的边实现。关键在于判无解。对每条边设向上和向下两种状态,对于路径的 lca,经过它的这两条边方向必定相反,而对于路径上的其他点,经过它的这两条边方向必定相同,种类并查集即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=3e5+5,mod=1e9+7;
int n,m,fa[maxn],dep[maxn],anc[maxn][20];
vector<int> e[maxn];
struct{
	int u,v;
}a[maxn];
struct{
	int fa[maxn<<1];
	il void build(int n){
		for(int i=1;i<=n;i++){
			fa[i]=i;
		}
	}
	il int find(int x){
		return x!=fa[x]?fa[x]=find(fa[x]):x;
	}
	il void merge(int u,int v){
		fa[find(u)]=find(v);
	}
	il bool check(int u,int v){
		return find(u)==find(v);
	}
	il bool check(int x){
		return x==find(x);
	}
}D1,D2;
il void dfs(int u){
	for(int v:e[u]){
		if(v==fa[u]){
			continue;
		}
		fa[v]=u,dep[v]=dep[u]+1;
		anc[v][0]=u;
		for(int i=1;i<=18;i++){
			anc[v][i]=anc[anc[v][i-1]][i-1];
		}
		dfs(v);
	}
}
il int lca(int u,int v){
	if(dep[u]<dep[v]){
		swap(u,v);
	}
	int ddp=dep[u]-dep[v],t=0;
	while(ddp){
		if(ddp&1){
			u=anc[u][t];
		}
		t++,ddp>>=1;
	}
	if(u==v){
		return u;
	}
	for(int i=18;~i;i--){
		if(anc[u][i]!=anc[v][i]){
			u=anc[u][i],v=anc[v][i];
		}
	}
	return anc[u][0];
}
il int kth(int u,int k){
	int t=0;
	while(k){
		if(k&1){
			u=anc[u][t];
		}
		k>>=1,t++;
	}
	return u;
}
int main(){
//	freopen("my.out","w",stdout);
	freopen("usmjer.in","r",stdin);
	freopen("usmjer.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[u].pb(v),e[v].pb(u);
	}
	dfs(1),D1.build(n),D2.build(n<<1);
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		a[i]={u,v};
		int x=lca(u,v);
		if(u!=x&&v!=x){
			int uu=kth(u,dep[u]-dep[x]-1);
			int vv=kth(v,dep[v]-dep[x]-1);
			D2.merge(uu,vv+n);
			D2.merge(uu+n,vv);
		}
		while(dep[D1.find(u)]>dep[x]+1){
			u=D1.find(u);
//			cout<<u<<'\n';
			D1.merge(u,fa[u]);
			D2.merge(u,fa[u]);
			D2.merge(u+n,fa[u]+n);
		}
		while(dep[D1.find(v)]>dep[x]+1){
			v=D1.find(v);
//			cout<<v<<'\n';
			D1.merge(v,fa[v]);
			D2.merge(v,fa[v]);
			D2.merge(v+n,fa[v]+n);
		}
	}
//	puts("666");
	for(int i=1,u,v,x;i<=m;i++){
//		cout<<i<<'\n';
		u=a[i].u,v=a[i].v,x=lca(u,v);
//		cout<<u<<' '<<v<<' '<<x<<' ';
		if(u!=x&&v!=x){
			u=kth(u,dep[u]-dep[x]-1);
			v=kth(v,dep[v]-dep[x]-1);
	//		cout<<u<<' '<<v<<'\n';
			D1.merge(u,v);
		}
	}
//	puts("666");
	int ans=1;
	for(int i=2;i<=n;i++){
		if(D2.check(i,i+n)){
			cout<<0;
			return 0;
		}
		if(D1.check(i)){
			(ans<<=1)%=mod;
		}
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

2025CSP-S模拟赛65(HZOJ CSP-S模拟34)

A B C D Sum Rank
100 17 30 10 157 11/16(13/25)

A. 最长不下降子序列

首先,哥们翻转的必然是一段形如 \(222\dots2111\dots1222\dots2111\dots1\dots\dots222\dots2111\dots1\) 的一段,而最终的 LIS 必然是这一段前面的所有 \(1\)、这一段前半部分的 \(2\)、这一段后半部分的 \(1\) 和这一段后面的所有 \(2\)。这可以用调整法证明。于是哥们获得了一个 \(O(n^3)\) 做法:枚举这一段的左端点 \(l\) 和右端点 \(r\),再枚举中间 \(1\)\(2\) 的分界点 \(k\)。哥们不妨把连续的相同的数字缩到一块,假设有 \(cnt\) 段,设 \(f1_i\) 表示第 \(i\) 段及之前有多少个 \(1\)\(f2_i\) 表示第 \(i\) 段及之前有多少个 \(2\),于是这一段的答案为:

\[f1_l+(f2_k-f2_{l-1})+(f1_r-f1_k)+(f2_{cnt}-f2_r) \]

于是可以考虑那个经典方法,移动右端点,用线段树维护所有左端点的答案。用单调栈维护 \(f2_k-f1_k\) 的最大值的变化即可。

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=1e6+5,inf=1e9;
int n,a[maxn],f1[maxn],f2[maxn];
il int calc(){
	int m1=0,m2=0;
	for(int i=1;i<=n;i++){
		if(a[i]==1){
			m1++;
		}else{
			m2=max(m1,m2)+1;
		}
	}
	return max(m1,m2);
}
struct{
	int typ,len;
}b[maxn];
int tr[maxn<<2],tag[maxn<<2],stk[maxn],top;
il void pushup(int id){
	tr[id]=max(tr[lid],tr[rid]);
}
il void pushtag(int id,int v){
	tag[id]+=v,tr[id]+=v;
}
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]=b[l].typ==2?0:-inf;
		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 v){
	if(L>=l&&R<=r){
		pushtag(id,v);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r,v);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r,v);
	}
	pushup(id);
}
int main(){
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	int ans=calc(),cnt=0;
	for(int i=1;i<=n;i++){
		int j=i;
		while(a[j+1]==a[i]){
			j++;
		}
		b[++cnt]={a[i],j-i+1};
		i=j;
	}
	for(int i=1;i<=cnt;i++){
		f1[i]=f1[i-1],f2[i]=f2[i-1];
		if(b[i].typ==1){
			f1[i]+=b[i].len;
		}else{
			f2[i]+=b[i].len;
		}
	}
	build(1,1,cnt);
	for(int i=b[1].typ==1?3:2;i<=cnt;i+=2){
		add(1,1,cnt,i-1,i-1,f2[i-1]-f2[i-2]+f2[cnt]);
		while(top&&f2[i-1]-f1[i-1]>f2[stk[top]]-f1[stk[top]]){
			add(1,1,cnt,stk[top-1]+1,stk[top],f2[i-1]-f1[i-1]-f2[stk[top]]+f1[stk[top]]);
			top--;
		}
		stk[++top]=i-1;
		ans=max(ans,f1[i]-f2[i]+tr[1]);
//		cout<<i<<' '<<f1[i]-f2[i]+tr[1]<<'\n';
	}
//	for(int i=b[1].typ==1?2:1;i<=cnt;i+=2){
//		for(int j=i+1;j<=cnt;j+=2){
//			int res=0;
//			for(int k=i;k<j;k+=2){
//				res=max(res,f2[k]-f2[i-1]+f1[j]-f1[k]);
//			}
//			ans=max(ans,res+f1[i]+f2[cnt]-f2[j]);
//		}
//	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 美食节

首先考虑无解情况,就是众数的出现次数 \(>\lceil\frac{n}{2}\rceil\)。然后一位一位贪心。首先选择与上一个数颜色不同的位置,考察选了它之后后面是否合法,若不合法则选择当前的众数即可。用 set 维护,时间复杂度线性对数。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=3e5+5;
int n,a[maxn],b[maxn];
set<int> st[maxn];
set<pii> s1,s2;
il void insert(int i){
	if(b[a[i]]){
		s2.erase(mp(b[a[i]],a[i]));
		s1.erase(mp(*st[a[i]].begin(),a[i]));
	}
	st[a[i]].insert(i);
	b[a[i]]++;
	s1.insert(mp(*st[a[i]].begin(),a[i]));
	s2.insert(mp(b[a[i]],a[i]));
}
il void erase(int i){
	s1.erase(mp(*st[a[i]].begin(),a[i]));
	s2.erase(mp(b[a[i]],a[i]));
	st[a[i]].erase(i);
	b[a[i]]--;
	if(b[a[i]]){
		s1.insert(mp(*st[a[i]].begin(),a[i]));
		s2.insert(mp(b[a[i]],a[i]));
	}
}
int main(){
	freopen("food.in","r",stdin);
	freopen("food.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		insert(i);
	}
	if(s2.rbegin()->fir>(n+1)>>1){
		cout<<-1;
		return 0;
	}
	for(int i=n-1,j=0;~i;i--){
		int x=s1.begin()->fir;
		if(a[x]==j){
			x=next(s1.begin())->fir;
		}
		erase(x);
		if(s2.size()&&s2.rbegin()->fir>(i+1)>>1){
			insert(x);
//			cout<<'|';
			x=*st[s2.rbegin()->sec].begin();
			erase(x);
		}
		j=a[x];
		cout<<x<<' ';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 字符串

考虑枚举对称中心 \(i\)。只考虑 \(i\)\(s\) 中的情况,在 \(t\) 中同理。

首先马拉车出最大回文半径。如果在 \(s\) 的范围内,那么直接给答案贡献即可。否则需要考虑和 \(t\) 拼接。考虑最大回文子串的左端点 \(l\),哥们要做的就是将 \(s[1\dots l-1]\) 对所有 \(t\) 进行匹配,对 \(t\) 建 trie,树上差分即可。但时间复杂度不能承受。于是将 trie 的所有前缀路径插入哈希表,哈希二分出匹配的最大长度即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
using namespace std;
namespace asbt{
const int maxn=1e5+5,maxm=2e6+5;
const ull bas=13331,mod=1999993;
int T,n,m,tr[maxm][26],sz[maxm],cnt[maxm],d[maxm],hd[maxm],hnm;
ll ans;
ull pw[maxm],ha[maxm];
string a[maxn],b[maxn];
struct{
	int id,nxt;
	ull val;
}hs[maxm];
il void insert(ull val,int id){
	int p=val%mod;
	hs[++hnm]={id,hd[p],val};
	hd[p]=hnm;
}
il bool check(ull val){
	int p=val%mod;
	for(int i=hd[p];i;i=hs[i].nxt){
		if(hs[i].val==val){
			return 1;
		}
	}
	return 0;
}
il void add(ull val){
	int p=val%mod;
	for(int i=hd[p];i;i=hs[i].nxt){
		if(hs[i].val==val){
			cnt[hs[i].id]++;
			return ;
		}
	}
}
il void dfs1(int u,ull val){
	for(int i=0;i<=25;i++){
		if(tr[u][i]){
			insert(val*bas+i+'a',tr[u][i]);
			dfs1(tr[u][i],val*bas+i+'a');
		}
	}
}
il void dfs2(int u){
	for(int i=0;i<=25;i++){
		if(tr[u][i]){
			dfs2(tr[u][i]);
			cnt[u]+=cnt[tr[u][i]];
		}
	}
	ans+=cnt[u]*1ll*sz[u];
}
il void manacher(string s,int n){
	int l=0,r=-1;
	for(int i=0;i<n;i++){
		if(i>r){
			int &j=d[i]=1;
			while(i-j>=0&&i+j<n&&s[i-j]==s[i+j]){
				j++;
			}
			if(r<i+j-1){
				r=i+j-1,l=i-j+1;
			}
		}else{
			int j=l+r-i,&k=d[i];
			if(j-d[j]+1<=l){
				k=r-i+1;
				while(i-k>=0&&i+k<n&&s[i-k]==s[i+k]){
					k++;
				}
			}else{
				k=d[j];
			}
			if(r<i+k-1){
				r=i+k-1,l=i-k+1;
			}
		}
	}
}
il void work(int n,int m,string *a,string *b){
	int tot=0;
	sz[0]=cnt[0]=0;
	for(int i=0;i<=25;i++){
		tr[0][i]=0;
	}
	for(int i=1;i<=m;i++){
		int p=0;
		for(char j:b[i]){
			if(!tr[p][j-'a']){
				tr[p][j-'a']=++tot;
				sz[tot]=cnt[tot]=0;
				for(int j=0;j<=25;j++){
					tr[tot][j]=0;
				}
			}
			p=tr[p][j-'a'];
			sz[p]++;
		}
	}
	dfs1(0,0);
	for(int i=1;i<=n;i++){
		int kk=a[i].size();
		ha[0]=1;
		for(int j=kk-1;~j;j--){
			ha[kk-j]=ha[kk-j-1]*bas+a[i][j];
		}
		manacher(a[i],kk);
//		for(int j=0;j<kk;j++){
//			cout<<d[j]<<' ';
//		}
//		cout<<'\n';
		for(int j=0;j<kk;j++){
			ans+=d[j]*1ll*m;
			if(j+d[j]==kk){
				int k=j-d[j];
				int l=0,r=k+1;
				while(l<r){
					int mid=(l+r+1)>>1;
					if(check(ha[kk-k+mid-1]-ha[kk-k-1]*pw[mid])){
						l=mid;
					}else{
						r=mid-1;
					}
				}
				add(ha[kk-k+l-1]-ha[kk-k-1]*pw[l]);
			}
		}
	}
	dfs2(0);
	for(int i=1;i<=hnm;i++){
		hd[hs[i].val%mod]=0;
	}
	hnm=0;
}
int main(){
	freopen("str.in","r",stdin);
	freopen("str.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	pw[0]=1;
	for(int i=1;i<=2e6;i++){
		pw[i]=pw[i-1]*bas;
	}
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++){
			cin>>a[i];
		}
		for(int i=1;i<=m;i++){
			cin>>b[i];
		}
		ans=0;
		work(n,m,a,b);
//		cout<<ans<<'\n';
		for(int i=1;i<=n;i++){
			reverse(a[i].begin(),a[i].end());
		}
		for(int i=1;i<=m;i++){
			reverse(b[i].begin(),b[i].end());
		}
		work(m,n,b,a);
		cout<<ans<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}
/*
1
3 4
ryi
xvo
npr
zqcu
uvhz
fkgp
bbbb
90
*/

D. 概率

\(n\) 个数和后 \(n\) 个数是等价的,于是哥们求前后两组和相等的概率即可。总方案数显然,求合法方案数即可。

考虑将后 \(n\) 个数全都取相反数,哥们要求的就是前 \(n\) 个数 \(\in[0,m]\),后 \(n\) 个数 \(\in[-m,0]\),和为 \(0\) 的方案数。再给后 \(n\) 个数都加 \(m\),就是求所有数都 \(\in[0,m]\),和为 \(nm\) 的方案数。设这 \(2n\) 个数组成的集合为 \(A\),于是有:

\[\begin{aligned} & \sum_{\sum A=n m \text { 的方案 }}[A \text { 中 }>m \text { 的数的集合 }=\varnothing] \\ =&\sum_{\sum A=n m \text { 的方案 }} \sum_{T \subseteq(A \text { 中 }>m \text { 的数的集合 })}(-1)^{|T|} \\ =&\sum_{T \subseteq A}(-1)^{|T|}\left(T \text { 中的数都 }>m \text { 时 } \sum A=n m \text { 的方案数 }\right) \\ =&\sum_{i=0}^{2 n}(-1)^{i}\binom{2 n}{i}(i \text { 个 }>m \text { 的整数和 } 2 n-i \text { 个非负整数总和为 } n m \text { 的方案数 }) \\ =&\sum_{i=0}^{2 n}(-1)^{i}\binom{2 n}{i}(2 n \text { 个非负整数总和为 } n m-(m+1) i \text { 的方案数 }) \\ =&\sum_{i=0}^{2 n}(-1)^{i}\binom{2 n}{i}\binom{n m-(m+1) i+2 n-1}{2 n-1} \end{aligned} \]

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e6+5;
int mod,T,n,m,fac[maxn],inv[maxn];
il int qpow(int x,int y=mod-2){
	int res=1;
	while(y){
		if(y&1){
			res=res*1ll*x%mod;
		}
		y>>=1,x=x*1ll*x%mod;
	}
	return res;
}
il void init(int n=5e6){
	fac[0]=1;
	for(int i=1;i<=n;i++){
		fac[i]=fac[i-1]*1ll*i%mod;
	}
	inv[n]=qpow(fac[n]);
	for(int i=n;i;i--){
		inv[i-1]=inv[i]*1ll*i%mod;
	}
}
il int C(int x,int y){
	if(x<y||y<0){
		return 0;
	}
	return fac[x]*1ll*inv[y]%mod*inv[x-y]%mod;
}
int main(){
	freopen("pr.in","r",stdin);
	freopen("pr.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>mod>>T;
	init();
	while(T--){
		cin>>n>>m;
		int ans=0;
		for(int i=0;i<=n<<1;i++){
			int t=C(n<<1,i)*1ll*C(n*m-(m+1)*i+2*n-1,2*n-1)%mod;
			if(i&1){
				ans=(ans-t+mod)%mod;
			}else{
				ans=(ans+t)%mod;
			}
		}
		ans=(1-ans*1ll*qpow(qpow(m+1,n<<1))%mod+mod)*qpow(2)%mod;
		cout<<ans<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

HZOJ CSP-S模拟35

A B C D Sum Rank
10 50 15 5 80 9/27

A. 集合

将操作序列倒过来扫,用 bitset 可以直接维护出每个点在哪些点的集合内,时间复杂度 \(O(\frac{n^2}{w})\)。考虑容斥,\(|S\cup T|=|S|+|T|-|S\cap T|\),发现对于同一条边,上次操作得到的并集就是这次操作的交集,于是时间复杂度就线性了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=4e5+5;
int n,m,a[maxn],b[maxn],ans[maxn];
struct{
	int u,v;
}e[maxn];
int main(){
	freopen("set.in","r",stdin);
	freopen("set.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		e[i]={u,v};
	}
	for(int i=1;i<=n;i++){
		ans[i]=1;
	}
	for(int i=1;i<=m;i++){
		cin>>a[i];
	}
	for(int i=m;i;i--){
		int u=e[a[i]].u,v=e[a[i]].v;
		ans[u]=ans[v]=b[a[i]]=ans[u]+ans[v]-b[a[i]];
	}
	for(int i=1;i<=n;i++){
		cout<<ans[i]<<' ';
	}
	return 0;
}
}
int main(){return asbt::main();}

B. 存钱

先把所有 \(a_i>m\) 都去掉,最后取个 \(\max\) 即可。

首先考虑 \(k=n-1\) 的情况。记第 \(a_i\) 的开头为 \(p\),由于要求两个区间不被同一个长为 \(m\) 的区间包含,\(a_{i+1}\) 的结尾最小为 \(p+m\),于是开头就为 \(p+m-a_{i+1}+1\),于是答案就为 \(m+1+\sum_{i=2}^{n}m-a_i+1\)。将最小的两个放在首尾即可。

考虑 \(k=n-2\),哥们显然可以将所有 \(a\) 分为两组,分别算出 \(k=n-1\) 的方案后再合并。具体地,先把最小的四个扔掉,设 \(b_i=m-a_i+1\),两组其中的一组为 \(S\),于是答案就是 \(m+1+\max(\sum\limits_{i\in S}b_i,\sum\limits_{i\not\in S}b_i)\)

\(\sum b_i=tot\),哥们考虑尽量使 \(\sum\limits_{i\in S}b_i\) 接近 \(\frac{tot}{2}\)。首先从小到大在 \(b\) 中取使和 \(sum\le\frac{tot}{2}\) 直到不能取为止,记下一个位置为 \(p\),哥们要做的就是当 \(sum<\frac{tot}{2}\) 时加进去一个 \(b_i\),当 \(sum>\frac{tot}{2}\) 时减掉一个 \(b_i\)。显然过程中 \(|sum-\frac{tot}{2}|\le m\)。设 \(f_{i,j}\) 表示当前考虑到了 \(i\),凑出 \(\frac{tot}{2}+i\),删掉的最小的位置的最大值,于是可以转移。转移过程中每个位置只会被每个 \(j\) 考虑一次,于是时间复杂度 \(O(nm)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e4+5,M=5e3;
int T,n,m,kk,a[maxn],b[maxn],f[maxn],g[maxn];
int main(){
	freopen("money.in","r",stdin);
	freopen("money.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m>>kk;
		int mx=0;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			mx=max(mx,a[i]);
		}
		sort(a+1,a+n+1);
		if(kk==n-1){
			while(n&&a[n]>m){
				n--;
			}
			int p=1;
			for(int i=3;i<=n;i++){
				p=p+m-a[i]+1;
			}
			mx=max(mx,p+m);
			cout<<mx<<'\n';
			continue;
		}
		while(n&&a[n]>m){
			n--;
		}
		if(n<=2){
			cout<<mx<<'\n';
			continue;
		}
		if(n<=4){
			cout<<max(mx,m+1)<<'\n';
			continue;
		}
		int tot=0;
		for(int i=n,j=1;i>=5;i--,j++){
			b[j]=m+1-a[i];
			tot+=b[j];
		}
		n-=4;
//		for(int i=1;i<=n;i++){
//			cout<<b[i]<<' ';
//		}
//		cout<<'\n';
		int p=1,sum=0;
		while(p<=n&&sum+b[p]<=tot>>1){
			sum+=b[p++];
		}
		for(int i=0;i<=M<<1;i++){
			f[i]=g[i]=0;
		}
		g[sum-tot/2+M]=p;
		for(int i=p;i<=n;i++){
			for(int j=0;j<=M<<1;j++){
				f[j]=g[j];
			}
			for(int j=0;j<M;j++){
				f[j+b[i]]=max(f[j+b[i]],g[j]);
			}
			for(int j=M<<1;j>M;j--){
				for(int k=g[j];k<f[j];k++){
					f[j-b[k]]=max(f[j-b[k]],k);
				}
			}
			for(int j=0;j<=M<<1;j++){
				g[j]=f[j];
			}
		}
		int ans=0;
		for(int i=M;~i;i--){
			if(f[i]){
				ans=tot/2+i-M;
				break;
			}
		}
		cout<<max(mx,tot-ans+m+1)<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

C. 串串

首先考虑如果 \(S\) 确定了怎么做。设 \(f_i\) 表示考虑到 \(T\) 的第 \(i\) 位,没有匹配过 \(S\) 的方案数,\(g_i\) 表示考虑到 \(T\) 的第 \(i\) 位,只在最后面匹配了 \(S\) 的方案数。于是有转移:

\[f_i=f_{i-1}\times k-g_i\\ g_i=f_{i-n}-\sum_{j\in\operatorname{border}}g_{i-n+j} \]

注意到只和 \(\operatorname{border}\) 这个集合有关。考虑预处理出所有的 \(\operatorname{border}\) 集合与其对应的 \(S\) 的数量。这可以通过搜索 + 容斥处理。发现 \(\operatorname{border}\) 集合最多只有 \(4399\) 个,因此时间复杂度是对的。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=6e3+5,mod=1e9+7;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,m,kk,pw[maxn],fa[65],f[maxn],g[maxn];
vector<pii> bd;
il int find(int x){
	return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void dfs(int x,int S){
	if(!x){
		for(int i=1;i<n;i++){
			if(S>>(i-1)&1){
				continue;
			}
			for(int j=1;j<=i;j++){
				if(find(j)!=find(n-i+j)){
					goto togo;
				}
			}
			return ;
			togo:;
		}
		int cnt=0;
		for(int i=1;i<=n;i++){
			if(i==find(i)){
				cnt++;
			}
		}
		bd.pb(mp(S,pw[cnt]));
		return ;
	}
	for(int i=1;i<=x;i++){
		if(find(i)!=find(n-x+i)){
			int ff[65];
			memcpy(ff,fa,(n+1)<<3);
			dfs(x-1,S);
			memcpy(fa,ff,(n+1)<<3);
			break;
		}
	}
	int ff[65];
	memcpy(ff,fa,(n+1)<<3);
	for(int i=1;i<=x;i++){
		fa[find(i)]=find(n-x+i);
	}
	dfs(x-1,S|1ll<<(x-1));
	memcpy(fa,ff,(n+1)<<3);
}
int main(){
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m>>kk;
	if(n>m){
		cout<<0;
		return 0;
	}
	pw[0]=1;
	for(int i=1;i<=m;i++){
		pw[i]=pw[i-1]*kk%mod;
	}
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	dfs(n-1,0);
	sort(bd.begin(),bd.end(),greater<>());
	for(int i=0;i<bd.size();i++){
		for(int j=0;j<i;j++){
			if((bd[i].fir&bd[j].fir)==bd[i].fir){
				sub(bd[i].sec,bd[j].sec);
			}
		}
	}
	int ans=0;
	for(pii BD:bd){
		int S=BD.fir,cnt=BD.sec;
		vector<int> vec;
		for(int i=1;i<n;i++){
			if(S>>(i-1)&1){
				vec.pb(i);
			}
		}
		f[0]=1,g[0]=0;
		for(int i=1;i<=m;i++){
			f[i]=g[i]=0;
			if(i>=n){
				g[i]=f[i-n];
				for(int j:vec){
					sub(g[i],g[i-n+j]);
				}
			}
			f[i]=(f[i-1]*kk-g[i]+mod)%mod;
		}
		ans=((pw[m]-f[m]+mod)*cnt+ans)%mod;
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

D. 游走

HZOJ CSP-S模拟36

A B C D Sum Rank
- 50 5 70 125 11/29

其实 T1T3T4 都是之前考过的原。我是煞笔💀

A. 岛屿

首先用暴力打个表:

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define gcd __gcd
using namespace std;
namespace asbt{
int n,m,fa[39],sz[39],ans,tot,top,a[39],b[39],vis[39];
pii stk[39];
il int find(int x){
	return x!=fa[x]?find(fa[x]):x;
}
il void merge(int u,int v){
	u=find(u),v=find(v);
	if(u==v){
		return ;
	}
	if(sz[u]>sz[v]){
		swap(u,v);
	}
	stk[++top]=mp(u,v);
	fa[u]=v,sz[v]+=sz[u];
}
il void split(){
	int u=stk[top].fir,v=stk[top].sec;
	top--,fa[u]=u,sz[v]-=sz[u];
}
il void dfs(int x){
	if(x>n){
		tot++;
//		for(int i=1;i<=n<<1;i++){
//			for(int j=1;j<=n<<1;j++){
//				cout<<vis[i][j];
//			}
//			cout<<'\n';
//		}
//		cout<<ans<<' ';
//		puts("---------------");
//		for(int i=1;i<=n;i++){
//			cout<<a[vis[i]]<<' '<<b[i]<<'\n';
//		}
//		puts("---------------");
		for(int i=1;i<=n<<1;i++){
			if(i==find(i)){
				ans++;
			}
		}
//		cout<<ans<<'\n';
		return ;
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			int tp=top;
			merge(a[x],b[i]);
			vis[i]=x;
			dfs(x+1);
			vis[i]=0;
			if(top>tp){
				split();
			}
		}
	}
}
int main(){
	printf(" |");
	for(int i=0;i<=5;i++){
		printf("%17d|",i);
	}
	puts("");
	for(int i=0;i<=5;i++){
		printf("----------------------------------------------------------");
		puts("----------------------------------------------------------");
		printf("%d|",i);
		for(int j=0;j<=5;j++){
			if(2*i+j>10){
				break;
			}
//			cout<<i<<' '<<j<<":\n";
			m=i,n=j;
			n+=m<<1;
			for(int k=1;k<=m;k++){
				a[k]=k;
			}
			for(int k=m+1;k<=n;k++){
				b[k-m]=k;
			}
			for(int k=n+1;k<=2*n-m;k++){
				a[2*n-k+1]=k;
			}
			for(int k=2*n-m+1;k<=n<<1;k++){
				b[k-n]=k;
			}
//			for(int k=1;k<=n;k++){
//				cout<<a[k]<<' ';
//			}
//			cout<<'\n';
//			for(int k=1;k<=n;k++){
//				cout<<b[k]<<' ';
//			}
//			cout<<'\n';
			for(int k=1;k<=n;k++){
				fa[k]=fa[k+n]=k;
				sz[k]=2;
			}
			tot=ans=0; 
			dfs(1);
//			cout<<ans<<' '<<tot<<'\n';
			printf("%8d/%8d|",ans/gcd(ans,tot),tot/gcd(ans,tot));
		}
		puts("");
	}
	printf("----------------------------------------------------------");
	puts("----------------------------------------------------------");
	return 0;
}
}
int main(){return asbt::main();}
打表

然后找规律。首先对于第一列,上下做差,哥们可以得到 \(f_{x,0}=f_{x-1,0}+\frac{1}{2x-1}\)。第二列的规律是类似的。但是第三列是难做的,考虑到哥们已经求出了第一列,只需要再求出 \(f_{x,y}-f_{x,y-1}\) 即可。于是哥们左右做差,发现 \(f_{x,y}=f_{x,y-1}+\frac{1}{2x+y}\)。于是得到了答案:

\[\sum_{i=1}^{x}\frac{1}{2i-1}+\sum_{i=1}^{y}\frac{1}{2x+i} \]

时间复杂度线性。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int n,m;
int main(){
	freopen("island.in","r",stdin);
	freopen("island.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	double ans=0;
	for(int i=1;i<=n;i++){
		ans+=1.0/(2*i-1);
	}
	for(int i=1;i<=m;i++){
		ans+=1.0/(2*n+i);
	}
	cout<<fixed<<setprecision(9)<<ans;
	return 0;
}
}
int main(){return asbt::main();}

B. 小朋友

\(dp_{i,j}\) 表示在前 \(i\) 位中选了 \(j\) 位的最大的字符串,直接转移即可,时间复杂度 \(O(n^3)\)。用 pair 好存一点。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
int n;
string a,b;
pair<string,string> dp[55][55];
int main(){
	freopen("xiao.in","r",stdin);
	freopen("xiao.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>a>>b;
	n=a.size(),a=" "+a,b=" "+b;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			dp[i][j]=max(dp[i-1][j],mp(dp[i-1][j-1].fir+a[i],dp[i-1][j-1].sec+b[i]));
		}
	}
	string ans;
	for(int i=1;i<=n;i++){
		ans=max(ans,dp[n][i].fir+dp[n][i].sec);
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

C. 列表

Proposition 1:集合 \(S\) 合法当且仅当 \(\forall i\in[0,n]\),列表中 \([n+1-i,n+1+i]\)\(S\) 中的数的个数 \(\ge i+1\)。证明显然。

Corollary:连续数字段 \(A\) 合法当且仅当 \(\forall i\in[0,n]\),列表中 \([n+1-i,n+1+i]\)\(A\) 中的数的个数 \(\le n-i\)。证明也显然。

Proposition 2:若 \(A\) 合法,则 \(B\subseteq A\) 合法。证明依然显然。

于是根据 Proposition 2,哥们双指针枚举连续数字段 \(A=[l,r]\),然后用线段树维护 \(\max_{i=0}^{n}i+p(i)\)(其中 \(p(i)\) 表示列表中 \([n+1-i,n+1+i]\) 以外 \(A\) 中的数的个数)即可。

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=4e5+5;
int n,a[maxn],b[maxn];
int tr[maxn<<2],tag[maxn<<2];
il void pushup(int id){
	tr[id]=max(tr[lid],tr[rid]);
}
il void pushtag(int id,int v){
	tag[id]+=v,tr[id]+=v;
}
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]=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 v){
	if(l>r){
		return ;
	}
	if(L>=l&&R<=r){
		pushtag(id,v);
		return ;
	}
	pushdown(id);
	int mid=(L+R)>>1;
	if(l<=mid){
		add(lid,L,mid,l,r,v);
	}
	if(r>mid){
		add(rid,mid+1,R,l,r,v);
	}
	pushup(id);
}
int main(){
	freopen("list.in","r",stdin);
	freopen("list.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n*2+1;i++){
		cin>>a[i];
		b[a[i]]=i;
	}
	int ans=0;
	build(1,0,n);
	for(int l=1,r=0;l<=2*n+1;l++){
		while(r<n*2+1){
			r++;
			int p=abs(b[r]-n-1);
			add(1,0,n,0,p-1,1);
			if(tr[1]>n){
				add(1,0,n,0,p-1,-1);
				r--;
				break;
			}
		}
//		cout<<l<<' '<<r<<'\n';
		ans=max(ans,r-l+1);
		int p=abs(b[l]-n-1);
		add(1,0,n,0,p-1,-1);
	}
	cout<<ans;
	return 0;
}
}
int main(){return asbt::main();}

D. 种植

posted @ 2025-10-16 20:50  zhangxy__hp  阅读(36)  评论(0)    收藏  举报