2026 ZR暑假AB班联考

Day1

T1

注意到有一个很显然的必要条件,就是必须要有三个颜色。还有就是连续两个相邻节点肯定颜色不同,因为相邻两个节点肯定在一个三角形内。容易感觉这个也是充分的,考虑最极限的情况,就是一个颜色只有一个节点,那么这个节点可以向其他所有节点连边,显然也是合法的。所以说我们考虑减量构造,由于上述两个条件,我们可以每次找到一个合法的三个节点,然后割掉中间的点,但是我们有可能把唯一剩下的割掉,就是这种情况。image显然我们可以换一个方法割,这样还是可以剩下。image然后就作完了。可以使用链表维护割边。

Code

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
namespace IO{
	#define gc getchar
	#define pc putchar
	#define ps puts
	inline int in(){
		int x=0,f=1;
		char c=gc();
		for(;!isdigit(c);c=gc())if(c=='-')f=-1;
		for(;isdigit(c);c=gc())x=x*10+(c-'0');
		return x*f;
	}
	inline void out(int x){
		if(x<0)pc('-'),x=-x;
		if(x>=10)out(x/10);
		pc(x%10+'0');
	}
	inline void outsp(int x){out(x),pc(' ');}
	inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=1e6+5;
int n,a[N],c[3],pre[N],suf[N];
vector< pair<int,int> > ans;
bool C(int x,int y,int z){return a[x]+a[y]+a[z]==3&&a[x]!=a[y];}
int main(){
	n=in();
	for(int i=1;i<=n;i++)a[i]=in(),c[a[i]]++;
	for(int i=1;i<=n;i++)pre[i]=(i+n-2)%n+1,suf[i]=i%n+1;
	bool p=1;
	for(int i=1;i<=n;i++)if(a[i]==a[suf[i]])p=0;
	for(int i=0;i<3;i++)if(!c[i])p=0;
	if(!p){out(-1);return 0;}
	for(int i=1;i<=n;i++)if(C(pre[i],i,suf[i])){
		int x=pre[i],y=i,z=suf[i];
		for(int t=1;t<=n-3;t++){
			if(C(pre[x],x,z))suf[x]=z,pre[z]=x,ans.pb({x,z}),y=x,x=pre[x];
			else if(C(x,z,suf[z]))suf[x]=z,pre[z]=x,ans.pb({x,z}),y=z,z=suf[z];
			else pre[y]=pre[x],suf[pre[x]]=y,ans.pb({pre[x],y}),x=y,y=z,z=suf[z];
		}
		break;
	}
	outln(ans.size());
	for(auto [u,v]:ans)outsp(min(u,v)),outln(max(u,v));
	return 0;
}

T3

乱搞

首先可以考虑每次从前往后排除一个最小值,然后选择尝试最后两个,因为最后两个肯定删得最少,这样期望会不断增高,但是如果删掉了最后两个,那么就往前面找,可以适当调整阈值就能过了。

Code

#include<bits/stdc++.h>
#include"pen.h"
using namespace std;
const int N=20;
int a[N],c[N];
bool p[N];
mt19937 rnd(random_device{}());
int find(int x){for(int i=x;i>=1;i--)if(!p[i])return i;return 0;}
pair<int,int> solve_one(int n){
    for(int i=1;i<=n;i++)c[i]=p[i]=0;
    for(int now=1;now<=7;now++){
        for(int i=1;i<=n;i++)if(!p[i]){
            while(c[i]<now){
                if(!try_write(i)){p[i]=1;break;}
                c[i]++;
            }
            if(p[i])break;
        }
        int u=find(n),v=find(u-1),x=0,y=0;
        double z;
        for(int i=now;i<n;i++)for(int j=i+1;j<n;j++){
            if(i+j-c[u]-c[v]>=n)x++;
            y++;
        }
        z=1.0*x/y;
        if(now<=3&&z>0.7)break;
        if(now>=4&&z>0.6)break;
    }
    int u=find(n),v=find(u-1);
    return {u,v};
}

正解

考虑暴搜出状态数,每次往概率最大的地方走就行了。

得分

0+0+0=0,T1 没看到u<v输出格式错误挂完了,T3打错暴力导致错过了高分和正解

Day2

T1

正睿有原题,考虑每9个数肯定会经过其中一个,所以说我们可以找到路径中的一个点,把路径分成两半。发现这个东西可以使用猫树维护,每一层维护中间9个点到整个区间的最短路径,查询的时候只需要找到包含这两个的区间然后枚举中间点就行了。

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
namespace IO{
	#define gc getchar
	#define pc putchar
	#define ps puts
	inline int in(){
		int x=0,f=1;
		char c=gc();
		for(;!isdigit(c);c=gc())if(c=='-')f=-1;
		for(;isdigit(c);c=gc())x=x*10+(c-'0');
		return x*f;
	}
	inline void out(int x){
		if(x<0)pc('-'),x=-x;
		if(x>=10)out(x/10);
		pc(x%10+'0');
	}
	inline void outsp(int x){out(x),pc(' ');}
	inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=2e5+55,L=35,inf=1e9;
int n=2e5+L,s,t,ans=1;
vector<int> g[N],h[N],st[N],ed[N];
queue<int>q;
void bfs(int x,int l,int r,vector<int> g[],vector<int> &d){
	d.resize(r-l+1,inf),d[x-l]=0,q.push(x);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int v:g[u])if(v>=l&&v<=r&&d[v-l]>=inf)d[v-l]=d[u-l]+1,q.push(v);
	}
}
void init(int l,int r){
	if(l>r)return;
	int m=(l+r)>>1;
	for(int i=max(l,m-4);i<=min(r,m+4);i++)bfs(i,max(0,l-L),min(n,r+L),g,st[i]),bfs(i,max(0,l-L),min(n,r+L),h,ed[i]);
	init(l,m-5),init(m+5,r);
}
void solve(int l,int r){
	if(l>r)return;
	int m=(l+r)>>1;
	if(max(m-4,min(s,t))<=min(m+4,max(s,t))){
		int d=max(0,l-L);
		for(int i=max(l,m-4);i<=min(r,m+4);i++)ans=min(ans,ed[i][s-d]+st[i][t-d]);
		return;
	}
	if(s<m-4)solve(l,m-5);
	else solve(m+5,r);
}
int main(){
	freopen("digit.in","r",stdin);
	freopen("digit.out","w",stdout);
	for(int i=0;i<=n;i++)for(int j=i;j;j/=10){
		int x=j%10;
		g[i].pb(i-x),g[i].pb(i+x),h[i-x].pb(i),h[i+x].pb(i);
	}
	init(0,n);
	int q=in();
	while(q--){
		s=in()^ans,t=in()^ans,ans=inf,solve(0,n);
		if(ans>=inf)ans=-1;
		outln(ans),ans++;
	}
	return 0;
}

T2

后缀问题,考虑建出SAM,然后式子就是形如

\[\sum_{u}\sum_{v\in sub(u)}sz_u sz_v\sum_{i=l_u}^{r_u}\sum_{j=l_v}^{r_v}[i|j] \]

通过对v差分,化为

\[\sum_{u}\sum_{v\in sub(u)}sz_usz_v\sum_{i=l_u}^{r_u}\sum_{j=1}^{a_v}[i|j] \]

右边的东西有组合意义

\[\sum_{u}\sum_{v\in sub(u)}sz_usz_v\sum_{i=l_u}^{r_u}\lceil\frac{a_v}{i}\rceil \]

把u的贡献拆到i上面

\[\sum_{v}sz_v\sum_{i=1}^{a_v}\lceil\frac{a_v}{i}\rceil val_i \]

显然右边的东西可以整除分块,所以说我们需要一个支持 \(O(n)\) 次区间push/pop_back一些相同的数,\(O(n\sqrt n)\)次区间求和的数据结构,可以使用用树状数组区间加区间求和的trick改成分块,这样就可以做到 \(O(\sqrt n)\) 修改 \(O(1)\) 查询,总复杂度 \(O(\sqrt n)\)

得分

100+20+5=125,没有挂分,但是T2还是硬实力不够,没办法还是要提高熟练度和解题技巧。

T3

Day3

正如原题赛

T1

华附校赛原题。考虑每次死掉一个人时,会让左右两边的点不能同时是好人,并且删除这个人。删除的操作我们可以用链表维护,但是左右两边不能同时是好人这个怎么维护呢?我们发现,好人的个数可以用(max,+)矩阵乘法来刻画,因为中间的人只对两边有影响,那我们一条边左右两个人不能同时取的条件就变成了这条边对应的矩阵 \(a_{1,1}\) 的权值为负无穷就行了,还有一个问题,就是你删除一个人的时候,这个人的左右两条边会合并,但是线段树上合并操作不好做,注意到我们只有合并没有分裂,而(max,+)矩阵是有单位矩阵的,所以我们把一个连通块最左边矩阵的当成代表元,值为所有矩阵的积,因为合并的时候内部的边已经考虑完了,其他边改成单位矩阵就行,最后环的要求就是首尾节点的状态要相同,总的时间复杂度\(O(n\log n)\)

Code

#include<bits/stdc++.h>
using namespace std;
namespace IO{
	#define gc getchar
	#define pc putchar
	#define ps puts
	inline int in(){
		int x=0,f=1;
		char c=gc();
		for(;!isdigit(c);c=gc())if(c=='-')f=-1;
		for(;isdigit(c);c=gc())x=x*10+(c-'0');
		return x*f;
	}
	inline void out(int x){
		if(x<0)pc('-'),x=-x;
		if(x>=10)out(x/10);
		pc(x%10+'0');
	}
	inline void outsp(int x){out(x),pc(' ');}
	inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=2e5+5;
int t,n,L[N],R[N];
namespace SGT{
#define ls (k<<1)
#define rs (ls|1)
#define lm ((l+r)>>1)
#define rm (lm+1)
	const int M=N<<2;
	struct mat{
		int a[2][2];
		int *operator [](int x){return a[x];}
	}a[M],mp[N],E,I;
	mat operator *(mat x,mat y){
		mat z{};
		z[0][0]=max(x[0][0]+y[0][0],x[0][1]+y[1][0]);
		z[0][1]=max(x[0][0]+y[0][1],x[0][1]+y[1][1]);
		z[1][0]=max(x[1][0]+y[0][0],x[1][1]+y[1][0]);
		z[1][1]=max(x[1][0]+y[0][1],x[1][1]+y[1][1]);
		return z;
	}
	void pushup(int k){a[k]=a[ls]*a[rs];}
	void build(int k,int l,int r){
		if(l==r){a[k]=mp[l]=E,L[l]=R[l]=l;return;}
		build(ls,l,lm),build(rs,rm,r);
		pushup(k);
	}
	void upd(int k,int l,int r,int x){
		if(l>x||r<x)return;
		if(l==r){a[k]=mp[x];return;}
		upd(ls,l,lm,x),upd(rs,rm,r,x);
		pushup(k);
	}
}
using namespace SGT;
int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	E[0][1]=E[1][1]=1,I[0][1]=I[1][0]=-N;
	in(),t=in();
	while(t--){
		n=in(),build(1,1,n);
		for(int i=1;i<=n-3;i++){
			int x=in(),u=L[(x+n-2)%n+1],v=R[x];
			mp[u]=mp[u]*mp[x],mp[u][1][1]=-N,mp[x]=I,R[u]=v,L[v]=u;
			upd(1,1,n,u),upd(1,1,n,x);
			outsp(n-max(a[1][0][0],a[1][1][1]));
		}
		ps("");
	}
	return 0;
}

T2

吴浩岚太强了。首先考虑暴力dp,t是s的前缀的情况很好考虑,单独求就行了。对于其他情况,肯定跟s有一个公共前缀,然后t有一个(,s有一个),设 \(f_{a,b}\) 表示当在b这个位置与s不同,前b位有a个左括号,能生成多少个合法括号序列的方案数,那么我们枚举最终串的左括号的数量l,那么就相当于从(a,b),每次可以往右上或者往上走一格,且不能碰到y=2x+1,走到(l,2l)的方案数,那就是经典的反射容斥。由于每次都会往上走,所以考虑减掉x,然后就变成了从(a,b-a),每次可以往上或者往右走一个,且不能碰到y=x+1,最后走到(l,l)的方案数,那么就是\(\binom{2l-a-(a-b)}{l-a}-\binom{2l-a-(a-b)}{l-a-1}=\binom{2l-b}{l-a}-\binom{2l-b}{l-a-1}\),然后我正常推到这个式子就结束了,可以\(O(n^2)\)计算。我们发现,问题出在求\(f_{a,b}\)需要每次重新枚举长度,由于我们在遍历字符串时,每次a和b的变化最多只有1,这个sum肯定是可以重复利用的,所以我们考虑递推 \(f_{a,b}\),即通过 \(f_{a,b}\)求出\(f_{a,b+1}\)\(f_{a+1,b+1}\)。首先可以通过拆组合数得到

\[\begin{align*} f_{a,b}&=\sum_{l=0}^{n/2}\binom{2l-b}{l-a}-\binom{2l-b}{l-a-1}\\ &=\sum_{l=0}^{n/2}\binom{2l-b-1}{l-a}+\binom{2l-b-1}{l-a-1}-\binom{2l-b-1}{l-a-1}-\binom{2l-b-1}{l-a-2}\\ &=\sum_{l=0}^{n/2}\binom{2l-b-1}{l-a}-\binom{2l-b-1}{l-a-1}+\binom{2l-b-1}{l-a-1}-\binom{2l-b-1}{l-a-2}\\ &=\sum_{l=0}^{n/2}\binom{2l-b-1}{l-a}-\binom{2l-b-1}{l-a-1}+\sum_{l=0}^{n/2}\binom{2l-b-1}{l-a-1}-\binom{2l-b-1}{l-a-2}\\ &=f_{a,b+1}+f_{a+1,b+1} \end{align*}\]

然后我们只需要求出其中一个就可以求出另一个,现在只用到了组合数的性质,但是没有利用项数,然后我们会发现

\[\begin{align*} f_{a,b}&=\sum_{l=0}^{n/2}\binom{2l-b}{l-a}-\binom{2l-b}{l-a-1}\\ &=\sum_{l=0}^{n/2-1}\binom{2l-b}{l-a}-\binom{2l-b}{l-a-1}+\binom{n-b}{\frac{n}{2}-a}-\binom{n-b}{\frac{n}{2}-a-1}\\ &=\sum_{l=0}^{n/2}\binom{2l-b-2}{l-a-1}-\binom{2l-b-2}{l-a-2}+\binom{n-b}{\frac{n}{2}-a}-\binom{n-b}{\frac{n}{2}-a-1}\\ &=f_{a+1,b+2}+\binom{n-b}{\frac{n}{2}-a}-\binom{n-b}{\frac{n}{2}-a-1} \end{align*}\]

所以我们可以求出\(f_{a+1,b+2}\)了,这样就可以通过\(f_{a,b}\)\(f_{a,b+1}\)相互转移了,时间复杂度 \(O(n)\)

posted @ 2026-08-07 14:20  Lqs314  阅读(4)  评论(0)    收藏  举报