2026年NOICA联盟-NOIP模拟赛(六)

A. 宇宙 (universe)

题目描述

\(n\times n\) 的网格,有 \(k\) 个星球,第 \(i\) 个星球质量为 \(m_i\),位于 \((x_i,y_i)\),它会产生引力场,与物理中不同,它产生的引力场是以回型一圈一圈扩散的。

具体地,一个位置 \((a,b)\) 受到星球 \(i\) 的引力大小为:\(max(0,m_i-max(|a-x_i|,|b-y_i|))\)。

求受到的引力大小之和小于等于 \(p\) 的位置的个数。

做法

一个星球的引力场可以看作 \(m\) 个以其所在位置为中心的正方形的叠加。将这些正方形二维差分一下,发现差分数组十分优美,从左上到右下的主对角线上都是加一,副对角线都是减一,其他地方为零。注意这个右下是原本最大的正方形右下角的右下一格。

我的做法有点蠢,看到切比雪夫距离没有思考直接暴力分讨去掉取最大值,具体来说就是对于某个点,以这个点为原点画两条线:\(y=x,y=-x\),将整个网格分成四个区域,每一区域中一种坐标的差一定不小于另一个坐标的差。

以左边的区域为例,其他区域可以旋转后同样处理。\(s_{x,y}\) 表示点 \((x,y)\) 的左边区域的星球对这个点的引力大小之和,整个网格以左上角为原点,左侧第一行为 \(x\) 轴,正方向向下,上面第一行为 \(y\) 轴,正方向向右,坐标从 \(1\) 开始标号。\(s_{x,y}=s_{x-1,y-1}+s_{x+1,y-1}-s{x,y-2}+v(x,y)+v(x,y-1)\),\(v(x,y)\) 表示在这个点的星球重量之和。但是这样明显是不对的,对于 \(s_{x-1,y-1}\) 要转移过来,要减去 \((y-1)-y_i>m\) 且在 \((x-1,y-1)\) 左边区域的星球个数,\(s_{x,y-2}\) 还要额外再减。

处理完这些发现有重,对于个点来说,这个点本身被加了 \(4\) 次,两条划分的直线各被加了 \(2\) 次,需要减去。那对每条斜线再做一次类似的,发现点本身被减成 \(0\) 次了,最后还要再加上。

还有这个寺阁玛出题人卡空间,由于 vector 空间消耗极大,旋转是不能额外开 vector 存,要直接交换旋转。

大份:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e3+5;
int n,k,P;
vector<int>h[N][N];
int t[N][N];
vector<pair<int,int> >p[N];
int sum[N][N],num[N][N],s[N],ans[N][N],ANS[N][N];
void round(){
    for(int i=1;i<=n/2;i++){
        for(int j=i;j<=n-i;j++){
            int x1=i,y1=j;
            int x2=j,y2=n-i+1;
            int x3=n-i+1,y3=n-j+1;
            int x4=n-j+1,y4=i;
            swap(t[x1][y1], t[x2][y2]);
            swap(t[x2][y2], t[x3][y3]);
            swap(t[x3][y3], t[x4][y4]);
            swap(h[x1][y1], h[x2][y2]);
            swap(h[x2][y2], h[x3][y3]);
            swap(h[x3][y3], h[x4][y4]);
            swap(ans[x1][y1], ans[x2][y2]);
            swap(ans[x2][y2], ans[x3][y3]);
            swap(ans[x3][y3], ans[x4][y4]);
        }
    }
}
void print(){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)cout<<ans[i][j]<<" ";cout<<"\n";
	}cout<<"\n";
	return;
}
void solve(){
	for(int j=1;j<=n;j++){
		for(int i=1;i<=n;i++)
			for(int x:h[i][j])if(x+j<=n)p[x+j].push_back(make_pair(i,j));
		if(j==1){
			for(int i=1;i<=n;i++){
				sum[i][j]=t[i][j];
				num[i][j]=h[i][j].size();
				ans[i][j]+=sum[i][j];
				sum[i][j]-=num[i][j];
			}
			continue;
		}
		for(int i=1;i<=n;i++){
			sum[i][j]=sum[i-1][j-1]+sum[i+1][j-1]-sum[i][j-2]+t[i][j]+t[i][j-1]-h[i][j-1].size();
			num[i][j]=num[i-1][j-1]+num[i+1][j-1]-num[i][j-2]+h[i][j].size()+h[i][j-1].size();
			if(i==1)
				sum[i][j]+=sum[i][j-2]-sum[i-1][j-1],
				num[i][j]+=num[i][j-2]-num[i-1][j-1];
			if(i==n)
				sum[i][j]+=sum[i][j-2]-sum[i+1][j-1],
				num[i][j]+=num[i][j-2]-num[i+1][j-1];
			ans[i][j]+=sum[i][j];
		}
		for(int i=1;i<=n;i++)s[i]=0;
		for(auto x:p[j]){
			s[max(1,x.first-(j-x.second))]++;
			s[min(n,x.first+(j-x.second))+1]--;
		}
		for(int i=1;i<=n;i++)
			s[i]+=s[i-1],
			num[i][j]-=s[i],
			sum[i][j]-=num[i][j];
		for(int i=1;i<=n;i++)s[i]=0;
		for(auto x:p[j]){
			s[max(1,x.first-(j-1-x.second))]++;
			s[min(n,x.first+(j-1-x.second))+1]--;
		}
		for(int i=1;i<=n;i++)
			s[i]+=s[i-1],
			num[i][j-1]-=s[i],
			sum[i][j-1]-=num[i][j-1];
		p[j].clear();
	}
	return;
}
pair<int,int> w[N];int cnt;
vector<int> e[N];
void solve2(){
	int sum2=0,num2=0;
	for(int i=0;i<=n;i++)s[i]=0;
	for(int i=1;i<=cnt;i++){
		for(int x:h[w[i].first][w[i].second])if(w[i].second+x<=n)s[w[i].second+x]++;
		sum2+=t[w[i].first][w[i].second];
		num2+=h[w[i].first][w[i].second].size();
		ans[w[i].first][w[i].second]-=sum2;
		num2-=s[w[i].second];
		sum2-=num2;
	}
	return;
}
void work(){
	for(int j=1;j<=n;j++){
		cnt=0;
		for(int i=1;i<=n-j+1;i++)w[++cnt]=make_pair(i,j+i-1);
		solve2();
	}
	for(int i=2;i<=n;i++){
		cnt=0;
		for(int j=1;j<=n-i+1;j++)w[++cnt]=make_pair(i+j-1,j);
		solve2();
	}
	return;
}
int main(){
	freopen("universe.in","r",stdin);
	freopen("universe.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>k>>P;
	for(int i=1;i<=k;i++){
		int x,y,m;cin>>x>>y>>m;
		t[x][y]+=m;
		h[x][y].push_back(m);
	}
	solve();work();round();
	solve();work();round();
	solve();work();round();
	solve();work();round();
	int Ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			ans[i][j]+=t[i][j];
			if(ans[i][j]<=P)Ans++;
		}
	cout<<Ans;
	return 0;
}

B. 翻牌很爽(cards)

题目描述

有两个数组 \(a,b\),可以交换任意多次 \(a_i,b_i\),求交换完后两个数组的 mex 的和的最大值。

做法

看到选择两个数中一个放序列 \(a\) 一个放序列 \(b\),想到连边。

观察一下,发现只出现了一次的数肯定只能在一个序列中,找到最小的只出现了一次的数,假设分配给序列 \(a\),那么序列 \(b\) 的 mex 不超过这个数。

将 \(a_i,b_i\) 连边,将分配转化成给无向边定向,被指向的点分配给序列 \(a\)。将连通块分类,对于边数大于等于点数的连通块一定可以找到一个环,对于环上的所有边定同一个方向一定最优,因为所有数都同时属于两个序列。环外的边和点怎么办呢,断掉环上的边,随便找一棵生成树,每棵生成树找环上的点作为根,所有边指向叶子的方向,这样的构造使这个连通块中所有点都在序列 \(a\) 中,所有非叶子节点都在序列 \(b\) 中,刚好序列 \(b\) 已经不选了最小的出现次数为 \(1\) 的数,选不选叶子没有影响。

对于树连通块,无论怎么定向,树上一定有一个点不在序列 \(a\) 中,那就贪心选择最大的数不在序列 \(a\) 中。将不在序列 \(a\) 中的数作为根,边方向定为指向叶子,那么序列 \(b\) 同时也是最优的,因为只有叶子没选入序列 \(b\)。

实现

整理一下思路,现在就是要求只出现了一次的数的最小值,和树连通块中最大的点的最小值。加起来就是答案。没出现的数就是单点,也是树。但是序列 \(b\) 的最大值也不能超过没出现过的的数,所以序列 \(b\) 的答案是出现次数小于等于 \(1\) 次的最小数。并查集连边的时候,如果两个点在同一个连通块内,就将这个连通块的最大值设为正无穷,否则取两个连通块的较大值作为新连通块的最大值。

一个疑问

数的大小很大数组开不下怎么办?可以直接对 \(n+Q\) 取较小值。这样是不会影响答案的,即不会出现原本答案是 \(n+Q\) 但是这样做就变成了 \(n+Q+1\),因为对于序列 \(a\),最大值要是 \(n+Q+1\),小于等于 \(n+Q\) 的点之间就至少有 \(n+Q+1\) 条边(因为 \(0\) 也是一个点),这显然是不可能的。对于序列 \(b\),mex 要是 \(n+Q+1\),小于等于 \(n+Q\) 的数都要出现两次,总共出现次数至少为 \(2\times(n+Q+1)\)(\(0\) 也要出现两次),而一条边只能提供两个出现次数,至少要 \(n+Q\) 条边,不行。

原题需要动态加入和删除,套个线段树分治和可撤销并查集就行了。

注意线段树分治要用 multiset 存出现过的操作。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<set>
#include<queue>
using namespace std;
typedef long long ll;
typedef multiset<pair<pair<int,int>,int> > mtst;
const int N=2e5+5,inf=1061109567;
bool FI;
int n,Q,num,a[N],b[N],d[N<<2],ans[N];
struct ques{
	char op;int a,b;
}q[N];
struct bcj{
	int fa,siz,maxn;
}f[N<<2],g[N<<2];int st[N<<2],tp;
priority_queue<int,vector<int>,greater<int> >q1,q2,q3,q4;
int Find(int u){return f[u].fa==u?u:Find(f[u].fa);}
void Clear(){
	while(!q1.empty()&&!q2.empty()&&q1.top()==q2.top())q1.pop(),q2.pop();
	while(!q3.empty()&&!q4.empty()&&q3.top()==q4.top())q3.pop(),q4.pop();
	return;
}
void Merge(int u,int v){
	if(d[u]==1)q4.push(u);d[u]++;
	if(d[v]==1)q4.push(v);d[v]++;
	u=Find(u),v=Find(v);
	if(f[u].siz>f[v].siz)swap(u,v);
	st[++tp]=u;g[tp]=f[v];
	Clear();
	if(u==v)return q2.push(f[u].maxn),q1.push(inf),f[u].maxn=inf,void();
	f[u].fa=v;
	if(f[v].maxn>=f[u].maxn)q2.push(f[u].maxn);
	else q2.push(f[v].maxn),f[v].maxn=f[u].maxn;
	f[v].siz+=f[u].siz;
	return;
}
void undo(){
	if(f[st[tp]].fa==st[tp])q2.push(inf),q1.push(g[tp].maxn);
	else if(g[tp].maxn==f[f[st[tp]].fa].maxn)q1.push(f[st[tp]].maxn);
	else q1.push(g[tp].maxn);
	Clear();
	f[f[st[tp]].fa]=g[tp];
	f[st[tp]].fa=st[tp];
	if(f[st[tp]].fa!=st[tp])f[f[st[tp]].fa].siz-=f[st[tp]].siz;
	tp--;
	return;
}
mtst s;
vector<pair<int,int> >o[N<<2];
void update(int id,int l,int r,int x,int y,pair<int,int> v){
	if(x>y)return ;
	if(l>=x&&r<=y)return o[id].push_back(v),void();
	int mid=(l+r)>>1,ls=id<<1,rs=ls|1;
	if(x<=mid)update(ls,l,mid,x,y,v);
	if(y>=mid+1)update(rs,mid+1,r,x,y,v);
	return;
}
void solve(int id,int l,int r){
	if(l>r)return;
	int mem=tp;
	for(auto x:o[id])Merge(x.first,x.second);
	if(l==r){
		Clear();
		ans[l]=q1.top()+q3.top();
		while(tp>mem)undo();
		for(auto x:o[id]){
			d[x.first]--;if(d[x.first]==1)q3.push(x.first);
			d[x.second]--;if(d[x.second]==1)q3.push(x.second);
		}
		return;
	}
	int mid=(l+r)>>1,ls=id<<1,rs=ls|1;
	solve(ls,l,mid);solve(rs,mid+1,r);
	while(tp>mem)undo();
	for(auto x:o[id]){
		d[x.first]--;if(d[x.first]==1)q3.push(x.first);
		d[x.second]--;if(d[x.second]==1)q3.push(x.second);
	}
	return;
}
bool SE;
int main(){
//	freopen("P16458.in","r",stdin);
//	freopen("P16458.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>Q;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i];
		a[i]=min(a[i],n+Q);
		b[i]=min(b[i],n+Q);
	}
	Q++;
	for(int i=2;i<=Q;i++){
		cin>>q[i].op>>q[i].a>>q[i].b;
		q[i].a=min(q[i].a,n+Q-1);
		q[i].b=min(q[i].b,n+Q-1);
	}
	for(int i=0;i<n+Q;i++)
		f[i]=(bcj){i,1,i},
		q1.push(i),
		q3.push(i);
	q3.push(inf);
	mtst::iterator it;
	for(int i=1;i<=n;i++)
		s.insert(make_pair(make_pair(a[i],b[i]),1));
	for(int i=2;i<=Q;i++){
		if(q[i].op=='+')s.insert(make_pair(make_pair(q[i].a,q[i].b),i));
		else{
			it=s.lower_bound(make_pair(make_pair(q[i].a,q[i].b),0));
			update(1,1,Q,(*it).second,i-1,(*it).first);
			s.erase(it);
		}
	}
	for(auto x:s)update(1,1,Q,x.second,Q,x.first);
	solve(1,1,Q);
	for(int i=1;i<=Q;i++)cout<<ans[i]<<"\n";
	return 0;
}

C. 数学(math)

被 T1 的做法坑惨了,已经无法思考了。

题目描述

有大小为 \(n\) 的数集,求所有子集乘积的欧拉函数之和。

做法

对于一个数的欧拉函数,唯一特殊的在于一种质数初次出现的时候是乘 \(p-1\),那么显然要维护出现的质数的集合。由于质数太多了直接状压会爆炸,发现一个数,大于根号 \(V\) 的质因子至多只有 \(1\) 个,那么对于质数非常多的大于根号 \(V\) 的部分,一个数最多只会把一个位置改为 \(1\),这样就太浪费状态了。按大质数分类,每类分别 dp,再整一类贡献给全局的 dp,这样就只需要对小于根号 \(V\) 的质数状态压缩,开的下了。

dp

设 \(f_{i,S}\) 表示前 \(i\) 个数,选择的数的小质数集合为 \(S\),的所有方案的乘积之和。当然由于我们已经将每个数分类了,这里的前 \(i\) 个数并不一定是原序列的前 \(i\) 个数。欧拉函数的一种求法是,如果 \(x\) 被质因数分解之后是 \(p_1^{e_1}p_2^{e_2}\dots p_m^{e_m}\),那么 \(\varphi(x)=x\times\prod_{1\leq i\leq m}\dfrac{p_i-1}{p_i}\)。当我们知道小质数的集合时,连乘那一部分相当于是固定了,所以 dp 只需要记录乘积(\(x\))和,当然大质数特殊处理,还要乘上大质数 \(P\) 的 \(\dfrac{P-1}{P}\)。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N=2005,V=3005,mod=998244353;
int add(int a){return a>=mod?a-mod:a;}
int ksm(int a,int b){int ans=1;while(b){if(b&1)ans=1ll*ans*a%mod;a=1ll*a*a%mod;b>>=1;}return ans;}
int n,f[2][(1<<16)+114],now=1,pre,g[2][(1<<16)+114];
vector<int>h[V];
bool isprime[V];int prime[V],inv[V],cnt;
void init(){
	isprime[0]=isprime[1]=1;
	for(int i=2;i<V;i++){
		if(!isprime[i])
			prime[++cnt]=i,
			inv[cnt]=ksm(i,mod-2);
		for(int j=1;j<=cnt;j++){
			if(i*prime[j]>=V)break;
			isprime[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
	return;
}
int getv(int x){
	int S=0;
	for(int i=1;i<=16;i++)if(x%prime[i]==0)S|=1<<i-1;
	return S;
}
int main(){
//	freopen("math.in","r",stdin);
//	freopen("math.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	init();
	for(int i=1;i<=n;i++){
		int w,v;
		cin>>w;v=w;
		for(int j=1;j<=16;j++)if(w%prime[j]==0){
			while(w%prime[j]==0)w/=prime[j];
		}
		h[w].push_back(v);
	}
	f[now][0]=1;
	for(int x:h[1]){
		int T=getv(x);
		swap(now,pre);
		for(int S=0;S<(1<<16);S++)
			f[now][S]=add(f[now][S]+f[pre][S]),
			f[now][S|T]=add(f[now][S|T]+1ll*f[pre][S]*x%mod);
		for(int S=0;S<(1<<16);S++)f[pre][S]=0;
	}
	for(int i=17;i<=cnt;i++){
		int q=now;
		now=1;pre=0;
		for(int S=0;S<(1<<16);S++)
			g[now][S]=f[q][S],
			g[pre][S]=0;
		for(int x:h[prime[i]]){
			int T=getv(x);
			swap(now,pre);
			for(int S=0;S<(1<<16);S++)
				g[now][S]=add(g[now][S]+g[pre][S]),
				g[now][S|T]=add(g[now][S|T]+1ll*g[pre][S]*x%mod);
			for(int S=0;S<(1<<16);S++)g[pre][S]=0;
		}
		swap(now,q);pre=now;now^=1;
		for(int S=0;S<(1<<16);S++)
			f[now][S]=add(1ll*(prime[i]-1)*inv[i]%mod*g[q][S]%mod+1ll*inv[i]*f[pre][S]%mod),
			f[pre][S]=0;
	}
	int ans=0;
	for(int S=0;S<(1<<16);S++){
		int Inv=1;
		for(int i=1;i<=16;i++)if((S>>i-1)&1)Inv=1ll*(prime[i]-1)*inv[i]%mod*Inv%mod;
		ans=add(ans+1ll*f[now][S]*Inv%mod);
	}
	cout<<ans;
	return 0;
}
posted @ 2026-10-01 11:22  Tanliu  阅读(27)  评论(0)    收藏  举报