多校A层冲刺NOIP2024模拟赛19

多校A层冲刺NOIP2024模拟赛19

\(T1\) A. 图书管理 (book) \(90pts/90pts\)

  • 部分分

    点击查看代码
    int p[10010];
    int main()
    {
        freopen("book.in","r",stdin);
        freopen("book.out","w",stdout);
        int n,i,l,r;
        ll ans=0;
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&p[i]);
        }
        for(l=1;l<=n;l++)
        {
            priority_queue<int,vector<int>,greater<int> >q1;
            priority_queue<int,vector<int>,less<int> >q2;
            for(r=l;r<=n;r+=2)
            {
                if(q2.empty()==0&&p[r]>q2.top())
                {
                    q1.push(p[r]);
                }
                else
                {
                    q2.push(p[r]);
                }
                if(abs((int)q1.size()-(int)q2.size())>=2)
                {
                    if(q1.size()>q2.size())
                    {
                        while(q1.size()-q2.size()>=2)
                        {
                            q2.push(q1.top());
                            q1.pop();
                        }
                    }
                    else
                    {
                        while(q2.size()-q1.size()>=2)
                        {
                            q1.push(q2.top());
                            q2.pop();
                        }
                    }
                }
                ans+=1ll*l*r*(q1.size()>q2.size()?q1.top():q2.top());
                if(r+2<=n)
                {
                    if(q2.empty()==0&&p[r+1]>q2.top())
                    {
                        q1.push(p[r+1]);
                    }
                    else
                    {
                        q2.push(p[r+1]);
                    }
                    if(abs((int)q1.size()-(int)q2.size())>=2)
                    {
                        if(q1.size()>q2.size())
                        {
                            while(q1.size()-q2.size()>=2)
                            {
                                q2.push(q1.top());
                                q1.pop();
                            }
                        }
                        else
                        {
                            while(q2.size()-q1.size()>=2)
                            {
                                q1.push(q2.top());
                                q2.pop();
                            }
                        }
                    }
                }
            }
        }
        printf("%lld\n",ans);
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    
  • 正解

    • 考虑枚举中位数 \(p_{i}\) ,计算区间个数。
    • \([l,r]\)\(>p_{i}\) 的数看做 \(1\)\(<p_{i}\) 的数看做 \(-1\) ,中位数的约束条件等价于 \([l,r]\) 内数的和等于 \(0\) ,开个桶一并维护左右端点即可。
    点击查看代码
    ll p[10010],suml[20010],sumr[20010];
    int main()
    {
        freopen("book.in","r",stdin);
        freopen("book.out","w",stdout);
        ll n,num,ans=0,i,j;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>p[i];
        }
        for(i=1;i<=n;i++)
        {
            memset(suml,0,sizeof(suml));
            memset(sumr,0,sizeof(sumr));
            num=0;
            suml[n]=sumr[n]=i;
            for(j=i-1;j>=1;j--)
            {
                num+=(p[j]>p[i])?1:-1;
                suml[num+n]+=j;
            }
            num=0;
            for(j=i+1;j<=n;j++)
            {
                num+=(p[j]>p[i])?-1:1;
                sumr[num+n]+=j;
            }
            for(j=-n;j<=n;j++)
            {
                ans+=suml[j+n]*sumr[j+n]*p[i];
            }
        }
        cout<<ans<<endl;
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

\(T2\) B. 两棵树 (tree) \(30pts/30pts\)

  • 部分分

    • \(30pts\) :爆搜出每种情况挨个计算。
    点击查看代码
    const ll p=998244353;
    ll ans=0;
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    struct Graph
    {
        struct node
        {
            ll nxt,to;
        }e[400010];
        ll head[200010],vis[200010],check[200010],cnt=0,tot=0;
        void add(ll u,ll v)
        {
            cnt++;
            e[cnt].nxt=head[u];
            e[cnt].to=v;
            head[u]=cnt;
        }
        void dfs(ll x)
        {
            vis[x]=tot;
            for(ll i=head[x];i!=0;i=e[i].nxt)
            {
                if(vis[e[i].to]!=tot&&check[e[i].to]==0)
                {
                    dfs(e[i].to);
                }
            }
        }
        ll ask(ll n)
        {
            tot++;
            ll ans=0;
            for(ll i=1;i<=n;i++)
            {
                if(vis[i]!=tot&&check[i]==0)
                {
                    dfs(i);
                    ans++;
                }
            }
            return ans;
        }
    }T,U;
    void dfs(ll pos,ll n)
    {
        if(pos==n+1)
        {
            ans=(ans+T.ask(n)*U.ask(n))%p;
        }
        else
        {
            T.check[pos]=1;
            U.check[pos]=0;
            dfs(pos+1,n);
            T.check[pos]=0;
            U.check[pos]=1;
            dfs(pos+1,n);
        }
    }
    int main()
    {
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
        ll n,u,v,i;
        cin>>n;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            T.add(u,v);
            T.add(v,u);
        }
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            U.add(u,v);
            U.add(v,u);
        }
        dfs(1,n);
        cout<<ans*qpow(qpow(2,n,p),p-2,p)%p<<endl;
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    
  • 正解

    • 对于一个森林,连通块数(树数)等于点数 \(V\) 减边数 \(E\) 。此时等价于求 \((V_{T}-E_{T}) \times (V_{U}-E_{U})=V_{T}V_{U}-E_{T}V_{U}-V_{T}E_{U}+E_{T}E_{U}\) 的期望值,考虑分别统计这几项的贡献。
    • \(V_{T}V_{U}\)
      • 暴力推式子,有 \(\begin{aligned} &E(V_{T}V_{U}) \\ &=\frac{1}{2^{n}} \times \sum\limits_{i=0}^{n}\dbinom{n}{i}(n-i)i \\ &=\frac{1}{2^{n}} \times \sum\limits_{i=1}^{n}\frac{n!}{(i-1)!(n-i-1)!} \\ &=\frac{1}{2^{n}} \times \sum\limits_{i=0}^{n-2}\dbinom{n-2}{i}(n-1)n \\ &=\frac{n(n-1)}{4} \end{aligned}\)
      • 考虑组合意义,对于 \(x \in T,y \in U\) ,当 \(x \ne y\) 时均被保留的概率为 \(\frac{1}{4}\) ,否则均被保留的概率为 \(0\) ,总期望为 \(\frac{n(n-1)}{2}\)
    • \(E_{T}V_{U}\)
      • 考虑组合意义,对于 \((x,y) \in T,u \in U\) ,当 \(x,y,u\) 互不相同时均被保留的概率为 \(\frac{1}{8}\) ,否则均被保留的概率为 \(0\) ,总期望为 \(\frac{(n-1)(n-2)}{8}\)
    • \(V_{T}E_{U}\)
      • \(E_{T}V_{U}\) ,总期望为 \(\frac{(n-1)(n-2)}{8}\)
    • \(E_{T}E_{U}\)
      • 考虑组合意义,对于 \((x,y) \in T,(u,v) \in U\) ,当 \(x,y,u,v\) 互不相同时均被保留的概率为 \(\frac{1}{16}\) ,否则均被保留的概率为 \(0\)
      • 枚举 \((x,y) \in T\) ,那么 \((u,v) \in U\) 满足 \(x,y,u,v\) 互不相同的数量等于 \(n-1-du_{U,x}-du_{U,y}+[(x,y) \in U]\)
      • 总期望为 \(\sum\limits_{(x,y) \in T}\frac{n-1-du_{U,x}-du_{U,y}+[(x,y) \in U]}{16}\)
    • 最终,有 \(\frac{n-1}{2}+\sum\limits_{(x,y) \in T}\frac{n-1-du_{U,x}-du_{U,y}+[(x,y) \in U]}{16}\) 即为所求。
    点击查看代码
    const ll p=998244353;
    map<pair<ll,ll>,ll>e;
    ll du[200010],u[400010],v[400010];
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    int main()
    {
        freopen("tree.in","r",stdin);
        freopen("tree.out","w",stdout);
        ll n,ans,inv=qpow(16,p-2,p),i;
        cin>>n;
        ans=(n-1)*qpow(2,p-2,p)%p;
        for(i=1;i<=n-1;i++)
        {
            cin>>u[i]>>v[i];
            if(u[i]>v[i])
            {
                swap(u[i],v[i]);
            }
        }
        for(i=1;i<=n-1;i++)
        {
            cin>>u[i+n]>>v[i+n];
            du[u[i+n]]++;
            du[v[i+n]]++;
            if(u[i+n]>v[i+n])
            {
                swap(u[i+n],v[i+n]);
            }
            e[make_pair(u[i+n],v[i+n])]=1;
        }
        for(i=1;i<=n-1;i++)
        {
            ans=(ans+(n-1-du[u[i]]-du[v[i]]+(e.find(make_pair(u[i],v[i]))!=e.end()))*inv%p)%p;
        }
        cout<<ans<<endl;
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

\(T3\) C. 函数(fun) \(60pts/60pts\)

  • 部分分

    • \(30 \%\) :模拟。

      点击查看代码
      ll x[1000010];
      int main()
      {
          freopen("fun.in","r",stdin);
          freopen("fun.out","w",stdout);
          ll n,q,a,b,ans,i,j;
          scanf("%lld%lld",&n,&q);
          for(i=1;i<=n;i++)
          {
              scanf("%lld",&x[i]);
          }
          for(j=1;j<=q;j++)
          {
              scanf("%lld%lld",&a,&b);
              ans=-1;
              for(i=1;i<=n-1;i++)
              {
                  if(((x[i]^a)-b)*((x[i+1]^a)-b)<=0)
                  {
                      ans=i;
                      break;
                  }
              }
              cout<<ans<<endl;
          }
          fclose(stdin);
          fclose(stdout);
          return 0;
      }
      

  • 正解

    • 考虑手动建出 \(f(x)\) 的函数图象,设左右端点分别为 \(l,r\) ,使用 \(01Trie\) 可以轻松维护。
    • \(f(l)f(r)>0\) 则一定无解,否则一定存在 \(f(l)f(mid) \le 0 \lor f(mid)f(r) \le 0\) ,其中 \(mid=\frac{l+r}{2}\) 。二分处理即可。
      • 正确性由二分法求函数零点可知。
    • 把二分改成递归在 accoders NOI 上大数据平均快了 \(300ms\) 左右,但在学校 \(OJ\) 上变慢了,不是很理解为啥。
    点击查看代码
    int x[1000010];
    struct Trie
    {
        int son[32000010][2],ed[32000010],rt_sum=0;
        void insert(int s,int id)
        {
            int x=0;
            for(int i=30;i>=0;i--)
            {
                if(son[x][(s>>i)&1]==0)
                {
                    rt_sum++;
                    son[x][(s>>i)&1]=rt_sum;
                }
                x=son[x][(s>>i)&1];
            }
            ed[x]=id;
        }
        int query_min(int s)
        {
            int x=0;
            for(int i=30;i>=0;i--)
            {
                if(son[x][(s>>i)&1]!=0)
                {
                    x=son[x][(s>>i)&1];
                }
                else
                {
                    x=son[x][((s>>i)&1)^1];
                }
            }
            return ed[x];
        }
        int query_max(int s)
        {
            int x=0;
            for(int i=30;i>=0;i--)
            {
                if(son[x][((s>>i)&1)^1]!=0)
                {
                    x=son[x][((s>>i)&1)^1];
                }
                else
                {
                    x=son[x][(s>>i)&1];
                }
            }
            return ed[x];
        }
    }T;
    int f(int x,int a,int b)
    {
        return (x^a)-b;
    }
    int main()
    {
        freopen("fun.in","r",stdin);
        freopen("fun.out","w",stdout);
        int n,q,a,b,l,r,ans,mid,i;
        scanf("%d%d",&n,&q);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&x[i]);
            T.insert(x[i],i);
        }
        for(i=1;i<=q;i++)
        {
            scanf("%d%d",&a,&b);
            l=T.query_min(a);
            r=T.query_max(a);
            if(l>r)
            {
                swap(l,r);
            }
            ans=-1;
            if(1ll*f(x[l],a,b)*f(x[r],a,b)<=0)
            {
                while(r-l>1)
                {
                    mid=(l+r)/2;
                    if(1ll*f(x[l],a,b)*f(x[mid],a,b)<=0)
                    {
                        r=mid;
                    }
                    else
                    {
                        l=mid;
                    }
                }
                ans=l;
            }
            printf("%d\n",ans);
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

\(T4\) D. 编辑(edit) \(60pts/60pts\)

  • 原题: QOJ 5312.Levenshtein Distance

  • 弱化版: luogu P5479 [BJOI2015] 隐身术

  • 部分分

    • \(30pts\) :枚举左右端点,然后 \(O(n^{2})\) 计算编辑距离,同 luogu P2758 编辑距离 ,时间复杂度为 \(O(kn^{3})\)
    • \(60pts\) :考虑固定一个端点,然后让另一个端点单调,时间复杂度为 \(O(n^{3})\)
    点击查看代码
    ll ans[50],f[2][50010];
    char s[50010],t[50010];
    int main()
    {
        freopen("edit.in","r",stdin);
        freopen("edit.out","w",stdout);
        ll k,lens,lent,l,r,i;
        cin>>k>>(s+1)>>(t+1);
        lens=strlen(s+1);
        lent=strlen(t+1);
        for(l=1;l<=lent;l++)
        {
            for(i=0;i<=lens;i++)
            {
                f[(l-1)&1][i]=i;
            }
            for(r=l;r<=lent;r++)
            {
                f[r&1][0]=r-l+1;
                for(i=1;i<=lens;i++)
                {
                    f[r&1][i]=min(f[(r-1)&1][i-1]+(s[i]!=t[r]),min(f[r&1][i-1]+1,f[(r-1)&1][i]+1));
                }
                if(f[r&1][lens]<=k)
                {
                    ans[f[r&1][lens]]++;
                }
            }
        }
        for(i=0;i<=k;i++)
        {
            cout<<ans[i]<<endl;
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    
  • 正解

    • 观察在编辑距离的存在下,长度差至多为 \(k\)
    • 考虑设 \(f_{i,j}\) 表示最大的 \(x\) 使得 \(s_{1 \sim x}\)\(t_{1 \sim x+j}\) 可以在 \(i\) 次编辑内得到,即从 \(1\) 开始操作 \(i\) 次且长度增加 \(j\) 的最大匹配位置。
      • 显然 \(x,i\) 具有单调性。
    • 转移时考虑截取一段 \(t\) 的后缀进行转移。
    • 假设已经操作完了,那么 \(f_{i,j}\) 能向外延伸的就是 \(s_{f_{i,j}+1,|s|}\)\(t_{f_{i,j}+j+1,|t|}\) 的公共前缀,查询 \(\operatorname{LCP}\) 进行更新即可。
      • \(\operatorname{LCP}\) 可以用二分哈希单次 \(O(\log n)\) 查询,也可以后缀数组 \(O(n \log n)\) 预处理完 \(O(1)\) 查询。
    • 转移时考虑刷表,分别对 \(f_{i+1,j-1},f_{i+1,j},f_{i+1,j+1}\) 进行转移(三者分别对应删除、替换、添加)。
      • 注意下标为负数时的移位。
    • 统计贡献时在遇到第一个合法的 \(i\) 加入贡献即可。
    • 如果用后缀数组的话时间复杂度为 \(O(nk^{2}+n \log n)\)
    点击查看代码
    int f[40][70],ans[40];
    char s[50010],t[50010],tmp[100010];
    struct SA
    {
        struct ST
        {
            int fminn[100010][25];
            void init(int n,int a[])
            {
                memset(fminn,0x3f,sizeof(fminn));
                for(int i=1;i<=n;i++)
                {
                    fminn[i][0]=a[i]; 
                }
                for(int j=1;j<=log2(n);j++)
                {
                    for(int i=1;i+(1<<j)-1<=n;i++)
                    {
                        fminn[i][j]=min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]);
                    }
                }
            }
            int query(int l,int r)
            {
                if(l>r)
                {
                    swap(l,r);
                }
                l++;
                int t=log2(r-l+1);
                return min(fminn[l][t],fminn[r-(1<<t)+1][t]);
            }
        }T;
        int sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010],height[100010];
        int val(char x)
        {
            return (int)x;
        }
        void counting_sort(int n,int m)
        {
            memset(cnt,0,sizeof(cnt));
            for(int i=1;i<=n;i++)
            {
                cnt[key[i]]++;
            }
            for(int i=1;i<=m;i++)
            {
                cnt[i]+=cnt[i-1];
            }
            for(int i=n;i>=1;i--)
            {
                sa[cnt[key[i]]]=id[i];
                cnt[key[i]]--;
            }
        }
        void init(char s[],int len)
        {
            int m=127,tot=0,num=0;
            for(int i=1;i<=len;i++)
            {
                rk[i]=val(s[i]);
                id[i]=i;
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(int w=1;tot!=len;w<<=1,m=tot)
            {
                num=0;
                for(int i=len;i>=len-w+1;i--)
                {
                    num++;
                    id[num]=i;
                }
                for(int i=1;i<=len;i++)
                {
                    if(sa[i]>w)
                    {
                        num++;
                        id[num]=sa[i]-w;
                    }
                }
                for(int i=1;i<=len;i++)
                {
                    key[i]=rk[id[i]];
                }
                counting_sort(len,m);
                for(int i=1;i<=len;i++)
                {
                    oldrk[i]=rk[i];
                }
                tot=0;
                for(int i=1;i<=len;i++)
                {
                    tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                    rk[sa[i]]=tot;
                }
            }
            for(int i=1,j=0;i<=len;i++)
            {
                j-=(j>=1);
                while(s[i+j]==s[sa[rk[i]-1]+j])
                {
                    j++;
                }
                height[rk[i]]=j;
            }
            T.init(len,height);
        }
        int lcp(int x,int y)
        {
            return T.query(rk[x],rk[y]);
        }
    }S;
    int main()
    {
        freopen("edit.in","r",stdin);
        freopen("edit.out","w",stdout);
        int k,lens,lent,i,j,h;
        cin>>k>>(s+1)>>(t+1);
        lens=strlen(s+1);
        lent=strlen(t+1);
        for(i=1;i<=lens;i++)
        {
            tmp[i]=s[i];
        }
        tmp[lens+1]='&';
        for(i=1;i<=lent;i++)
        {
            tmp[lens+1+i]=t[i];
        }
        S.init(tmp,lens+1+lent);
        for(h=1;h<=lent;h++)
        {	
            memset(f,-0x3f,sizeof(f));
            f[0][k]=0;
            for(i=0;i<=k;i++)
            {
                for(j=-k;j<=k;j++)
                {
                    if(f[i][j+k]>=0&&f[i][j+k]+j>=0&&f[i][j+k]+j+h-1<=lent)
                    {
                        if(f[i][j+k]+1<=lens&&f[i][j+k]+j+1+h-1+lens+1<=lens+1+lent)
                        {
                            f[i][j+k]+=S.lcp(f[i][j+k]+1,f[i][j+k]+j+1+h-1+lens+1);
                        }
                        if(j-1+k>=0)
                        {
                            f[i+1][j-1+k]=max(f[i+1][j-1+k],f[i][j+k]+(f[i][j+k]!=lens));
                        }
                        f[i+1][j+k]=max(f[i+1][j+k],f[i][j+k]+(f[i][j+k]!=lens));
                        f[i+1][j+1+k]=max(f[i+1][j+1+k],f[i][j+k]);
                    }
                }
            }
            for(j=-k;j<=k;j++)
            {
                for(i=0;i<=k;i++)
                {
                    if(f[i][j+k]==lens&&lens+j>0&&lens+j+h-1<=lent)//要求非空
                    {
                        ans[i]++;
                        break;
                    }
                }
            }
        }
        for(i=0;i<=k;i++)
        {
            cout<<ans[i]<<endl;
        }
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    

总结

  • \(T1\) 直接对着 \(2s\) 的时限冲了个 \(O(n^{2} \log n)\) 的做法,大样例在本地跑了 \(1.4s \sim 1.6s\) ,遂直接交了。
  • \(T2\)\(Trick\) 好像在之前见过,但想不起来是哪道题了。
  • \(T4\) 想了一个小时的怎么求编辑距离(先会写 \(O(3^{n})\) 的刷表爆搜后才会写 \(O(n^{2})\) 的转移),属于去年初赛白考了。

后记

  • \(T1\)

    • 下发的 \(PDF,markdown\) 题面都给了 \(2s\) 的时限,且在一段时间内题目界面也给了 \(2s\) 的时限,但在比赛结束时发现改成了 \(1s\)
    • 第一次下发的大样例假了,暴力都跑不过去。
  • \(T2\) 临结束时在学校 \(OJ\) 补充了额外的大样例。

  • \(T3\) 中途更换了大样例,并下发了 testlibchecker.cpp ,但默认我们已经会使用 checker.cpp 了, \(huge\) 还让我们别卡 checker.cpp

    点击查看代码
    #include"testlib.h"
    #include<bits/stdc++.h>
    const int N=1e6+7;
    int f[N];
    int main(int argc,char *argv[]){
        registerTestlibCmd(argc,argv);
        int n=inf.readInt(),q=inf.readInt();
        for(int i=1;i<=n;i++) f[i]=inf.readInt(); 
        while(q--){
            int x=inf.readInt(),y=inf.readInt();
            int a=ouf.readInt(),b=ans.readInt();
            if((a==-1)^(b==-1)) quitf(_wa,"Wrong Answer.");
            if(a==-1) continue;
            if(1ll*((f[a]^x)-y)*((f[a+1]^x)-y)>0) quitf(_wa,"Wrong Answer.");
            std::swap(a,b);
            if(1ll*((f[a]^x)-y)*((f[a+1]^x)-y)>0) quitf(_wa,"Wrong Answer.");
        }
        quitf(_ok,"Correct. Participant found a valid solution.");
        return 0;
    }
    
    
  • \(T4\) 中途更换了大样例。

  • \(T3,T4\) 因为时间复杂度较大,考虑到各学校 \(OJ\) 评测机,所以 后两题没有时间,设置题目的时候用标程跑一下就行了

posted @ 2024-11-07 17:47  hzoi_Shadow  阅读(34)  评论(0编辑  收藏  举报
扩大
缩小