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;
}

浙公网安备 33010602011771号