暑假集训CSP提高模拟8

暑假集训CSP提高模拟8

组题人: @joke3579

\(T1\) P155.基础的生成函数练习题(gf) \(100pts\)

  • 原题: [ABC093C] Same Integers

  • 设通过操作 \(a,b,c\) 所能达到的最小整数为 \(x\)

  • 观察到对同两个数进行操作 \(2\) 等价于分别对这两个数各进行操作 \(1\) ,在 \(a,b,c\) 达到 \(x\) 的过程中优先使用操作 \(1\) ,不够的再用操作 \(1\) 来凑。

  • 最终,有 \(\dfrac{3x-a-b-c}{2}\) 即为所求,其中 \(x\) 是满足 \(\ge \max(a,b,c)\)\((3x-a-b-c) \bmod 2=0\) 的最小整数。

  • 直接枚举判断就行,可以证明枚举次数不会太多。

    点击查看代码
    #define endl '\n'
    int main()
    {
        ll a,b,c,i;
        cin>>a>>b>>c;
        for(i=max(a,max(b,c));;i++)
        {
            if((3*i-a-b-c)%2==0)
            {
                cout<<(3*i-a-b-c)/2<<endl;
                break;
            }
        }
        return 0;
    }
    

\(T2\) P156. 简单的拉格朗日反演练习题(lagrange) \(35pts\)

  • 原题: CF1706E Qpwoeirut and Vertices

  • 部分分

    • \(35pts\) :显然答案具有单调性,考虑二分答案 \(k\) 。可持久化并查集维护历史祖先节点并依次枚举 \([l,r]\) 判断,时间复杂度为 \(O(m \log^{2} n+nq \log n \log m)\)

      点击查看代码
      int a[200010];
      struct PDS_SMT
      {
          int root[200010],rt_sum=0;
          struct SegmentTree
          {
              int ls,rs,fa,dep;
          }tree[200010<<5];
          #define lson(rt) tree[rt].ls
          #define rson(rt) tree[rt].rs
          int build_rt()
          {
              rt_sum++;
              return rt_sum;
          }
          void build_tree(int &rt,int l,int r)
          {
              rt=build_rt();
              if(l==r)
              {
                  tree[rt].fa=l;
                  tree[rt].dep=0; 
                  return;
              }
              int mid=(l+r)/2;
              build_tree(lson(rt),l,mid);
              build_tree(rson(rt),mid+1,r);
          }
          void update(int pre,int &rt,int l,int r,int pos,int val,int pd)
          {
              rt=build_rt();
              tree[rt]=tree[pre];
              if(l==r)
              {
                  tree[rt].fa=(pd==0)?val:tree[rt].fa;
                  tree[rt].dep+=(pd==1)*val;
                  return;
              }
              int mid=(l+r)/2;
              if(pos<=mid)
              {
                  update(lson(pre),lson(rt),l,mid,pos,val,pd);
              }
              else
              {
                  update(rson(pre),rson(rt),mid+1,r,pos,val,pd);
              }
          }
          int query(int rt,int l,int r,int pos)
          {
              if(l==r)
              {
                  return rt;
              }
              int mid=(l+r)/2;
              if(pos<=mid)
              {
                  return query(lson(rt),l,mid,pos);
              }
              else
              {
                  return query(rson(rt),mid+1,r,pos);
              }
          }
      }T;
      struct PDS_DSU
      {
          int find(int rt,int x,int n)	
          {
              int fa=T.query(rt,1,n,x);
              return (T.tree[fa].fa==x)?fa:find(rt,T.tree[fa].fa,n);
          }
          void merge(int pre,int &rt,int x,int y,int n)
          {
              x=find(rt,x,n);
              y=find(rt,y,n);
              if(T.tree[x].fa!=T.tree[y].fa)
              {
                  if(T.tree[x].dep>T.tree[y].dep)
                  {
                      swap(x,y);
                  }
                  T.update(pre,rt,1,n,T.tree[x].fa,T.tree[y].fa,0);
                  if(T.tree[x].dep==T.tree[y].dep)
                  {
                      T.update(rt,rt,1,n,T.tree[y].fa,1,1);
                  }
              }
          }
      }D;
      bool check(int mid,int u,int v,int n)
      {
          int fa=D.find(T.root[mid],u,n);
          for(int i=u+1;i<=v;i++)
          {
              if(fa!=D.find(T.root[mid],i,n))
              {
                  return false;
              }
          }
          return true;
      }
      int main()
      {
          int n,m,q,u,v,l,r,mid,ans,i;
          scanf("%d%d%d",&n,&m,&q);
          T.build_tree(T.root[0],1,n);
          for(i=1;i<=m;i++)
          {
              scanf("%d%d",&u,&v);
              T.root[i]=T.root[i-1];
              D.merge(T.root[i-1],T.root[i],u,v,n);
          }
          for(i=1;i<=q;i++)
          {
              scanf("%d%d",&u,&v);
              l=0;
              r=m;
              ans=0;
              while(l<=r)
              {
                  mid=(l+r)/2;
                  if(check(mid,u,v,n)==true)
                  {
                      ans=mid;
                      r=mid-1;
                  }
                  else
                  {
                      l=mid+1;
                  }
              }
              printf("%d\n",ans);
          }
          return 0;
      }
      
    • \(80pts\) :维护历史节点并不是最优做法,考虑记录当前属于第几个连通块,主席树维护历史版本,线段树上子节点信息在上传至父亲节点时判断是否均属于一个连通块,方便查询。由于合并时需要启发式合并,主席树需要多点修改,时间复杂度为 \(O(m \log^{2} n+q \log n \log m)\)

      • 因为学校 \(OJ\) 跑得慢所以过不了。
  • 正解

    • 将时间作为这条边的权值。
    • \([l,r]\) 两两连通等价于 \(\forall i \in [l,r),i\)\(i+1\) 连通。
    • \(f_{u,v}\) 表示 \(u \to v\) 的最大边权的最小值,跑 \(Kruskal\) 重构树维护即可,做法同 luogu P2245 星际导航 。那么 \(\max\limits_{i=l}^{r-1}\{ f_{i,i+1} \}\) 即为所求,挂个 \(ST\) 表维护即可。
    点击查看代码
    struct DSU
    {
        int fa[400010];
        int find(int x)
        {
            return (fa[x]==x)?x:fa[x]=find(fa[x]);
        }
    }D;
    struct ST
    {
        int fmaxx[400010][25];
        void init(int n,int a[])
        {
            for(int i=1;i<=n;i++)
            {
                fmaxx[i][0]=a[i];
            }
            for(int j=1;j<=log2(n);j++)
            {
                for(int i=1;i<=n-(1<<j)+1;i++)
                {
                    fmaxx[i][j]=max(fmaxx[i][j-1],fmaxx[i+(1<<(j-1))][j-1]);
                }
            }
        }
        int query(int l,int r)
        {
            int t=log2(r-l+1);
            return max(fmaxx[l][t],fmaxx[r-(1<<t)+1][t]);
        }
    }T;
    struct node
    {
        int nxt,from,to,w;
    }e[400010],E[400010];
    int head[400010],dep[400010],fmaxx[400010][25],fa[400010][25],f[400010],N,cnt=0;
    bool cmp(node a,node b)
    {
        return a.w<b.w;
    }
    void add(int u,int v,int w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void kruskal(int m)
    {
        int x,y,i;
        sort(E+1,E+1+m,cmp);
        for(i=1;i<=m;i++)
        {
            x=D.find(E[i].from);
            y=D.find(E[i].to);
            if(x!=y)
            {
                D.fa[x]=y;
                add(E[i].from,E[i].to,E[i].w);
                add(E[i].to,E[i].from,E[i].w);
            }
        }
    }
    void dfs(int x,int father,int w)
    {
        fa[x][0]=father;
        dep[x]=dep[father]+1;
        fmaxx[x][0]=w;
        for(int i=1;(1<<i)<=dep[x];i++)//原题为避免清空带来的时间影响,这里需要遍历到 N
        {
            fa[x][i]=fa[fa[x][i-1]][i-1];
            fmaxx[x][i]=max(fmaxx[x][i-1],fmaxx[fa[x][i-1]][i-1]);
        }
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=father)
            {
                dfs(e[i].to,x,e[i].w);
            }
        }
    }
    int lca(int x,int y)
    {
        if(D.find(x)!=D.find(y))
        {
            return -1;
        }
        else
        {
            int maxx=0;
            if(dep[x]>dep[y])
            {
                swap(x,y);
            }
            for(int i=N;i>=0;i--)
            {
                if(dep[x]+(1<<i)<=dep[y])
                {
                    maxx=max(maxx,fmaxx[y][i]);
                    y=fa[y][i];
                }
            }
            if(x==y)
            {
                return maxx;
            }
            else
            {
                for(int i=N;i>=0;i--)
                {
                    if(fa[x][i]!=fa[y][i])
                    {
                        maxx=max(maxx,max(fmaxx[x][i],fmaxx[y][i]));
                        x=fa[x][i];
                        y=fa[y][i];
                    }
                }
                maxx=max(maxx,max(fmaxx[x][0],fmaxx[y][0]));
                return maxx;
            }
        }
    }
    int main()
    {
        int n,m,q,u,v,i;
        cin>>n>>m>>q;
        N=log2(n)+1;
        for(i=1;i<=m;i++)
        {
            cin>>E[i].from>>E[i].to;
            E[i].w=i;
        }
        for(i=1;i<=n;i++)
        {
            D.fa[i]=i;
        }
        kruskal(m);
        for(i=1;i<=n;i++)
        {
            if(dep[i]==0)
            {
                dfs(i,0,0);
            }
        }
        for(i=1;i<=n-1;i++)
        {
            f[i]=lca(i,i+1);
        }
        T.init(n,f);
        for(i=1;i<=q;i++)
        {
            cin>>u>>v;
            cout<<((u==v)?0:T.query(u,v-1))<<endl;
        }
        return 0;
    }
    

\(T3\) P157. 容易的多元拉格朗日反演练习题(multi) \(10pts\)

  • 原题: [AGC056D] Subset Sum Game

  • 部分分

    • \(10pts\) :输出样例 \(1\)
  • 正解

    • \(\{ a \}\) 升序排序。
    • \(sum=\sum\limits_{i=1}^{n}a_{i},t=sum-s\)
    • \(s \in [l,r]\) 不太好做,考虑将式子拆拆加加成能做的形式。将 \(s\) 乘以 \(2\) 并减去 \(sum\) 后有 \(2s-t \in [2l-sum,2r-sum]\)
    • \(x=sum-l-r\) ,再次加上 \(x\)\(x+s-t \in [l-r,r-l]\) ,即 \(|x+s-t| \le r-l\)
    • 此时得到了博弈策略: Alice 想让 \(|x+s-t|\) 尽可能小, Bob 想让 \(|x+s-t|\) 尽可能大。
    • 考虑下这个式子的实际意义是要求 \(x+s\)\(t\) 尽可能接近。
    • \(x=0\) 时实际 Alice / Bob 会选择奇数位/偶数位上的数,拆掉绝对值,最后得到的结果为 \(|x+s-t|=\sum\limits_{i=1}^{n}(-1)^{i \bmod 2} \times a_{i}\)
      • 实际上是临项差分。
    • \(x \ne 0\) 时取任意一个 \(a_{i}\)\(a_{i}+x\) 代替后再和上面一样做即可。
    • 证明有些麻烦,能感性理解就感性理解,建议去看 luogu 题解
    点击查看代码
    ll a[5010];
    vector<ll>q;
    int main()
    {
        ll t,n,l,r,x,ans,sum,i,j,k;
        cin>>t;
        for(k=1;k<=t;k++)
        {
            cin>>n>>l>>r;
            x=0;
            ans=0x7f7f7f7f7f7f7f7f;
            for(i=1;i<=n;i++)
            {
                cin>>a[i];
                x+=a[i];
            }
            x-=l+r;   
            sort(a+1,a+1+n);
            for(i=1;i<=n;i++)
            {
                sum=0;
                q.clear();
                for(j=1;j<=n;j++)
                {
                    if(i!=j)
                    {
                        q.push_back(a[j]);
                    }
                }
                q.insert(lower_bound(q.begin(),q.end(),a[i]+x),a[i]+x);
                for(j=0;j<q.size();j+=2)
                {
                    sum+=q[j+1]-q[j];
                }
                ans=min(ans,sum);
            }
            if(ans<=r-l)
            {
                cout<<"Alice"<<endl;
            }
            else
            {
                cout<<"Bob"<<endl;
            }
        }
        return 0;
    }
    

\(T4\) P158. 朴素的抽象代数题(algebra) \(0pts\)

  • 不会置换,所以以下部分贺的官方题解。

    定义在集合 \(\mathbb{P}\) 上的置换为一个 \(\mathbb{P}\rightarrow \mathbb{P}\) 的双射 \(f\)。容易发现,任何一个置换 \(f\) 都可以被大小为 \(|\mathbb{P}|\) 的排列 \(p\) 表示。由置换的定义自然导出单位置换 \(e\) 与置换的复合运算。定义 \(f^k\)\(f\) 自复合 \(k\) 次后得到的置换。对于置换 \(f\),若 \(f^k = e\)\(k(k>0)\) 极小,就称 \(k\)\(f\) 的周期。

    对于一个 \(\mathbb{P}\) 上的置换 \(f\) 对应的排列 \(p\) ,我们在一个 \(\mathbb{|P|}\) 个点的图上,由 \(i\)\(p_i\) 连边,会得到一个由环构成的有向图,这称作 \(f\) 的置换环图,其中的环称作 \(f\) 的一个置换环。

    断言:\(f\) 的周期为所有置换环大小的最小公倍数。

    证明:

    \(f\) 仅有一个置换环时定理显然成立,因为每个节点都需要环大小长度的遍历后才能回到其本身。

    首先考察 \(f\) 仅有两个置换环的情况。设这两个环的大小为 \(a\)\(b\)。当 \(a = b\) 时退化到一个置换环的情况,显然成立。因此考虑 \(a \neq b\)。不妨设 \(a < b\)

    我们需要的是同时遍历完两个环的最小长度。现讨论遍历两个环的过程中位置的同步度。不妨设遍历 \(\frac{a}{\text{period}(a,b)}\)\(b\) 环后 \(a\) 环被遍历完。容易发现定义是良的,因为当 \(\text{period}(a,b) = 1\) 时显然成立。

    考虑当遍历完一个环后在另一个环上余下的长度 \(b - a\)

    \(b - a \mid a\),那 \(\text{period}(a,b) = b - a\)。因此有

    \[\text{总周期} = \frac{a}{\text{period}(a,b)}\times b = \frac{a}{b - a} \times b = \frac{a \times b}{b - a} = \frac{a \times b}{\gcd(a,b)} = \text{lcm}(a,b) \]

    考虑 \(b - a\nmid a\)。由于现在需要让 \(a\)\(b - a\) 同步,因此我们现在需要解决的子问题表述出了 \(\text{period}(a,b) = \text{period}(a,a-b)\)。根据更相减损术,这表明 \(\text{period}(a,b) = \gcd(a,b)\)。这也表示了 \(b - a \mid a\) 是当前情况的特解。

    因此当只存在两个环时有

    \[\text{总周期} = \frac{a}{\text{period}(a,b)}\times b = \frac{a \times b}{\gcd(a,b)} = \text{lcm}(a,b) \]

    同时这给出了将两个大小任意的环归约成一个等价环的方法。

    对于 \(k(k >2)\) 个环的情况,可以先将两个环归约为一个环,这就转化到了 \(k-1\) 个环的情况。根据数学归纳法,该断言成立。\(\square\)

    我们将按操作序列顺序执行完一个循环节后得到的置换称作 \(f_0\)。由置换环定理,这个置换在 \(\text{M} = \text{lcm}\{\text{各环长度}\}\) 次自映射后得到单位映射。

    这说明了操作序列每个位置对应的置换是有循环节的。我们只需要记录 \(f_0,f^2_0,\dots,f^\text{M}_ 0\) 以及操作序列一个循环节中,每个位置的置换,即可将每个位置对应的置换转换成去除循环节后的部分。单次询问只需要合并 \(O(1)\) 个置换。

    总时间复杂度 \(O((n + \text{M} + q)L)\)

总结

  • \(T2\) 没想到把时间作为边权的 \(Trick\) ,直接现学现用,写可持久化并查集了,貌似和普通暴力一个分。
  • \(T3\) 赛时以为会和 luogu P7137 [THUPC2021 初赛] 切切糕 一样,遂从 Bob 的角度分析,把 \(s \notin [l,r]\) 拆成了 \(n-s \in [0,l) \lor n-s \in (r,n]\) 然后口胡了个假结论,仅过了小样例。
  • \(T4\) 因为上一道与置换相关的题 tgHZOJ 2197.置换 没写,遂直接弃了,赛后听别人说貌似是大模拟。

后记

posted @ 2024-07-26 15:49  hzoi_Shadow  阅读(58)  评论(0编辑  收藏  举报
扩大
缩小