2024初三年后集训模拟测试3

前言

难度不好说,感觉是东拼西凑的题,但是除了 \(T1\) 都相当不简单。

  • \(T1~100pts:\)

    签到题,贪心即可。

  • \(T2~70pts:\)

    读假题了,是最大 \(w_i\) 不是固定 \(w_i\) ,做法是 二分答案+DP ,不过需要单调队列优化,不会这玩意儿赛后学了好久 \(qwq\) 。

    但是读假题了还能拿 \(70pts\) 。

  • \(T3~0pts:\)

    不会,想到了树形 \(DP\) ,但建树都没建明白。

    题解给了一半,正解没给,给了个复杂度 \(O(n^2)\) 的要过 \(3e5\) ,差评!

  • \(T4~0pts:\)

    本来人手骗 \(20pts\) 的,\(continue\) 加错地方了,喜提 \(0pts\) 。

    题解更加抽象,教练发现快吃晚饭了还没有人 \(A\) ,直接又把代码放里面了;至今 2024年02月21日20:55:27 还是没人 \(A\) ,又搞来一份详细题解,把代码加上了注释。

    可怜其良苦用心,但那篇题解码风太抽象了......

T1 排序

  • 题意:

    给定 \(4n\) 个数 \(s_i\) ,分别 \(n\) 组,对于每组 \(4\) 个数 \(a,b,c,d\) ,求所有组 \(|ab-cd|\) 的最大和。

  • 解法:

    • 导入一个常识:

      已知 \(s\) ,将其拆分为 \(s=a+b\) ,若想 \(a\times b\) 尽可能小,则 \(|a-b|\) 尽可能大;若想 \(a\times b\) 尽可能大,则 \(|a-b|\) 尽可能小。

      和相同周长的 \(S_{正方形}>S_{长方形}\) 是一个道理。

    先将所有 \(s_i\) 排序,设 \(ab>cd\) 。

    将所有 \(s_i\) 分为大的和小的两组,\(a,b\) 相邻搭配,\(c,d\) 分别从两边取即可。

    直接看代码就行了,签到题。

点击查看代码

cpp 复制代码
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
int n,a[N],ans,sum;
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    for(int i=1;i<=4*n;i++)
        read(a[i]);
    stable_sort(a+1,a+1+4*n);
    for(int i=1,j=4*n;i<=n,j>=2*n+1;i++,j-=2)
        ans+=a[j]*a[j-1]-a[i]*a[2*n-i+1];
    cout<<ans;
}

T2 牛吃草

  • 题意:

此处注意他是延伸距离最大为 \(w_i\) ,不是固定 \(w_i\) ,赛时因为这个读假题了 \(qwq\) 。

  • 部分分:

    • \(70pts:\)

      读假题的情况,二份答案+ \(O(n)的DP\) 。

      定义 \(f[i][1]\) 为第 \(i\) 个位置上放的最大覆盖长度,反之 \(f[i][0]\) 就是不放的情况。

      转移方程为:

      \(f[i][1]=\max(f[i-len[i]][1],f[i-len[i]][0])+len[i];\)

      \(f[i][0]=\max(f[i-1][1],f[i-1][0])\) 。

      此处 \(len_i\) 为其真实的 \(w_i\) ,因为他左端点顶到头就 \(1\) 了,再往前就负了,比如不管 \(w_1\) 为多少,\(len_1\) 都 \(=1\) 。

      因为读假题了, \(DP\) 自然成 \(O(n)\) 了。

      复杂度最坏情况下 \(O(n\log(n))\) ,实则只要 \(f_i>=s\) 就说明满足,跳出就行了,所以真实复杂度远小于此。
      点击查看代码

      cpp 复制代码
      #include<bits/stdc++.h>
      #define int long long 
      #define endl '\n'
      using namespace std;
      const int N=5e5+10;
      template<typename Tp> inline void read(Tp&x)
      {
          x=0;register bool z=1;
          register char c=getchar();
          for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
          for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
          x=(z?x:~x+1);
      }
      int n,s,l,r,maxx,mid,ans,w,f[N][2];
      struct aa
      {
          int l,len;
      }a[N];
      bool DP(int x,int s)
      {
          for(int i=1;i<=n;i++)
              f[i][0]=f[i][1]=0;
          for(int i=1;i<=n;i++)
          {
              if(a[i].len>=x) 
                  f[i][1]=max(f[a[i].l-1][1],f[a[i].l-1][0])+a[i].len;
              f[i][0]=max(f[i-1][1],f[i-1][0]);
              if(f[i][1]>=s||f[i][0]>=s) return 1;
          }
          return 0;
      }
      signed main()
      {
          #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
          freopen("out.txt","w",stdout);
          #endif
          read(n);
          for(int i=1;i<=n;i++) 
              read(w),
              a[i].l=max(1ll,i-w+1),
              a[i].len=i-a[i].l+1,
              maxx=max(maxx,a[i].len);
          read(s);
          s=ceil(double(n*s/100.0));
          l=1,r=maxx;
          while(l<=r)
          {
              mid=(l+r)>>1;
              if(DP(mid,s)) ans=max(ans,mid),l=mid+1;
              else r=mid-1;
          }
          cout<<ans;
      }
    • \(90pts:\)

      把题读真了,发现还需要套一层循环。

      循环 \(j=\{i-len_i\sim i-size\}\) ,那么转移方程为:

      \(f[i][1]=\max(\{f[i][1],f[j][1]+i-j,f[j][0]+i-j\});\)

      \(f[i][0]\) 和 \(70pts\) 的一样。

      这样复杂度最坏就成了 \(O(n^2\log(n))\) ,虽然真实情况比这小,但还是会 \(TLE\) 两个点。
      点击查看代码

      cpp 复制代码
      #include<bits/stdc++.h>
      #define int long long 
      #define endl '\n'
      using namespace std;
      const int N=5e5+10;
      template<typename Tp> inline void read(Tp&x)
      {
          x=0;register bool z=1;
          register char c=getchar();
          for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
          for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
          x=(z?x:~x+1);
      }
      int n,s,l,r,maxx,mid,ans,w,f[N][2];
      struct aa
      {
          int l,len;
      }a[N];
      bool DP(int x,int s)
      {
          f[0][0]=f[0][1]=0;
          for(int i=1;i<=n;i++)
          {
              f[i][1]=0;
              for(int j=i-a[i].len;j<=i-x;j++)
                  f[i][1]=max({f[i][1],f[j][1]+i-j,f[j][0]+i-j});
              f[i][0]=max(f[i-1][1],f[i-1][0]);
              if(f[i][1]>=s||f[i][0]>=s) return 1;
          }
          return 0;
      }
      signed main()
      {
          #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
          freopen("out.txt","w",stdout);
          #endif
          read(n);
          for(int i=1;i<=n;i++) 
              read(w),
              a[i].l=max(1ll,i-w+1),
              a[i].len=i-a[i].l+1,
              maxx=max(maxx,a[i].len);
          read(s);
          s=ceil(double(n*s/100.0));
          l=1,r=maxx;
          while(l<=r)
          {
              mid=(l+r)>>1;
              if(DP(mid,s)) ans=max(ans,mid),l=mid+1;
              else r=mid-1;
          }
          cout<<ans;
      }
  • 正解:

    在 \(90pts\) 的做法上使用单调队列优化 \(DP\) 。

    ∵ 题目数据范围中给了 \(w_{i-1}\geq w_i-1\) ,

    ∴ \(i-1-w_{i-1}\leq i-1-(w_i-1)\) ,

    ∴ \(i-1-w_{i-1}\leq i-w_i\) 。

    由此,可以使用单调队列优化 \(DP\) 。

    至于这玩意之前给跳了 \(qwq\) ,反正会的肯定会的。
    点击查看代码

    cpp 复制代码
    #include<bits/stdc++.h>
    #define int long long 
    #define endl '\n'
    using namespace std;
    const int N=5e5+10;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=1;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    int n,s,l,r,maxx,mid,ans,w,f[N][2],t,head,tail,len[N];
    struct aa
    {
        int id,num;
    }q[N];
    bool DP(int x,int s)
    {
        head=1,tail=0;
        f[0][1]=f[0][0]=0;
        for(int i=1;i<=n;i++)
        {
            f[i][0]=f[i][1]=0;
            if(i>=x)
            {
                t=max(f[i-x][1],f[i-x][0])+n-(i-x);
                while(head<=tail&&q[tail].num<t) tail--;
                q[++tail].num=t;
                q[tail].id=i-x;
                while(head<=tail&&q[head].id<i-len[i]) head++;
            }
            f[i][0]=max(f[i-1][1],f[i-1][0]);
            f[i][1]=max(f[i][1],(head>tail?0:q[head].num)+i-n);
            if(f[i][1]>=s||f[i][0]>=s) return 1;
        }
        return 0;
    }
    signed main()
    {
        #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
        #endif
        read(n);
        for(int i=1;i<=n;i++) 
            read(w),
            l=max(1ll,i-w+1),
            len[i]=i-l+1,
            maxx=max(maxx,len[i]);
        read(s);
        s=ceil(double(n*s/100.0));
        l=1,r=maxx;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(DP(mid,s)) ans=max(ans,mid),l=mid+1;
            else r=mid-1;
        }
        cout<<ans;
    }

T3 树上的宝藏

  • 题意:

    对于一颗数上有 \(n\) 个节点,每个节点上填入 \(1\) 或 \(0\) ,\(n-1\) 条边。

    每个普通的边连接的两点最多只能有一个 \(1\) ,也就是 \([1,0],[0,1],[0,0]\) 三种情况。

    每个特殊的边连接的两点最少必须有一个 \(1\) ,也就是 \([1,0],[0,1],[1,1]\) 三种情况。

    求在第 \(i\) 条边为特殊边,其余为普通边的情况下可行的情况数量。

  • 部分分:

    • \(30pts:\) 纯暴力。

    • \(60pts:\)

      树形 \(DP\) 显然,下面说思路:

      对于第 \(i\) 条边,将其两节点 \(a,b\) 分别为根拆成两棵树,在每棵树上跑树形 \(DP\) 。

      定义 \(f[i][1]\) 为以 \(i\) 为根的数(子树)\(i\) 处为 \(1\) 的情况数,同样的 \(f[i][0]\) 就是 \(i\) 处为 \(0\) 的情况数。

      结合对于题意的分析,若根节点为 \(1\) ,则子节点必须为 \(0\) ;若根节点为 \(0\) ,则子节点可以为 \(1\) 或 \(0\) 。

      那么便有了转移方程,设 \(u,v\) 分别为根节点和叶节点:

      \(f[u][1]\times =f[v][0]\) ;

      \(f[u][0]\times =(f[v][1]+f[v][0])\) 。

      而问题来到怎么打又儿子向父亲传的树形 \(DP\) :

      先跑一遍 \(dfs\) ,处理出每个节点的 \(son\) 与 \(father\) ,然后根据此来跑树形 \(DP\) 就可以了,具体的看代码就理解了。

      最后处理答案:

      现在以 \(a,b\) 为根的树都处理完了,需要将他们合并。

      结合上面题意的分析,特殊边时,有 \(3\) 中情况:\([1,1],[1,0],[0,1]\) 。

      对应的,\(ans=f[a][1]\times f[b][1]+f[a][1]\times f[b][0]+f[a][0]\times f[b][1]\) 。

      就结束了,别忘了 \(\bmod ~P=998244353\) 。

      最后分析复杂度:不难发现要从 \(1\sim n-1\) 每次都要跑 \(dfs\) 和树形 \(DP\) ,每次都是 \(O(n)\) 的,这样复杂度就成了 \(O(n^2)\) 。
      点击查看代码

      cpp 复制代码
      #include<bits/stdc++.h>
      #define int long long 
      #define endl '\n'
      using namespace std;
      const int N=3e5+10,P=998244353;
      template<typename Tp> inline void read(Tp&x)
      {
          x=0;register bool z=1;
          register char c=getchar();
          for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
          for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
          x=(z?x:~x+1);
      }
      int n,x,y,tx[N],ty[N],f[N][2],ans,father[N];
      vector<int>e[N],son[N];
      void dfs(int x,int fa)
      {
          for(int y:e[x])
          {
              if(y==fa) continue;
              father[y]=x;
              son[x].push_back(y);
              dfs(y,x);
          }
      }
      void dp(int x)
      {
          if(!son[x].size())
          {
              f[x][0]=f[x][1]=1;
              return ;
          }
          for(int y:son[x])
              dp(y),
              (f[x][1]*=f[y][0])%=P,
              (f[x][0]*=(f[y][0]+f[y][1]))%=P;
      }
      signed main()
      {
          #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
          freopen("out.txt","w",stdout);
          #endif
          read(n);
          for(int i=1;i<n;i++)
              read(x),read(y),
              e[x].push_back(y),
              e[y].push_back(x),
              tx[i]=x,ty[i]=y;
          for(int i=1;i<n;i++)
          {
              int a=tx[i],b=ty[i];
              for(int j=0;j<=n;j++) 
                  father[j]=f[j][0]=f[j][1]=1,
                  son[j].clear();
              dfs(a,b),dp(a);
              dfs(b,a),dp(b);
              ans=(f[a][1]*f[b][1])%P+(f[a][1]*f[b][0])%P+(f[a][0]*f[b][1])%P;
              cout<<ans%P<<endl;
          }
      }
  • 正解:

    和上面思路是类似的,但是解决了上面要跑 \(n-1\) 遍 \(dfs,DP\) 的问题。

    先跑两边 \(dfs\) 和树形 \(DP\) ,转移方程和上面都是一样的,处理出 \(f[i][0||1],r[i][1||0]\)。

    然后就是处理答案;

    在 \(a,b\) 合并的同时去掉 \(b\) 对 \(a\) 的影响,这里默认 \(a,b\) 分别为父亲节点和子节点。

    去除影响的思路参考上面就可以了,就是把 \(b\) 对 \(a\) 的贡献除去。

    \(ans=\dfrac{f[a][1]\times f[b][1]}{f[b][0]}+\dfrac{f[a][1]\times f[b][0]}{f[b][0]}+\dfrac{f[a][0]\times f[b][1]}{f[b][0]+f[b][1]}\) 。

    这样有了除法又有模数,需要乘法逆元。

    思路就是这样的,可能有点模棱两可,可以去问 \(wkh\) ,他讲的正解,官方题解里压根没有正解。

    至于代码实现不会,因为不会处理第二遍 \(dfs\) 的换根问题,想要代码可以找 \(wkh\) 。

    复杂度比上面少了一个 \(n\) ,\(O(n)\) 。

    另外的思路跳转 @Ishar-zdl

    2024年02月21日21:53:38 ,\(T3\) 的官方题解被教练放进去了,(⊙o⊙)...,就是这份 \(\large{↑}\) 。

相关推荐
Tisfy5 天前
LeetCode 2741.特别的排列:状压DP
算法·leetcode·动态规划·题解·dp·状压dp
uanQ6 天前
背包DP——多重背包
dp
硕风和炜7 天前
【LeetCode:2742. 给墙壁刷油漆 + 递归 + 记忆化搜索 + dp】
java·算法·leetcode·缓存·dp·记忆化搜索·递归
Dearingxxx8 天前
蓝桥杯 经典算法题 求解01背包问题
算法·职场和发展·蓝桥杯·dp·01背包
Dearingxxx14 天前
蓝桥杯 经典算法题 求解完全背包问题
算法·职场和发展·蓝桥杯·dp·完全背包
Wzideng15 天前
93. 复原 IP 地址
java·数据结构·算法·深度优先·dp·回溯
lty_ylzsx1 个月前
平衡树 Treap & Splay [学习笔记]
线段树·字符串·dp·二分答案·splay·fhq_treap·treap
lty_ylzsx2 个月前
树链剖分[学习笔记]
lca·二分答案·树上差分
是基德吖2 个月前
【力扣】第397场周赛 A~C
算法·leetcode·动态规划·dp
Wzideng2 个月前
416. 分割等和子集
java·数据结构·算法·深度优先·dp