多校A层冲刺NOIP2024模拟赛27终结篇

多校A层冲刺NOIP2024模拟赛27终结篇

T1 A. 【模板】分治FFT 0pts

  • 将式子展开后是一个形如 fni=1nj=1i1ai,j 的形式。

  • 考虑 fn 如何转移。当我们选出一对 (i,j) 进行合并进入 n=n1 的子问题,故 aiaj 的系数为 fn=fn1((n2)1)+gn1 ,边界为 f2=g2=1 。其中 gn1 表示 n1 堆果子有多少种合并方式,即 gn=(n2)gn1

    • 进一步可以得到 fn=gn
  • 结论同 luogu P7950 [✗✓OI R1] 后方之水

    点击查看代码
    const ll p=998244353;
    ll a[100010],sum[100010],f[100010],g[100010];
    int main()
    {
    	freopen("fft.in","r",stdin);
    	freopen("fft.out","w",stdout);
    	ll n,ans=0,i,j;
    	scanf("%lld",&n);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld",&a[i]);
    		a[i]%=p;
    		sum[i]=(sum[i-1]+a[i])%p;
    		ans=(ans+a[i]*sum[i-1]%p)%p;
    	}
    	f[2]=g[2]=1;
    	for(i=3;i<=n;i++)
    	{
    		f[i]=((i*(i-1)/2-1)%p*f[i-1]%p+g[i-1])%p;
    		g[i]=i*(i-1)/2%p*g[i-1]%p;
    	}
    	printf("%lld\n",ans*f[n]%p);
    	return 0;
    }
    
    

T2 B. 【模板】最近公共祖先 15pts

  • 通过数学直觉,得到边数为 k 的连通块的答案为 k2 ,调整法即可证明。

  • 当保证连通块是一棵树时,容易得到一种构造方式是从下往上考虑,对于 x 先尽可能让儿子之间一 x 为中转点互相连边,如果还有一条剩下的边那就传给 fa 尝试与 fa 的其他儿子连边。

  • 当不保证连通块是一棵树时,过早地拿走一条边使得图不连通可能会导致不能全部取完,故仍考虑在 DFS 树上操作并延续树边的构造方式。对于非树边(前向边和返祖边)先进行内部互相连边,然后将余下的扔给 DFS 树上深度较浅的节点即可。

    点击查看代码
    struct quality
    {
    	int u,v,w;
    };
    struct node
    {
    	int nxt,to,id;
    }e[600010];
    int head[300010],vis[300010],ins[300010],fa[300010],cnt=0;
    queue<int>q;
    vector<quality>ans;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(int x)
    {
    	vis[x]=ins[x]=1;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(vis[e[i].to]==0)
    		{
    			dfs(e[i].to);
    		}
    	}
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(ins[e[i].to]==0)//非树边
    		{
    			if(fa[e[i].to]==0)//尝试内部连边
    			{
    				q.push(e[i].to);
    			}
    			else//直接连上
    			{
    				ans.push_back((quality){x,e[i].to,fa[e[i].to]});
    				fa[e[i].to]=0;
    			}
    		}
    	}
    	while(q.size()>=2)
    	{
    		int tmp=q.front();
    		q.pop();
    		ans.push_back((quality){tmp,x,q.front()});
    		q.pop();
    	}
    	if(q.empty()==0)
    	{
    		fa[x]=q.front();
    		q.pop();
    	}
    	ins[x]=0;
    }
    int main()
    {
    	freopen("lca.in","r",stdin);
    	freopen("lca.out","w",stdout);
    	int n,m,u,v,i;
    	scanf("%d%d",&n,&m);
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d",&u,&v);
    		add(u,v);
    		add(v,u);
    	}
    	for(i=1;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			dfs(i);
    		}
    	}
    	printf("%d\n",ans.size());
    	for(i=0;i<ans.size();i++)
    	{
    		printf("%d %d %d\n",ans[i].u,ans[i].v,ans[i].w);
    	}
    	return 0;
    }
    
    

T3 C. 【模板】普通平衡树 45pts

  • 部分分

    • 测试点 19 :最长锯齿子序列可以 O(n2) 转移。
    • 测试点 12,13 :答案只可能是 0/1/2
    点击查看代码
    int op[300010],l[300010],r[300010],x[300010],ans[300010];
    vector<int>f[300010],g[300010];
    void insert(int x,int y)
    {
    	if(f[x].size()==0)
    	{
    		f[x].push_back(-1044266559);
    		g[x].push_back(0x3f3f3f3f);
    		f[x].push_back(y);
    		g[x].push_back(y);
    		ans[x]=1;
    	}
    	else
    	{
    		f[x].push_back(0x3f3f3f3f);
    		g[x].push_back(-1044266559);
    		for(int i=f[x].size()-1;i>=1;i--)
    		{
    			if(y<g[x][i-1])
    			{
    				f[x][i]=min(g[x][i-1],y);
    			}
    			if(y>f[x][i-1])
    			{
    				g[x][i]=max(g[x][i],y);
    			}
    			if(f[x][i]!=0x3f3f3f3f||g[x][i]!=-1044266559)
    			{
    				ans[x]=max(ans[x],i);
    			}
    		}
    		ans[x]=max(ans[x],2);
    	}
    }
    struct SMT
    {
    	struct SegmentTree
    	{
    		int lazy;
    	}tree[1200010];
    	#define lson(rt) (rt<<1)
    	#define rson(rt) (rt<<1|1)
    	void update(int rt,int l,int r,int x,int y,int val)
    	{
    		if(x<=l&&r<=y)
    		{
    			tree[rt].lazy+=val;
    			return;
    		}
    		int mid=(l+r)/2;
    		if(x<=mid)
    		{
    			update(lson(rt),l,mid,x,y,val);
    		}
    		if(y>mid)
    		{
    			update(rson(rt),mid+1,r,x,y,val);
    		}
    	}
    	int query(int rt,int l,int r,int pos)
    	{
    		if(l==r)
    		{
    			return tree[rt].lazy;
    		}
    		int mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			return query(lson(rt),l,mid,pos)+tree[rt].lazy;
    		}
    		else
    		{
    			return query(rson(rt),mid+1,r,pos)+tree[rt].lazy;
    		}
    	}
    }T;
    int main()
    {
    	freopen("odt.in","r",stdin);
    	freopen("odt.out","w",stdout);
    	int n,m,flag1=1,flag2=1,cnt=0,i,j;
    	scanf("%d%d",&n,&m);
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d",&op[i],&l[i]);
    		if(op[i]==1)
    		{
    			scanf("%d%d",&r[i],&x[i]);
    			cnt++;
    			flag1&=(x[i]==cnt);
    			flag2&=(x[i]==1000000000-cnt);
    		}
    	}
    	if(flag1==1||flag2==1)
    	{
    		for(i=1;i<=m;i++)
    		{
    			if(op[i]==1)
    			{	
    				T.update(1,1,n,l[i],r[i],1);
    			}
    			else
    			{
    				printf("%d\n",min(T.query(1,1,n,l[i]),2));
    			}
    		}
    	}
    	else
    	{
    		for(j=1;j<=m;j++)
    		{
    			if(op[j]==1)
    			{
    				for(i=l[j];i<=r[j];i++)
    				{
    					insert(i,x[j]);
    				}
    			}
    			else
    			{
    				printf("%d\n",ans[l[j]]);
    			}
    		}
    	}
    	return 0;
    }
    
  • 正解

    • 手摸 len3 的情况后发现 2+i=2len1[(aiai1)(ai+1ai)<0] 即为所求,画在平面直角坐标系上取更新大小关系的截点加入答案即可。
    • 线段树 pushdown 先天已经维护了时间的先后关系,故可以直接更新。
    点击查看代码
    int check(int a,int b,int c)
    {
    	return (a>b&&b<c)||(a<b&&b>c);
    }
    struct SMT
    {
    	struct node
    	{
    		int siz,ans,lc,zlc,rc,zrc;
    		void clear()
    		{
    			siz=ans=lc=zlc=rc=zrc=0;
    		}
    		node operator + (const node &another)
    		{
    			node tmp;
    			if(siz==0){return another;}
    			if(another.siz==0){return *this;}
    			tmp.siz=siz+another.siz;
    			tmp.ans=ans+another.ans+(zrc!=0&&check(zrc,rc,another.lc))+(another.zlc!=0&&check(rc,another.lc,another.zlc));
    			tmp.lc=lc;
    			tmp.zlc=(zlc!=0)?zlc:another.lc;
    			tmp.rc=another.rc;
    			tmp.zrc=(another.zrc!=0)?another.zrc:rc;
    			return tmp;
    		}
    	};
    	struct SegmentTree
    	{
    		node lazy;	
    	}tree[1200010];
    	#define lson(rt) (rt<<1)
    	#define rson(rt) (rt<<1|1)
    	void pushdown(int rt)
    	{
    		tree[lson(rt)].lazy=tree[lson(rt)].lazy+tree[rt].lazy;
    		tree[rson(rt)].lazy=tree[rson(rt)].lazy+tree[rt].lazy;
    		tree[rt].lazy.clear();
    	}
    	void update(int rt,int l,int r,int x,int y,int val)
    	{
    		if(x<=l&&r<=y)
    		{
    			tree[rt].lazy=tree[rt].lazy+(node){1,0,val,0,val,0};
    			return;
    		}
    		pushdown(rt);
    		int mid=(l+r)/2;
    		if(x<=mid)
    		{
    			update(lson(rt),l,mid,x,y,val);
    		}
    		if(y>mid)
    		{
    			update(rson(rt),mid+1,r,x,y,val);
    		}
    	}
    	int query(int rt,int l,int r,int pos)
    	{
    		if(l==r)
    		{
    			return(tree[rt].lazy.siz>=3)?tree[rt].lazy.ans+2:tree[rt].lazy.siz;
    		}
    		pushdown(rt);
    		int mid=(l+r)/2;
    		if(pos<=mid)
    		{
    			return query(lson(rt),l,mid,pos);
    		}
    		else
    		{
    			return query(rson(rt),mid+1,r,pos);
    		}
    	}
    }T;
    int main()
    {
    	freopen("odt.in","r",stdin);
    	freopen("odt.out","w",stdout);
    	int n,m,pd,l,r,x,i;
    	scanf("%d%d",&n,&m);
    	for(i=1;i<=m;i++)
    	{
    		scanf("%d%d",&pd,&l);
    		if(pd==1)
    		{
    			scanf("%d%d",&r,&x);
    			T.update(1,1,n,l,r,x);
    		}
    		else
    		{
    			printf("%d\n",T.query(1,1,n,l));
    		}
    	}
    	return 0;
    }
    

T4 D. 【模板】平面最近点对 20pts

  • 等价于最小化 k 维上最大的极差。

  • 部分分

    • 测试点 1,4,8,9 :爆搜。
    点击查看代码
    int a[16010][25],ans=0x7f7f7f7f;
    vector<int>state;
    void dfs(int pos,int n,int k)
    {
    	if(pos==n+1)
    	{
    		int sum=0;
    		for(int i=1;i<=k;i++)
    		{
    			int maxx=0,minn=0x7f7f7f7f;
    			for(int j=0;j<state.size();j++)
    			{
    				maxx=max(maxx,a[state[j]][i]);
    				minn=min(minn,a[state[j]][i]);
    			}
    			sum=max(sum,maxx-minn);
    		}
    		ans=min(ans,sum);
    	}
    	else
    	{
    		state.push_back(pos*2);
    		dfs(pos+1,n,k);
    		state.pop_back();
    		state.push_back(pos*2+1);
    		dfs(pos+1,n,k);
    		state.pop_back();
    	}
    }
    int main()
    {
    	freopen("pair.in","r",stdin);
    	freopen("pair.out","w",stdout);
    	int n,k,i,j;
    	scanf("%d%d",&n,&k);
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=k;j++)
    		{
    			scanf("%d",&a[i*2][j]);
    		}
    		for(j=1;j<=k;j++)
    		{
    			scanf("%d",&a[i*2+1][j]);
    		}
    	}
    	dfs(1,n,k);
    	printf("%d\n",ans);
    	return 0;
    }
    
  • 正解

    • [ARC069F] Flags ,考虑二分答案后 2SAT 进行 check
    • 每一维相互独立,线段树优化建图常数巨大且难写,观察到升序排序后连边都是一个点向一段前、后缀连边的形式,写个前后缀优化建图即可。
    点击查看代码
    int dfn[680010],low[680010],ins[680010],col[680010],tot=0,scc_cnt=0,id;
    pair<int,int>a[25][16010];
    vector<int>e[680010];
    stack<int>s;
    void add(int u,int v)
    {
    	e[u].push_back(v);
    }
    int rev(int x,int n)
    {
    	return x<=n?x+n:x-n;
    }
    void tarjan(int x)
    {
    	tot++;  dfn[x]=low[x]=tot;
    	s.push(x);  ins[x]=1;
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(dfn[e[x][i]]==0)
    		{
    			tarjan(e[x][i]);
    			low[x]=min(low[x],low[e[x][i]]);
    		}
    		else  if(ins[e[x][i]]==1)  low[x]=min(low[x],dfn[e[x][i]]);
    	}
    	if(dfn[x]==low[x])
    	{
    		scc_cnt++;
    		int tmp=0;
    		while(tmp!=x)
    		{
    			tmp=s.top();  s.pop();
    			ins[tmp]=0;
    			col[tmp]=scc_cnt;
    		}
    	}
    }
    bool check(int mid,int n,int k)
    {
    	while(s.empty()==0)  s.pop();
    	for(int i=1;i<=id;i++)
    	{
    		e[i].clear();
    		dfn[i]=low[i]=ins[i]=col[i]=0;
    	}
    	tot=scc_cnt=0;  id=2*n;
    	int pre,suf;
    	for(int i=1;i<=k;i++)
    	{
    		id++;  pre=id;
    		for(int l=1,r=1;r<=2*n;r++)
    		{
    			for(;a[i][r].first-a[i][l].first>mid;l++)
    			{
    				id++;  pre=id;
    				add(pre,pre-1);  add(pre,rev(a[i][l].second,n));
    			}
    			if(l!=1)  add(a[i][r].second,pre);
    		}
    		id++;  suf=id;
    		for(int l=2*n,r=2*n;l>=1;l--)
    		{
    			for(;a[i][r].first-a[i][l].first>mid;r--)
    			{
    				id++;  suf=id;
    				add(suf,suf-1);  add(suf,rev(a[i][r].second,n));
    			}
    			if(r!=2*n)  add(a[i][l].second,suf);
    		}
    	}
    	for(int i=1;i<=id;i++)  if(dfn[i]==0)  tarjan(i);
    	for(int i=1;i<=n;i++)  if(col[i]==col[i+n])  return false;
    	return true;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("pair.in","r",stdin);
    	freopen("pair.out","w",stdout);
    #endif
    	int n,k,l=0,r=0x3f3f3f3f,mid,ans,i,j;
    	scanf("%d%d",&n,&k);
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=k;j++)
    		{
    			scanf("%d",&a[j][i].first);  a[j][i].second=i;
    		}
    		for(j=1;j<=k;j++)
    		{
    			scanf("%d",&a[j][i+n].first);  a[j][i+n].second=i+n;
    		}
    	}
    	for(i=1;i<=k;i++)  sort(a[i]+1,a[i]+1+2*n);
    	while(l<=r)
    	{
    		mid=(l+r)/2;
    		if(check(mid,n,k)==true)
    		{
    			ans=mid;
    			r=mid-1;
    		}
    		else  l=mid+1;
    	}
    	printf("%d\n",ans);
    	return 0;
    }
    

总结

  • T1 误直接把 {g} 写成了 1 ,挂了 100pts
  • T2 没去想树的部分分,只写了个乱搞做法。
  • T3
    • 特殊性质 A,B 打假了,挂了 20pts
    • 赛时以为线段树 pushdown 不能很好地维护插入数的先后关系,说明对 pushdown 更新过程的认识不清。
  • T4 又被卡在对高维空间的理解上,但因为有样例解释很快就理解了。

后记

  • T2 下发了 checker.exe ,但 Linux 下的一开始有点问题,中途又更换了两次。
posted @   hzoi_Shadow  阅读(44)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
扩大
缩小
点击右上角即可分享
微信分享提示