20251105 NOIP 模拟赛

20251105 NOIP 模拟赛

Problem A. Graph

Description

构造一张竞赛图 \(G\),进行 \(m\) 次翻转边的操作,要使得 \(G\) 任意时刻不强联通。

\(6\le n\le 400\)

Sub1:\(m\le n-2\),Sub2:\(m\le \lceil\frac {3n} 2 \rceil-3\),Sub3:\(m\le 2n-11\)

Solution

Sub1:把 \(m\) 条边建出来,图一定不连通。拿出一个连通块,放进 \(S\) 中,其余放进 \(T\) 中,只要让 \(S\) 的边全部指向 \(T\) 就行了,其他边都不重要。

Sub2:找到一个点放入 \(S\),其余都在 \(T\) 中。按时间遍历翻转边,若这条边连接了 \(S,T\),就把 \(T\) 中那个点拿到 \(S\) 中,设为 \(x\),然后再给 \(x\)\(T\) 中其他边钦定方向,使得它们此时都指向 \(x\);若连接同一个集合就忽略。

这样任意时刻 \(T\) 的边都指向 \(S\),最后 \(T\) 非空就合法。

给每个点记录一下什么时候第一次被翻转,取最晚的那个点就行。前面至少有 \(\lceil \frac n 2 \rceil-1\) 条边被忽略。

Sub3:以时间为边权找出最小生成树,找到最晚的边,将第一个点选在较小的那一边就可以让最大的那一边全部被忽略。一直做下去,直到只剩一个点,可以忽略 \(n-1-\lfloor \log_2 n\rfloor\) 条边。

int n,m;
bool vis[N];
int U[N<<1],V[N<<1];
int ans[N][N];
int cnt[N][N];

signed main(){
	read(n),read(m);
	for(int i=1;i<=m;i++) read(U[i]),read(V[i]);
	int Rt=0;
	for(int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		vis[i]=1;
		for(int j=1;j<=m;j++){
			vis[V[j]]|=vis[U[j]];
			vis[U[j]]|=vis[V[j]];
		}
		bool ok=0;
		for(int j=1;j<=n;j++)
			if(!vis[j]) ok=1;
		if(ok){
			Rt=i;
			break;
		}
	}
	memset(vis,0,sizeof(vis));
	vis[Rt]=1;
	for(int i=1;i<Rt;i++) ans[i][Rt]=1;
	for(int i=Rt+1;i<=n;i++) ans[Rt][i]=0;
	for(int i=1;i<=m;i++){
		int u=U[i],v=V[i];
		++cnt[u][v],++cnt[v][u];
		if(vis[u]==vis[v]) continue;
		if(vis[v]) swap(u,v);
		vis[v]=1;
		for(int x=1;x<=n;x++){
			if(vis[x]) continue;
			if(cnt[x][v]&1){
				if(v<x) ans[v][x]=1;
				else ans[x][v]=0;
			}
			else{
				if(x<v) ans[x][v]=1;
				else ans[v][x]=0;
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++)
			printf("%d ",ans[i][j]);
		puts("");
	}
	return 0;
}

Problem B. Gcds

Description

给定长为 \(n\) 的正整数序列 \(a\),求可以修改其中的任意一个元素,使它变为的任意一个 \(\le m\) 正整数时,它的 \(\gcd>1\) 的子串个数的最大值。

\(n\le 5\times 10^4,a_i\le m\le 5\times 10^5\)

Solution

枚举修改位置 \(x\),子串分为四部分:与 \(x\) 无交;以 \(x\) 为右端点;以 \(x\) 为左端点;跨过 \(x\)

第一种与 \(a_x\) 的值无关,预处理;第二、三种需要分别在 \(a_{x-1},a_{x+1}\) 中选择一个质因子;第四种需要选择一个 \(\gcd(a_{x-1},a_{x+1})\) 的因子。

而第四种的因子有重复质因子一定不优,所以只需要枚举质因子集合的子集。

枚举子集,枚举两边的质因子,这个看似是 \(O(2^{\omega}\omega^2)\) 的,但实际上界只有 \(144\)

算答案时,两边的前后缀 \(\gcd\) 只有 \(\log V\) 种。第四种预处理子集和,第二、三种直接暴力统计,\(O(\log V)\)

在一开始就可以把每个数的重复质因子去掉,把 \(\log V\) 降为 \(\omega\)。复杂度 \(O(n2^{\omega}\omega ^3)\),跑不满。

int n,a[N];
bool vis[M];
#define prev sbzcz
int prm[M],mnp[M],tot;
ll prev[N],sufv[N];
int msk[M],lg[K];
ll sum[K],prd[K];

struct Node{
	int x,w;
};
vector<Node> pre[N],suf[N];

const int V=500000;

void Init(){
	mnp[1]=1;
	for(int i=2;i<=V;i++){
		if(!vis[i]){
			mnp[i]=i;
			prm[++tot]=i;
		}
		for(int j=1;j<=tot;j++){
			if(i*prm[j]>V) break;
			vis[i*prm[j]]=1,mnp[i*prm[j]]=prm[j];
			if(i%prm[j]==0) break;
		}
	}
}

int Unique(int x){
	int y=1;
	while(x>1){
		y*=mnp[x];
		int z=mnp[x];
		while(x%z==0) x/=z;
	}
	return y;
}

void Workpre(){
	for(int i=1;i<=n;i++){
		pre[i].reserve(6);
		for(auto j:pre[i-1]){
			int x=__gcd(j.x,a[i]);
			if(x==1) continue;
			if(pre[i].size()&&pre[i].back().x==x) pre[i].back().w+=j.w;
			else pre[i].push_back(Node{x,j.w});
		}
		if(a[i]!=1){
			if(pre[i].size()&&pre[i].back().x==a[i]) ++pre[i].back().w;
			else pre[i].push_back(Node{a[i],1});
		}
		prev[i]=prev[i-1];
		for(auto j:pre[i]) prev[i]+=j.w;
	}	
}

void Worksuf(){
	for(int i=n;i;i--){
		suf[i].reserve(6);
		for(auto j:suf[i+1]){
			int x=__gcd(j.x,a[i]);
			if(x==1) continue;
			if(suf[i].size()&&suf[i].back().x==x) suf[i].back().w+=j.w;
			else suf[i].push_back({x,j.w});
		}
		if(a[i]!=1){
			if(suf[i].size()&&suf[i].back().x==a[i]) ++suf[i].back().w;
			else suf[i].push_back({a[i],1});
		}
		sufv[i]=sufv[i+1];
		for(auto j:suf[i]) sufv[i]+=j.w;
	}
}

vector<int> Divide(int x){
	vector<int> s;
	while(x>1){
		s.push_back(mnp[x]);
		x/=mnp[x];
	}
	return s;
}

ll Solve0(int x){
	ll res=0;
	for(auto i:pre[x-1]) res+=i.w;
	for(auto i:suf[x+1]) res+=i.w;
	for(auto i:pre[x-1])
		for(auto j:suf[x+1])
			if(__gcd(i.x,j.x)>1) res+=1ll*i.w*j.w;
	return res+prev[x-1]+sufv[x+1]+1;
}

ll Solve(int x){
	vector<int> p,sl,sr;
	int g=__gcd(a[x-1],a[x+1]);
	p=Divide(g);
	sl=Divide(a[x-1]/g);
	sr=Divide(a[x+1]/g);
	sl.push_back(1),sr.push_back(1);
	int siz=p.size(),AS=(1<<siz)-1;
	for(auto i:pre[x-1]){
		msk[i.x]=0;
		for(int j=0;j<siz;j++)
			if(i.x%p[j]==0) msk[i.x]|=(1<<j);
	}
	for(auto i:suf[x+1]){
		msk[i.x]=0;
		for(int j=0;j<siz;j++)
			if(i.x%p[j]==0) msk[i.x]|=(1<<j);
	}
	for(int i=0;i<=AS;i++) sum[i]=0;
	for(auto i:pre[x-1]){
		for(auto j:suf[x+1])
			sum[msk[i.x]&msk[j.x]]+=1ll*i.w*j.w;
	}
	for(int i=0;i<siz;i++){
		for(int s=0;s<=AS;s++)
			if(!(s>>i&1))
				sum[s|(1<<i)]+=sum[s];
	}
	ll ans=0;
	for(int s=0;s<=AS;s++){
		ll res=sum[AS]-sum[AS^s];
		if(s==0) prd[s]=1;
		else prd[s]=prd[s^(s&-s)]*p[__lg(s&-s)];
		for(int i:sl){
			for(int j:sr){
				if(1ll*prd[s]*i*j>500000) continue;
				ll val=0;
				for(auto k:pre[x-1]){
					if((msk[k.x]&s)||(i!=1&&!(k.x%i)))
						val+=k.w;
				}
				for(auto k:suf[x+1]){
					if((msk[k.x]&s)||(j!=1&&!(k.x%j)))
						val+=k.w;
				}
				Ckmax(ans,res+val);
			}
		}
	}
	ans+=prev[x-1]+sufv[x+1]+1;
	return ans;
}

signed main(){
	Init();
	read(n);
	for(int i=2;i<=(1<<6);i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++){
		read(a[i]);
		a[i]=Unique(a[i]);
	}
	Workpre(); Worksuf();
	a[0]=a[n+1]=1;
	ll ans=0;
	for(int i=1;i<=n;i++){
		if(a[i-1]==a[i+1]) Ckmax(ans,Solve0(i));
		else Ckmax(ans,Solve(i));
	}
	printf("%lld\n",ans);
	return 0;
}

Problem D. Perm

Description

给定排列 \(A,B\),求排列 \(C\) 的数量满足 \(C_i\ne A_i\land C_i\ne B_i\)\(n\le 3000\)

Solution

\(A_i\)\(B_i\) 连边,会连出若干个环。

容斥,钦定 \(j\) 个位置不合法,其余任意。这些位置要么选 \(A_x\),要么选 \(B_x\),相当于在环上每个位置可以不选,可以选自己,可以选择下一个点,但每个点不能被重复选。

对每个环做一遍 DP,每做完一个环就做一遍加法卷积。复杂度 \(O(n^2)\)

int n,a[N],b[N],p[N];
ll f[N][N][2],g[N][N],fac[N];
bool vis[N];

const ll mod=1e9+7;

inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}
inline void Add(ll &x,ll y){x=Mod(x+y);}

void Work(int m){
	for(int i=2;i<=m;i++){
		for(int j=0;j<i;j++){
			Add(f[i][j][0],f[i-1][j][0]);
			Add(f[i][j+1][0],f[i-1][j][0]);
			Add(f[i][j+1][1],f[i-1][j][0]);
			Add(f[i][j][0],f[i-1][j][1]);
			Add(f[i][j+1][1],f[i-1][j][1]);
		}
	}
}

void Solve(){
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	memset(vis,0,sizeof(vis));
	read(n);
	fac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	for(int i=1;i<=n;i++) read(a[i]);
	for(int i=1;i<=n;i++) read(b[i]),p[a[i]]=b[i];
	g[0][0]=1; int m=0;
	for(int x=1;x<=n;x++){
		if(vis[x]) continue;
		int siz=0,y=x; ++m;
		while(!vis[y]){
			++siz; vis[y]=1;
			y=p[y];
		}
		if(siz==1){
			for(int i=0;i<=n;i++){
				g[m][i]=g[m-1][i];
				if(i) Add(g[m][i],g[m-1][i-1]);
			}
			continue;
		}
		for(int j=0;j<=siz;j++)
			for(int k=0;k<=siz;k++)
				f[j][k][0]=f[j][k][1]=0;
		f[1][0][0]=f[1][1][1]=1; Work(siz);
		for(int i=0;i<=n;i++){
			for(int j=0;j<=min(i,siz);j++)
				(g[m][i]+=g[m-1][i-j]*(f[siz][j][0]+f[siz][j][1]))%=mod;
		}
		for(int j=0;j<=siz;j++)
			for(int k=0;k<=siz;k++)
				f[j][k][0]=f[j][k][1]=0;
		f[1][1][0]=1; Work(siz);
		for(int i=0;i<=n;i++){
			for(int j=0;j<=min(i,siz);j++)
				(g[m][i]+=g[m-1][i-j]*f[siz][j][0])%=mod;
		}
	}
	ll ans=0;
	for(int i=0;i<=n;i++){
		ll res=g[m][i]*fac[n-i]%mod;
		if(i&1) (ans+=mod-res)%=mod;
		else (ans+=res)%=mod;
	}
	printf("%lld\n",ans);
}

signed main(){
	int T; read(T);
	while(T--) Solve();
	return 0;
}
posted @ 2025-11-05 15:52  XP3301_Pipi  阅读(5)  评论(0)    收藏  举报
Title