2022牛客多校第七场

2022牛客多校第七场

过程

本场首先C题签到,一道小构造,随后F想了想直接暴力删除即可,之后G在理解题意后做出,随后便开始坐牢,队友和我在J上的dp为五次方,没敢下手,,随后在最后时刻想明白了K,但已经没时间下手了,惨淡收场。

题解

C

生成一个排列使得PiAi,首先如果Ai全相等则无解,否则我们直接默认Pi=i,如果只有一个相同的位置,那只要找到一个可交换的交换即可,否则将多个相同的位置依次交换即可。

int n;
int a[maxn],b[maxn];
bool check(){
    int now=a[1],flag=1;
    rep(i,2,n){
        if(a[i]!=now) flag=0;
    }
    return flag;
}
vector<int>c;
void solve(){
    cin>>n;
    rep(i,1,n) scanf("%d",&a[i]);
    if(check()){puts("NO");return;}
    puts("YES");
    rep(i,1,n) b[i]=i;
    c.clear();
    rep(i,1,n){
        if(i==a[i]) c.pb(i);
    }
    if(c.size()==1){
        int tmp=c[0];
        rep(i,1,n){
            if(a[i]!=tmp&&i!=a[tmp]) {    
                swap(b[i],b[tmp]);break;
            }
        }
    }
    else{
        int sze=c.size();
        rep(i,1,sze-1){
            swap(b[c[i]],b[c[i-1]]);
        }
    }
    rep(i,1,n) printf("%d ",b[i]);
    pts;
}  

F

暴力删除,用双向链表模拟即可,注意判断边界条件和停止条件即可。

int n,x,a[maxn];
int from[maxn],nxt[maxn];
int vis[maxn];
bool check(int now){
    bool flag=(a[now]+a[nxt[now]]==x)||(a[now]==a[nxt[now]]);
    flag&=(!vis[now] && !vis[nxt[now]]);
    flag&=(now!=nxt[now]);
    return flag;
}
void solve(){
    cin>>n>>x;
    rep(i,1,n){
        scanf("%d",&a[i]);
        from[i]=i-1;
        nxt[i]=i+1;
    }
    from[1]=n;nxt[n]=1;
    int now=1,cnt=0;
    int ans=0;
    while(1){
        if(check(now)){
            vis[now]=vis[nxt[now]]=1;
            from[nxt[nxt[now]]]=from[now];
            nxt[from[now]]=nxt[nxt[now]];
            now=from[now];ans++;
        }
        else now=nxt[now];
        if(now==nxt[now]) break;
        cnt++;
        if(cnt>=3*n) break;
    }
    cout<<ans<<endl;
}  

G

题意是表达字符串的最短正则表达式的长度和数量,在理解正则表达式后不难得出结论。

int n;
char s[maxn];
bool check(){
    int now=s[1],flag=1;
    rep(i,2,n) if(now!=s[i]) flag=0;
    return flag;
}
void solve(){
    scanf("%s",s+1);
    n=strlen(s+1);
    bool flag=check();
    if(n==1) printf("1 2\n");
    else if(n==2) {
        if(!flag) printf("2 6\n");
        else printf("2 8\n"); 
    }
    else{
        if(!flag) printf("2 2\n");
        else printf("2 4\n");
    }
}  

J

是一个n5dp,但因为跑不满,在判断非法状态后跑的飞快。
首先转换题意,因为是区间和可以被k整除,我们可以等价与前缀和模k后相同的前缀和可以构成一个区间使得区间和被k整除,那么这里将区间和转变为区间的两个端点。令si为前缀和模k,numisx=i的个数,则i=0k1numi=n,而此时区间和被k整除的个数为i=0k1C(numi,2),我们要求其等于t时的方案数。

此时设dp状态dp[i][j][sum]表示si=0..i的前缀和的值已被考虑,在n个位置中占据了j个位置,被k整除的区间个数为sum,那么最后所求为dp[k1][n][t]。考虑如何转移,对于新的i+1,枚举其在n个位置中放多少个,设放l个则对于状态dp[i][j][sum],可以有C(nj,l)个位置被考虑,因此转移方程dp[i+1][j+l][sum+C(l,2)]+=dp[i][j][sum]C(nj,l)

初始时化,当在前缀和数组上放0时,单独一个位置也算整除k,故初始化时dp[0][j][C(j+1,2)]=C(n,j)。随后在dp过程中不合法的条件直接跳过,最后dp复杂度看起来是O(n5),但实际上远远跑不满,接近O(n4).

int n,k,t;
ll dp[70][70][5000];
struct combinatorial{
 ll inv[maxn],fac[maxn];
 int qpow(ll a,ll b){
  ll c=1;
  while(b){
   if(b&1) c=c*a%mod;
   a=1LL*a*a%mod;b>>=1;
  }
  return c;
 }
 void build(){
  fac[0]=1;
  rep(i,1,N) fac[i]=fac[i-1]*i%mod;
  inv[N]=qpow(fac[N],mod-2)%mod;
  rpe(i,N-1,0) inv[i]=inv[i+1]*(i+1)%mod;
 }
 ll C(int a,int b){
  if(b==0) return 1;
        if(a<b) return 0;
   return fac[a]*inv[b]%mod*inv[a-b]%mod;
 }
}com;
void solve(){
    com.build();
    cin>>n>>k>>t;
    rep(j,0,n) dp[0][j][com.C(j+1,2)]=com.C(n,j);
    rep(i,0,k-1){
        rep(j,0,n){
            rep(v,0,t){
                if(!dp[i][j][v]) continue;
                rep(l,0,n){
                    if(j+l>n) break;
                    if(v+com.C(l,2)>n*n) break;
                    ll &tmp=dp[i+1][j+l][v+com.C(l,2)];
                    tmp=(tmp+dp[i][j][v]*com.C(n-j,l))%mod;
                }
            }
        }
    } 
    cout<<dp[k-1][n][t]<<"\n";
}  

K

博弈+莫队,需要首先分析出博弈的必胜条件。
n堆石子,每堆有ai个时:
n=1时,先手必胜。
n=2时,若a1a2=0,则后手可以模仿先手操作,后手必胜;若a1a2,则选手可以通过取来使得a1=a2,先手逼胜。
n=3时,对于三个数a1,a2,a3,不妨令其递增,先手可以从a3中拿取a1+a3a2个,再将a1,a3合并使得a1=a2,这样先手必胜。
n=4时,因为n=3先手必胜,所以谁改变堆数谁输,双方均只能拿去,最后终态为全为1,此时先手必输,因此将ai1后,转化为双方取一堆任意石子数量,全取完后无法操作的人输,此时即nim游戏,若ai1的异或和为0,则先手必输,因为后手可以跟着先手做,异或和不为0,则先手可将异或和转为0,后手必输。
n=5时,那么先手可以通过拿取最大的那一堆并将剩下的与其他堆合并使得n变为4且ai1异或和为0,故此时必胜。
n=6时同n=4

因此当n为奇数时必胜,n为偶数且ai1异或和不为0时必胜,为0必输。

那么对于区间询问,因为总的子区间个数为n(n1)2,故求出必输区间减去即可。对于区间内异或和为0的子区间数,可以用莫队来查询,首先处理出ai1的前缀异或和Si,我们维护两个数组s[0][i]s[1][i]表示区间内偶数位和奇数位上i出现的次数,每次进入当Si进入莫队当前范围时 ans+=s[i%2][Si],s[i%2][Si]++,删除时相反,即可在O(nn) 内解决问题。

int a[maxn],n,m;
struct Que{int l,r,id,bl;}q[maxn];
ll ans[maxn];
int block;
bool cmp(Que x,Que y){
    if(x.bl!=y.bl) return x.l<y.l;
    return x.r<y.r;
}
ll now=0;
int s[2][maxm];
void add(int x){
    now+=s[x&1][a[x]];s[x&1][a[x]]++;
}
void del(int x){
    s[x&1][a[x]]--;now-=s[x&1][a[x]];
}
void Solve(){
    cin>>n>>m;;block=n/sqrt(m);
    rep(i,1,n) scanf("%d",&a[i]);
    rep(i,1,m) {
        int x,y;
        scanf("%d %d",&x,&y);
        q[i]={x-1,y,i,(x-1)/block};
    }
    rep(i,1,n) a[i]=a[i-1]^(a[i]-1);
    sort(q+1,q+1+m,cmp);
    int l=0,r=0;s[0][0]=1;
    rep(i,1,m){
        int L=q[i].l,R=q[i].r;
        while(r<R) add(++r);
        while(r>R) del(r--);
        while(l<L) del(l++);
        while(l>L) add(--l);
        ll tmp=(r-l+1);
        ans[q[i].id]= tmp*(tmp-1)/2 - now; 
    }
    rep(i,1,m) printf("%lld\n",ans[i]);
}  
posted @   Mr_cold  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· Apache Tomcat RCE漏洞复现(CVE-2025-24813)
点击右上角即可分享
微信分享提示