1 条题解

  • 0
    @ 2025-8-24 23:00:42

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar XiaoZi_qwq
    此程虽艰,但我不曾退却;即便孤身一人,也不会放弃。每当想起,不禁热泪盈眶

    搬运于2025-08-24 23:00:42,当前版本为作者最后更新于2024-07-26 20:20:49,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    P10729 [NOISG 2023 Qualification] Dolls 题解

    前言

    有点意思的思维题

    题意分析

    有若干个套娃,第 ii 个套娃的尺寸为 aia_i,一个套娃 xx 可以被 套娃 yy 嵌套当且仅当 ayax2a_y-a_x \geqslant 2。求对于前 ii 个套娃最多可以嵌套几次。

    提两个小问题:
    看到 ayax2a_y-a_x \geqslant 2,你想到了什么?
    遇到有点棘手的条件,第一步应该怎么做?

    逐步分析

    SubTask1:n200n\le200:最初的思考

    暴力解法:每次询问对 aia_i 排序并去重,记 fif_i表示到第 ii 个套娃时,最大嵌套次数。显然我们有
    $f_i= \begin{cases} f_{i-1}+1 &\text{if } a_{i-1}+1 \not= a_i\\ f_{i-2} +1 &\text{ if } a_{i-1}+1 = a_i \end{cases}$

    SubTask2:aia_i 都是奇数:特殊情况

    由于 aia_i 都是奇数,所以一定有 i,aiai12\forall_i,a_i-a_{i-1}\ge 2 那么我们就可以直接按第一种情况转移。
    但是这样是最优的吗?
    显然对于每一个之前没出现过aia_i,它都可以使答案增大 11 (可以参照样例2)。那么我们可以直接用一个数组记录一个数是否出现过,O(1)O(1) 计算贡献即可。

    SubTask3:aia_i 都不是 44 的倍数:提示

    因为 aia_i 都不是 44 的倍数,所以你记录数是否出现过的数组的值可能长这样( 11 表示出现过):
    [0,1,1,1,0,1,1,0,0,0,1,0,0][0,1,1,1,0,1,1,0,0,0,1,0,0]
    相当明显,若干连续的 11 形成了一组。对于每一个组,它们之间的贡献是叠加的,而且互不影响(不理解可以再看一看递推方程),它们的贡献只受它们的长度影响(因为我们并不关心这个组内 aia_i 的具体值)。
    所以我们可以枚举这个组的长度(显然只有 0,1,2,30,1,2,3 ,四种可能),并求出贡献即可。
    思考一个小问题:如果有一个数出现在了两个组之间(如 66 出现在 5577 之间),这个时候该怎么计算贡献呢?

    正解:扩展

    让我们沿着上面的思路继续思考,因为一个组的贡献只受其长度影响,那么我们只需要维护一个组的长度,而且由上面的枚举可以得知:一个长 lenlen 的组,它对答案的贡献为 len2\lceil \dfrac{len}{2} \rceil。(可以在组内手动推一把验证一下)。 而这个信息可以存储在组内任意一个节点上。
    那么现在我们回到了之前那个问题:当一个数出现在了两个组之间时,我们怎么计算贡献?
    分析一下我们需要干什么:我们需要通过前面一个点和后面一个点分别找到它们所在组负责存储信息的点,还需要将它们合并。有查有并,这不就是并查集嘛!
    通过并查集我们就可以快速进行组长的合并与维护。
    到这里题目就分析完了,具体实现看代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef pair<int,int> pii;
    inline int read(){
      register int x=0,f=1;
      char c=getchar();
      while(c<'0' || '9'<c) f=(c=='-')?-1:1,c=getchar();
      while('0'<=c && c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar();
      return x*f;
    }
    const int N=500010;
    int n,f[N],ans,sz[N];
    bool vis[N];
    inline int find(int x){ return f[x]=f[x]==x?x:find(f[x]);}
    inline void un(int x,int y){
      int fx=find(x),fy=find(y);
      if(fx==fy) return;
      f[fx]=fy;
      sz[fy]+=sz[fx];
    }
    int main()
    {
      for(int i=1;i<=500000;i++) f[i]=i,sz[i]=1;
      n=read();
      for(int i=1,x;i<=n;i++){
        x=read();
        if(vis[x]) {printf("%d ",ans);continue;}
        vis[x]=true;
        //分类讨论 x 所处位置,不同情况有不同的合并方式
        if(!vis[x+1] && !vis[x-1]) ans++;
        else if(!vis[x+1]) un(x-1,x),ans+=sz[find(x)]%2;
        else if(!vis[x-1]) un(x,x+1),ans+=sz[find(x)]%2;
        else{
          int f1=find(x-1),f2=find(x+1);
          ans-=(sz[f1]+1)/2+(sz[f2]+1)/2;//消除影响
          un(x-1,x),un(x,x+1);
          ans+=(sz[find(x)]+1)/2;//再次计算贡献
        }
        printf("%d ",ans);
      }
    
      return 0;
    }
    
    
    • 1

    信息

    ID
    10511
    时间
    1000ms
    内存
    1024MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者