多校A层冲刺NOIP2024模拟赛21

多校A层冲刺NOIP2024模拟赛21

\(T1\) A. 送信卒 \(90pts/100pts\)

  • 部分分

    • \(90pts\)

      • 设最后的可能的最短路中左右共移动了 \(d\) 次,上下共移动了 \(x\) 次。

      • 则等价于求 \(\min \{ x_{i}k+d_{i} \}=s\) 的解,观察到 \(d \in [0,\min(\left\lceil \frac{nm}{2} \right\rceil,s)]\)

      • 将左右移动次数也扔进 \(Dijkstra\) 中转移即可,然后暴力进行 \(check\) ,时间复杂度为 \(O(n^{4}\log n)\)

      • 因为需要 \(O(n^{4})\) 的辅助空间,所以需要开 short

        点击查看代码
        const double eps=1e-8;
        struct node
        {
            int nxt,to,x,d;
        }e[40010];
        int head[10010],cnt=0,n,m,limit;
        short dis[10010][5010];
        bitset<5010>vis[10010];
        char c[110][110];
        struct quality
        {
            short dis;
            int x,d;
            bool operator < (const quality &another) const
            {
                return dis>another.dis;
            }
        };
        void add(int u,int v,int x,int d)
        {
            cnt++;
            e[cnt].nxt=head[u];
            e[cnt].to=v;
            e[cnt].x=x;
            e[cnt].d=d;
            head[u]=cnt;
        }
        int work(int x,int y)
        {
            return (x-1)*m+y;
        }
        void dijkstra(int s)
        {
            memset(dis,0x3f,sizeof(dis));
            priority_queue<quality>q;
            dis[s][0]=0;
            q.push((quality){dis[s][0],s,0});
            while(q.empty()==0)
            {
                int x=q.top().x,d=q.top().d;
                q.pop();
                if(vis[x][d]==0)
                {
                    vis[x][d]=1;
                    for(int i=head[x];i!=0;i=e[i].nxt)
                    {
                        if(d+e[i].d<=limit&&dis[e[i].to][d+e[i].d]>dis[x][d]+e[i].x)
                        {
                            dis[e[i].to][d+e[i].d]=dis[x][d]+e[i].x;
                            q.push((quality){dis[e[i].to][d+e[i].d],e[i].to,d+e[i].d});
                        }
                    }
                }
            }
        }
        int main()
        {
        #define Issac
        #ifdef Issac
            freopen("msg.in","r",stdin);
            freopen("msg.out","w",stdout);
        #endif
            int sx,sy,tx,ty,t,i,j;
            double s,ans=0x7f7f7f7f,k,minn;
            scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&tx,&ty);
            t=work(tx,ty);
            for(i=1;i<=n;i++)
            {
                for(j=1;j<=m;j++)
                {
                    scanf(" %c",&c[i][j]);
                }
            }
            cin>>s;
            limit=min(ceil(n*m/2.0),ceil(s)-1);
            for(i=1;i<=n;i++)
            {
                for(j=1;j<=m;j++)
                {
                    if(c[i][j]=='0')
                    {
                        if(i-1>=0&&c[i-1][j]=='0'){add(work(i,j),work(i-1,j),1,0);}
                        if(i+1<=n&&c[i+1][j]=='0'){add(work(i,j),work(i+1,j),1,0);}
                        if(j-1>=0&&c[i][j-1]=='0'){add(work(i,j),work(i,j-1),0,1);}
                        if(j+1<=m&&c[i][j+1]=='0'){add(work(i,j),work(i,j+1),0,1);}
                    }
                }
            }
            dijkstra(work(sx,sy));
            if((int)s==s&&tx==sx&&abs(ty-sy)==(int)s)
            {
                ans=0;
            }
            else
            {
                for(i=0;i<=limit;i++)
                {
                    if(dis[t][i]!=0x3f3f&&dis[t][i]!=0)
                    {
                        k=1.0*(s-i)/dis[t][i];
                        minn=0x7f7f7f7f;
                        for(j=0;j<=limit;j++)
                        {
                            if(minn-(1.0*j+1.0*k*dis[t][j])>eps)
                            {
                                minn=1.0*j+1.0*k*dis[t][j];
                            }
                        }
                        if(fabs(minn-s)<=eps&&ans-k>eps)
                        {
                            ans=k;
                        }
                    }
                }
            }
            printf("%.3lf\n",ans);
            return 0;
        }
        
    • \(100pts\) :将上述做法的 \(dijsktra\) 改成 \(01BFS\) 即可,时间复杂度为 \(O(n^{4})\)

      点击查看代码
      const double eps=1e-8;
      struct node
      {
          int nxt,to,x,d;
      }e[40010];
      int head[10010],cnt=0,n,m,limit;
      short dis[10010][5010];
      bitset<5010>vis[10010];
      char c[110][110];
      void add(int u,int v,int x,int d)
      {
          cnt++;
          e[cnt].nxt=head[u];
          e[cnt].to=v;
          e[cnt].x=x;
          e[cnt].d=d;
          head[u]=cnt;
      }
      int work(int x,int y)
      {
          return (x-1)*m+y;
      }
      void bfs(int s)
      {
          memset(dis,0x3f,sizeof(dis));
          deque<pair<int,int> >q;
          dis[s][0]=0;
          q.push_back(make_pair(s,0));
          while(q.empty()==0)
          {
              int x=q.front().first,d=q.front().second;
              q.pop_front();
              if(vis[x][d]==0)
              {
                  vis[x][d]=1;
                  for(int i=head[x];i!=0;i=e[i].nxt)
                  {
                      if(d+e[i].d<=limit&&dis[e[i].to][d+e[i].d]>dis[x][d]+e[i].x)
                      {
                          dis[e[i].to][d+e[i].d]=dis[x][d]+e[i].x;
                          if(e[i].x==1)
                          {
                              q.push_back(make_pair(e[i].to,d+e[i].d));
                          }	
                          else
                          {
                              q.push_front(make_pair(e[i].to,d+e[i].d));
                          }
                      }
                  }
              }
          }
      }
      int main()
      {
      #define Issac
      #ifdef Issac
          freopen("msg.in","r",stdin);
          freopen("msg.out","w",stdout);
      #endif
          int sx,sy,tx,ty,t,i,j;
          double s,ans=0x7f7f7f7f,k,minn;
          scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&tx,&ty);
          t=work(tx,ty);
          for(i=1;i<=n;i++)
          {
              for(j=1;j<=m;j++)
              {
                  scanf(" %c",&c[i][j]);
              }
          }
          cin>>s;
          limit=min(ceil(n*m/2.0),ceil(s)-1);
          for(i=1;i<=n;i++)
          {
              for(j=1;j<=m;j++)
              {
                  if(c[i][j]=='0')
                  {
                      if(i-1>=0&&c[i-1][j]=='0'){add(work(i,j),work(i-1,j),1,0);}
                      if(i+1<=n&&c[i+1][j]=='0'){add(work(i,j),work(i+1,j),1,0);}
                      if(j-1>=0&&c[i][j-1]=='0'){add(work(i,j),work(i,j-1),0,1);}
                      if(j+1<=m&&c[i][j+1]=='0'){add(work(i,j),work(i,j+1),0,1);}
                  }
              }
          }
          bfs(work(sx,sy));
          if((int)s==s&&tx==sx&&abs(ty-sy)==(int)s)
          {
              ans=0;
          }
          else
          {
              for(i=0;i<=limit;i++)
              {
                  if(dis[t][i]!=0x3f3f&&dis[t][i]!=0)
                  {
                      k=1.0*(s-i)/dis[t][i];
                      minn=0x7f7f7f7f;
                      for(j=0;j<=limit;j++)
                      {
                          if(minn-(1.0*j+1.0*k*dis[t][j])>eps)
                          {
                              minn=1.0*j+1.0*k*dis[t][j];
                          }
                      }
                      if(fabs(minn-s)<=eps&&ans-k>eps)
                      {
                          ans=k;
                      }
                  }
              }
          }
          printf("%.3lf\n",ans);
          return 0;
      }
      
  • 正解

    • 观察到 \(\min \{ x_{i}k+d_{i} \}\) 具有单调性,即随着 \(k\) 的增大最短路长度不降。
    • 二分答案即可。
    点击查看代码
    const double eps=1e-8;
    double dis[110][110];
    bool vis[110][110];
    char c[110][110];
    void dijkstra(int sx,int sy,double mid,int n,int m)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                vis[i][j]=0;
                dis[i][j]=0x3f3f3f3f;
            }
        }
        priority_queue<pair<int,pair<int,int>>>q;
        dis[sx][sy]=0;
        q.push(make_pair(-dis[sx][sy],make_pair(sx,sy)));
        while(q.empty()==0)
        {
            int x=q.top().second.first,y=q.top().second.second;
            q.pop();
            if(vis[x][y]==0)
            {
                vis[x][y]=1;
                if(x-1>=1&&dis[x-1][y]>dis[x][y]+mid&&c[x-1][y]=='0')
                {
                    dis[x-1][y]=dis[x][y]+mid;
                    q.push(make_pair(-dis[x-1][y],make_pair(x-1,y)));
                }
                if(x+1<=n&&dis[x+1][y]>dis[x][y]+mid&&c[x+1][y]=='0')
                {
                    dis[x+1][y]=dis[x][y]+mid;
                    q.push(make_pair(-dis[x+1][y],make_pair(x+1,y)));
                }
                if(y-1>=1&&dis[x][y-1]>dis[x][y]+1&&c[x][y-1]=='0')
                {
                    dis[x][y-1]=dis[x][y]+1;
                    q.push(make_pair(-dis[x][y-1],make_pair(x,y-1)));
                }
                if(y+1<=m&&dis[x][y+1]>dis[x][y]+1&&c[x][y+1]=='0')
                {
                    dis[x][y+1]=dis[x][y]+1;
                    q.push(make_pair(-dis[x][y+1],make_pair(x,y+1)));
                }
            }
        }
    }
    int main()
    {
    #define Issac
    #ifdef Issac
        freopen("msg.in","r",stdin);
        freopen("msg.out","w",stdout);
    #endif
        int n,m,sx,sy,tx,ty,i,j;
        double s,l=0,r,mid,ans=-1;
        cin>>n>>m>>sx>>sy>>tx>>ty;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                cin>>c[i][j];
            }
        }
        cin>>s;
        r=s;
        while(r-l>=eps)
        {
            mid=(l+r)/2;
            dijkstra(sx,sy,mid,n,m);
            if(dis[tx][ty]>=s)
            {
                ans=mid;
                r=mid;
            }
            else
            {
                l=mid;
            }
        }
        printf("%.3lf\n",ans);
        return 0;
    }
    

\(T2\) B. 共轭树图 \(0pts/0pts\)

  • 不妨钦定 \(u>v\) ,那么在断开 \((u,v)\) 这条边后等价于将 \(u\) 的深度最浅的祖先和 \(v\) 连接。故最终得到的 \(G\) 也一定是棵树,且当以 \(n\) 为根时也满足父亲节点的编号一定大于自身的编号。

  • 考虑这样的 \(G\) 是怎么构造出来的。当把 \(G\) 的边在原图上画出后,任意两条边之间只能不交和包含,因为如果相交的话下边的那条边的端点就可以连到上面的边上去。

  • 定义根节点的深度为 \(1\)

  • \(f_{x,i}\) 表示 \(x\)\(G\) 中只被允许与原图中它的 \(i \in [1,dep_{x}-1]\) 个祖先连边时(即 \(G\) 中的父亲节点)以 \(x\) 为根的子树中的方案数,状态转移方程为 \(f_{x,i}=\sum\limits_{j=2}^{i+1}\prod\limits_{y \in Son(x)}f_{y,j}\) ,边界为 \(f_{x,i}=i(i \in [1,dep_{x}-1] \land du_{x}=1)\)

  • 此时时间复杂度为 \(O(n^{3})\) ,考虑进一步优化。手摸展开后的式子,容易有 \(f_{x,i}=f_{x,i-1}+\prod\limits_{y \in Son(x)}f_{y,i+1}\) 。此时时间复杂度就优化成了 \(O(n^{2})\)

  • 最终,有 \(\prod\limits_{x \in Son(n)}f_{x,1}\) 即为所求。

    点击查看代码
    const ll p=998244353;
    int dep[3010],f[3010][3010];
    vector<int>e[3010];
    void add(int u,int v)
    {
        e[u].push_back(v);
    }
    void dfs(int x,int fa)
    {
        dep[x]=dep[fa]+1;
        for(int i=0;i<e[x].size();i++)
        {
            dfs(e[x][i],x);
        }
        for(int k=1;k<=dep[x]-1;k++)
        {
            f[x][k]=1;
            for(int i=0;i<e[x].size();i++)
            {
                f[x][k]=1ll*f[x][k]*f[e[x][i]][k+1]%p;
            }
            f[x][k]=(f[x][k]+f[x][k-1])%p;
        }
    }
    int main()
    {
    #define Issac
    #ifdef Issac
        freopen("reflection.in","r",stdin);
        freopen("reflection.out","w",stdout);
    #endif
        int n,u,v,ans=1,i;
        cin>>n;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            if(u<v)
            {
                swap(u,v);
            }
            add(u,v);
        }
        dfs(n,0);
        for(i=0;i<e[n].size();i++) 
        {
            ans=1ll*ans*f[e[n][i]][1]%p;
        }
        cout<<ans<<endl;
        return 0;
    }
    

\(T3\) C. 摸鱼军训 \(0pts/0pts\)

  • 部分分

    • \(20 \%\)\(O(n^{2})\) 预处理 \(O(1)\) 查询。
  • 正解

    • 先特判掉 \(k \ge n-x+1\) 的情况。
    • \(pre_{x}\) 表示 \(x\) 前面 \(>x\) 的数的个数,显然在 \(k\) 轮冒泡排序后会有 \(pre_{x} \gets \max(0,pre_{x}-k)\)
    • 在经过 \(k\) 轮冒泡排序后若 \(pre_{a_{i}} \ge k\) ,则 \(a_{i}\) 每轮冒泡排序后都会向前移动一次,到达 \(i-k\) 的位置;否则将所有的 \(pre_{a_{i}}<k\)\(a_{i}\) 升序排序后依次放入剩余的位置中(在将 \(pre_{a_{i}}=0\) 后每轮冒泡排序后都会到达 \(a_{i}\) 后面第一个比 \(a_{i}\) 大的数的前面,即比 \(a_{i}\) 大的数中顺序第 \(k\) 个位置向前移动了 \(k\) 次)。
    • 离线下来将询问挂在 \(x\) 上线段树维护单点修改、区间查询、线段树上二分即可。
    点击查看代码
    struct SMT
    {
        struct SegmentTree
        {
            int sum;
        }tree[2000010];
        int lson(int x)
        {
            return x*2;
        }
        int rson(int x)
        {
            return x*2+1;
        }
        void pushup(int rt)
        {
            tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum;
        }
        void update(int rt,int l,int r,int pos,int val)
        {
            if(l==r)
            {
                tree[rt].sum+=val;
                return;
            }
            int mid=(l+r)/2;
            if(pos<=mid)
            {
                update(lson(rt),l,mid,pos,val);
            }
            else
            {
                update(rson(rt),mid+1,r,pos,val);
            }
            pushup(rt);
        }
        int query(int rt,int l,int r,int x,int y)
        {
            if(x<=l&&r<=y)
            {
                return tree[rt].sum;
            }
            int mid=(l+r)/2,ans=0;
            if(x<=mid)
            {
                ans+=query(lson(rt),l,mid,x,y);
            }
            if(y>mid)
            {
                ans+=query(rson(rt),mid+1,r,x,y);
            }
            return ans;
        }
        int kth(int rt,int l,int r,int k)
        {
            if(l==r)
            {
                return l;
            }
            int mid=(l+r)/2;
            if(k<=tree[lson(rt)].sum)
            {
                return kth(lson(rt),l,mid,k);
            }
            else
            {
                return kth(rson(rt),mid+1,r,k-tree[lson(rt)].sum);
            }
        }
    }T[2];
    int a[500010],pre[500010],pos[500010],ans[500010];
    vector<pair<int,int> >q[500010];
    int main()
    {
    #define Issac
    #ifdef Issac
        freopen("bubble.in","r",stdin);
        freopen("bubble.out","w",stdout);
    #endif
        int n,m,k,x,i,j;
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            pre[i]=T[1].query(1,1,n,a[i],n);
            pos[a[i]]=i;
            T[1].update(1,1,n,a[i],1);
        }
        scanf("%d",&m);
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&k,&x);
            if(k>=n-x+1||pre[pos[x]]>=k)
            {
                ans[i]=(k>=n-x+1)?x:pos[x]-k;
            }
            else
            {
                q[x].push_back(make_pair(k,i));	
            }
        }
        for(i=n;i>=1;i--)
        {
            for(j=0;j<q[i].size();j++)
            {
                ans[q[i][j].second]=T[0].kth(1,1,n,q[i][j].first)-q[i][j].first;
            }
            T[0].update(1,1,n,pos[i],1);
        }
        for(i=1;i<=m;i++)
        {
            printf("%d\n",ans[i]);
        }
        return 0;
    }
    

\(T4\) D. 神奇园艺师 \(0pts/0pts\)

  • 部分分

  • 正解

    • 对于质数 \(p\) ,设 \(c_{i}\) 表示 \(a_{i}\) 质因数分解后 \(p\) 的指数,然后将其升序排序。
    • 考虑将货仓选址问题中的 \(\pm\) 中位数差分掉,考虑前一半和后一半每个数单独的贡献。
    • 设在 \(c_{i}\) 左边选择了 \(a \in [0,i-1]\) 个数,在右边选择了 \(b \in [0,n-i]\) 个数,其单个贡献为 \(\begin{cases} -c_{i} & a<b \\ 0 & a=b \\ c_{i} & a>b \end{cases}\) ,总贡献为 \(\begin{cases} -c_{i} \dbinom{i-1}{a}\dbinom{n-i}{b} & a<b \\ 0 & a=b \\ c_{i} \dbinom{i-1}{a}\dbinom{n-i}{b} & a>b \end{cases}\)
    • 先只考虑 \(a<b\) 时的贡献,有 \(\begin{aligned} &\sum\limits_{b=0}^{n-i}\sum\limits_{a=0}^{\min(b-1,i-1)}-c_{i}\dbinom{i-1}{a}\dbinom{n-i}{b} \\ &=\sum\limits_{d=1}^{n-i}\sum\limits_{a=0}^{\min(n-i-d,i-1)}-c_{i}\dbinom{i-1}{a}\dbinom{n-i}{a+d} \\ &=\sum\limits_{d=1}^{n-i}\sum\limits_{a=0}^{\min(n-i-d,i-1)}-c_{i}\dbinom{i-1}{a}\dbinom{n-i}{n-i-a-d} \\ &=\sum\limits_{d=1}^{n-i}-c_{i}\dbinom{n-1}{n-i-d} \\ &=\sum\limits_{d=0}^{n-i-1}-c_{i}\dbinom{n-1}{d} \end{aligned}\) 即为所求。
      • 第三、四步之间等价于范德蒙德卷积 \(\sum\limits_{i=0}^{s}\dbinom{n}{i}\dbinom{m}{s-i}=\dbinom{n+m}{s}\) ,考虑组合意义即可。
    • 同理,当 \(a>b\) 时的贡献为 \(\sum\limits_{d=0}^{i-2}c_{i}\dbinom{n-1}{d}\)
    • 预处理组合数的前缀和即可。
    点击查看代码
    const ll p=1000000007;
    ll prime[80010],pos[1000010],vis[1000010],a[1000010],jc[1000010],inv[1000010],jc_inv[1000010],s[1000010],len=0;
    vector<ll>c[80010];
    void isprime(ll n)
    {
        memset(vis,0,sizeof(vis));
        for(ll i=2;i<=n;i++)
        {
            if(vis[i]==0)
            {
                len++;
                prime[len]=i;
                pos[i]=len;
            }
            for(ll j=1;j<=len&&i*prime[j]<=n;j++)
            {
                vis[i*prime[j]]=1;
                if(i%prime[j]==0)
                {
                    break;
                }
            }
        }
    }
    void divide(ll n)
    {
        for(ll i=1;i<=len&&prime[i]*prime[i]<=n;i++)//只筛到 sqrt(n)
        {
            if(n%prime[i]==0)
            {
                int cnt=0;
                while(n%prime[i]==0)
                {
                    cnt++;
                    n/=prime[i];
                }
                c[i].push_back(cnt);
            }
        }
        if(n>1)
        {
            c[pos[n]].push_back(1);
        }
    }
    ll C(ll n,ll m,ll p)
    {
        return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0;
    }
    int main()
    {
    #define Issac
    #ifdef Issac
        freopen("game.in","r",stdin);
        freopen("game.out","w",stdout);
    #endif
        ll n,ans=0,sum,i,j,k;
        scanf("%lld",&n); 
        isprime(1000000);
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            divide(a[i]);
        }
        jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=inv[1]=1;
        for(i=2;i<=n;i++)
        {
            inv[i]=(p-p/i)*inv[p%i]%p;
            jc[i]=jc[i-1]*i%p;
            jc_inv[i]=jc_inv[i-1]*inv[i]%p;
        }
        s[0]=C(n-1,0,p);
        for(i=1;i<=n-1;i++)
        {
            s[i]=(s[i-1]+C(n-1,i,p))%p;
        }
        for(i=1;i<=len;i++)
        {
            sort(c[i].begin(),c[i].end());
            for(j=0;j<c[i].size();j++)
            {	
                k=n-c[i].size()+1+j;//补上前面的 0 
                sum=0;
                if(k-2>=0)
                {
                    sum=(sum+s[k-2])%p;
                }
                if(n-k-1>=0)
                {
                    sum=(sum-s[n-k-1]+p)%p;
                }	
                ans=(ans+sum*c[i][j]%p)%p;
            }
        }
        printf("%lld\n",ans);
        return 0;
    }
    

总结

  • \(T1\) 赛时觉得因为 \(k\) 的变化会导致最短路路径的变化然后就不能确定最短路长度了,没搞清其内部的单调性。
  • \(T2\) 始终没读懂题意。
  • \(T3\) 忘了可以 \(O(n^{2})\) 预处理,最低档部分分只写了单组询问 \(O(n^{2})\) 的做法,挂了 \(20pts\)
  • \(T4\) 质因数分解写的是预处理素数挨个分解的方法,导致无用素数极多,挂了 \(20pts\)
posted @ 2024-11-12 21:11  hzoi_Shadow  阅读(60)  评论(3编辑  收藏  举报
扩大
缩小