2025牛客多校第三场(持续更新)

E

题意:
给定数组。每次可以挑两个除它们的约数,或者同时乘以某个数。求是否能让全变为某个数

思路:
发现n为奇数时一定可以
n=2时特判
n为偶数时,只要有一个质因子出现次数为奇数就不可以
普通质因数分解O(nsqrt(V))不行
使用异或哈希,为每一个质因子分配一个哈希值。通过欧拉筛法把合数的哈希值预处理出来
只要数组哈希值异或结果为0就可以

生成随机数:

std::mt19937_64 rnd(std::random_device{}()); 
std::mt19937_64 rnd(std::random_device{}()); 
vector<int>prim;
bool vis[maxn];
ull h[maxn];

void euler(int n,int opt){
    if(opt==1){
      rep(i,2,n){
          if(!vis[i])prim.pb(i);
          for(int j=0;i*prim[j]<=n&&j<prim.size();j++){
                vis[i*prim[j]]=true;
                if(i%prim[j]==0)break;
            }
       }
       rep(i,1,n){
            vis[i]=0;
       }
    }else{
        rep(i,2,n){
            if(!vis[i])prim.pb(i);
            for(int j=0;i*prim[j]<=n&&j<prim.size();j++){
                vis[i*prim[j]]=true;
                int compose=i*prim[j];
                h[compose]=h[i]^h[prim[j]];
                if(i%prim[j]==0)break;
            }
        }
    }
}
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);rep(i,1,n)cin>>a[i];
    if(n&1){cout<<"YES"<<endl;return;}
    if(n==2){
        if(a[1]==a[2]){cout<<"YES"<<endl;}
        else cout<<"NO"<<endl;
        return;
    }
    ull res=0;
    rep(i,1,n){
        res^=h[a[i]];
    }
    if(res==0){
        cout<<"YES"<<endl;
    }else{
        cout<<"NO"<<endl;
    }
}

另一种分解质因数
O(nlogn)
先预处理出所有数的最小质因数,相当于把sqrt(n)里面中不是该数的因子的数优化掉

vector<int>prim;
bool vis[maxn];
int minp[maxn];
void euler(int n){
      rep(i,2,n){
          if(!vis[i]){
            prim.pb(i);
            minp[i]=i;
          }
          for(int j=0;i*prim[j]<=n&&j<prim.size();j++){
                if(minp[i*prim[j]]==0)minp[i*prim[j]]=prim[j];
                vis[i*prim[j]]=true;
                if(i%prim[j]==0)break;
            }
       }
}
map<int,int>mp;

void solve(){
    int n;cin>>n;
    mp.clear();
    
    vector<int>a(n+1);rep(i,1,n)cin>>a[i];
    if(n&1){cout<<"YES"<<endl;return;}
    if(n==2){
        if(a[1]==a[2]){
            cout<<"YES"<<endl;
        }else cout<<"NO"<<endl;
        return;
    }

    rep(i,1,n){
        int x=a[i];
        while(minp[x]){
            int f=minp[x];
            int cnt=0;
            while(x%f==0){
                x/=f;
                cnt++;
            }
            mp[f]+=cnt;
        }
    }
    for(auto[x,y]:mp){
        if(y&1){
            cout<<"NO"<<endl;
            return;
        }
    }
    cout<<"YES"<<endl;
}

B

谁家苯题
题意:
给定a,b,c三个数
有4种操作
1:a左移1位
2:b右移1位
3:a:=a xor b
4:b:=a xor b
求构造方法,使得可以在小于等于64次操作的条件下使a=b=c
思路:
可以通过a,b异或使a,b最高位相同,然后通过b的最高位的1进行异或从而逼近答案
最后让b变为0,再异或变成a即可

因此步骤如下:

  • 先异或a,b,让它们的最高位1相同
  • 再判断它们的最高位和c的最高位哪个大
  • 如果c大,那么a要左移,在左移过程中,通过a:=a xor b使得a的最终位与c的最终位相同,然后b要右移,通过a:=a xor b使得a的对应位与c的对应位相同
  • 如果a大,那么b要右移,在右移过程中,通过a:=a xor b使得a的对应位与c的对应位相同
  • 所以实际上是把 两数异或操作 分别作用在不同的独立的位上
    (PS:注意不能位数应该是二进制位数+1,否则如果a,b,c有0的话,你会发现0的二进制位和1的二进制位都是0/或者你需要把最高位变量初始化为-1)
void solve(){
    int a,b,c;cin>>a>>b>>c;
    if(a==0&&b==0){
        if(c>0){
            cout<<-1<<endl;return;
        }else{
            cout<<0<<endl;return;
        }
    }
    int ta=0,tb=0,tc=0;
    for(int i=62;i>=0;i--){
        if((1ll<<i)&a){ta=i+1;break;}
    }
    for(int i=62;i>=0;i--){
        if((1ll<<i)&b){tb=i+1;break;}
    }
    for(int i=62;i>=0;i--){
        if((1ll<<i)&c){tc=i+1;break;}
    }
    
    vector<int>ans;
    if(ta<tb){
        ans.pb(3);
        a=a^b;
    }else if(ta>tb){
        ans.pb(4);
        b=a^b;
    }
    //a与b二进制最高位相同
    ta=max(ta,tb);
    
    if(ta>tc){
        for(int i=ta-1;i>=0;i--){
            int f1=0,f2=0;
            if((1ll<<i)&a)f1=1;
            if((1ll<<i)&c)f2=1;
            if(f1^f2){
                ans.pb(3);
                a=a^b;
            }
            b>>=1;
            ans.pb(2);
        }
    }else{
        //tc>=ta
        int move=tc-ta;
        for(int i=1;i<=tc-ta;i++){
            a<<=1;
            ans.pb(1);
            int f1=0,f2=0;
            if((1ll<<ta-1)&a)f1=1;
            if((1ll<<(ta+move-i-1))&c)f2=1;
            if(f1^f2){
                ans.pb(3);
                a=a^b;
            }
            
        }

        for(int i=ta-1;i>=0;i--){
            int f1=0,f2=0;
            if((1ll<<i)&a)f1=1;
            if((1ll<<i)&c)f2=1;
            if(f1^f2){
                ans.pb(3);
                a=a^b;
            }
            b>>=1;
            ans.pb(2);
        }
    }
        //b=0 -> b=a
    b=a;
    ans.pb(4);
    cout<<ans.size()<<endl;
    for(int opt:ans){
        cout<<opt<<' ';
    }
    cout<<endl;
  
}

H

题意:
给定一颗有根树,初始起点在1.分别在k个时间段有一个终点,若起点和终点在一个连通块里,起点可以向终点方向移动一格
你可以在任意时间段切割任意个边,求使得到达终点的最小时间点

思路:
贪心的想,显然当终点确定后,我们可以切割不是走向终点的所有边,那么起点就只会向终点移动

不妨认为每个时间段起点都能向终点方向扩张(r-l+1)步

那么可以从终点向上通过倍增确定染色范围暴力维护染色块权值来确定答案

int Fa[maxn];
vector<int>e[maxn];
int anc[maxn][30];
int dep[maxn];
int val[maxn];
void init(int u){
    anc[u][0]=Fa[u];
    for(int i=1;i<=29;i++){
      anc[u][i]=anc[anc[u][i-1]][i-1];
    }
    dep[u]=dep[Fa[u]]+1;
    for(int v:e[u]){
      if(v==Fa[u])continue;
      init(v);
    }
}

void solve(){
  int n,k;cin>>n>>k;
  rep(i,2,n){
    cin>>Fa[i];
    e[i].pb(Fa[i]);
    e[Fa[i]].pb(i);
  }

{
    memset(val,inf,sizeof val);
    val[1]=val[0]=1;
    dep[1]=1;
    Fa[1]=0;
    init(1);
}

  rep(z,1,k){
      int u,l,r;cin>>u>>l>>r;
      int U=u;
      int t=u;
      
      if(val[U]!=inf){
        cout<<l<<endl;return;
      }
      
      for(int i=29;i>=0;i--){
        if(val[anc[U][i]]==inf)U=anc[U][i];
      }
      if(val[U]==inf)U=Fa[U];//深度最小的未染色结点 -> 深度最大的染色结点
      for(int i=29;i>=0;i--){
        if(dep[anc[t][i]]-dep[U]>(r-l+1))t=anc[t][i];
      }
      if(dep[t]-dep[U]>(r-l+1))t=Fa[t];
      //染色阶段可到达的最深结点
      
      for(int V=t;V!=U;V=Fa[V]){
        val[V]=l+dep[V]-dep[U]-1;
      }
      if(val[u]!=inf){
          cout<<val[u]<<endl;return;
      }
  }
  cout<<-1<<endl;
}
posted @ 2025-07-22 18:03  Marinaco  阅读(86)  评论(0)    收藏  举报
//雪花飘落效果