初三奥赛模拟测试2

前言

image

  • \(T1:\)

    概率期望,本来没学过,现学的(蓝书没看懂,还是网上的博客好理解),然后发现毕竟 \(T1\) 没那么难,知道概率期望是啥还是能做的。

  • \(T2:\)

    本来看 \(T1\) 概率期望想先开 \(T2\) 的,但是发现不会就去学概率期望了,后来发现树形 \(DP\) 可做,本来想贪心的但是没贪出来,听说可以贪心做法。

  • \(T3,T4:\)

    没啥好说的,不会。

    但是 \(T3\) 输出 \(q\)\(0\) 即可得 \(1\) 分。

  • 官方题解奉上

  • 不是有模拟赛你们也不知道来 \(1\) 机房告诉我一声?喵的 \(14:30\) 看到有个 进行中

T1 南

  • 原题:收集邮票(洛谷 P4550)

  • 前言:

    本来对概率期望仅限于了解,根本不会……

    但是 \(T1\) 就不会非常不甘心,开下面的题发现也不太会 \(qwq\)

    于是就去网上找博客现学了一下(虽然学得十分不透彻)。

    然后发现这题貌似没那么难,就基本的概率期望,唯独这个每次价格会改变比较恶心。

    然后毕竟是现学的,还有个地方不明白:对于他每次价格都是 \(+1\) ,满足等差数列,那么总钱数理论上 \(=\dfrac{(num+1)num}{2}\)\(num\) 是购买次数)才对啊,但是用这个式子连样例都过不去,请求大佬解释一下 \(qwq\)

  • 简化题意:

    \(n\) 类武器,每次买一把,买到每一类的概率均为 \(\dfrac{1}{n}\) ,第 \(k\) 次支付需要花 \(k\) 元,求总钱数的期望。

  • 解法:

    概率期望 \(+DP\)

    先思考每一次买都是 \(1\) 元的怎么搞。

    那么钱数的期望就等于购买次数的期望。

    • 什么是概率期望:

      无语了 \(oi-wiki\) 上没找到。

      • 概率:

        \(whk\) 那边都学过了,不解释了。

      • 期望:

        可以简单理解为加权平均数。

        对于一个场景有 \(n\) 个事件,每个事件发生的概率为 \(p_i\) ,每件事的影响为 \(x_i\)

        那么该场景总影响的期望即为 \(\sum\limits_{i=1}^nq_i\times x_i\)

    • 购买次数的期望:

      对于大多数的概率期望 \(DP\) 都是从后往前推比较好推的(现学现用了属于是)。

      对于概率期望 \(DP\) 的基本概念(单指从后往前推)是: 已经……还需要……的期望

      定义 \(f_i\) 为已经购买了 \(i\) 类武器,还需要 \(f_i\) 的购买次数期望值。

      那么根据定义显然 \(f_n=0\)

      下面思考状态转移方程:

      分两种情况讨论:

      • 买到没有买到过的种类:

        首先这种情况概率为 \(\dfrac{n-i}{n}\)

        那么既然买到新的了,当前买到的种数就变为 \(i+1\) 了,还需要的期望值就是 \(f_{i+1}\) ,那么转移方程为:

        \(f_i=\dfrac{n-i}{n}\times (f_{i+1}+1)\)

        至于这个 \(+1\) ,这个 \(f_i\) 表示的为购买次数的期望,显然购买次数 \(+1\) 了。

      • 买到之前已经买到过的种类:

        首先这种情况概率为 \(\dfrac{i}{n}\)

        买到重复的了,那么显然还是需要 \(f_i\) 的期望值,同时购买次数 \(+1\) ,那么转移方程为:

        \(f_i=\dfrac{i}{n}\times (f_i+1)\)

      最后将两个结合起来,就是:

      \(f_i=\dfrac{n-i}{n}\times (f_{i+1}+1)+\dfrac{i}{n}\times (f_i+1)\)

      需要给他化简一下,而且不化简是不行的,在程序里 \(f_i\) 还是初始值 \(0\) 呢,拿脚想在这个式子里他也不应该是 \(0\) ,所以需要化简成等式右面没有 \(f_i\) 的形式。

      \(f_i=\dfrac{n-i}{n}\times (f_{i+1}+1)+\dfrac{i}{n}\times (f_i+1)\)

      \(f_i=\dfrac{n-i}{n}\times f_{i+1}+\dfrac{n-i}{n}+\dfrac{i}{n}\times f_i+\dfrac{i}{n}\)

      \(f_i=\dfrac{n-i}{n}\times f_{i+1}+\dfrac{i}{n}\times f_i+1\)

      \(\dfrac{n-i}{n}\times f_i=\dfrac{n-i}{n}\times f_{i+1}+1\)

      \(f_i=f_{i+1}+\dfrac{n}{n-i}\)

      由此得出购买次数的期望。

    那么对于所有价格都为 \(1\) 元的情况答案就是 \(f_0\) 了。

    接下来思考他这个恶心的价格怎么搞。

    本来想的是直接 \(\dfrac{(f_0+1)\times f_0}{2}\) 的,但是发现不对,也不知道为啥,求大佬教教。

    需要一个新的数组 \(g_i\) 表示已经购买了 \(i\) 类武器,还需 \(g_i\) 的钱数期望值。

    还是需要思考 \(DP\) 的本质,前面几次操作均会对本次操作产生影响。

    那么每次买后都让价格 \(+1\) ,那么到第 \(i\) 次购买时价格就 \(+i\) 了,符合题面的价格要求。

    思考怎么实现。

    虽然说这个 \(DP\) 是从后往前推的,但是显然 \(1+2+3+4=4+3+2+1\) ,这个涨价的趋势反过来是没有影响的。

    那不放把他理解成是每次购买价格 \(-1\)

    那么对于最后一次购买,即第 \(f_0\) 次购买所花价格为 \(1\) ,同时 \(g_n=0\) 也是显然的。

    那么对于第 \(i\) 次购买,他所花的价格便为 \(f_0-i+1\)

    思路已经有了,怎么把他搞到转移方程里。

    首先和上面类似的,先假设 \(a_{i}\) 表示第 \(i\) 次购买时的价格。

    那么 \(g_i=\dfrac{n-i}{n}\times (g_{i+1}+a_{i+1})+\dfrac{i}{n}\times (g_i+a_i)\)

    怎样将这个 \(a_i\) 转换一下,发现之前处理的 \(f\) 数组有用了。

    \(i\) 次价格为 \(f_0-i+1\) ,也就是还需要买几次再加上 \(1\) ,发现恰好为 \(f_i+1\),即 \(a_i=f_i+1\)

    于是就有了 \(g_i=\dfrac{n-i}{n}\times (g_{i+1}+f_{i+1}+1)+\dfrac{i}{n}\times (g_i+f_i+1)\)

    类似的化简过程,不再赘述,最后化简完是:

    \(g_i=f_{i+1}+g_{i+1}+\dfrac{i}{n-i}\times f_i+\dfrac{n}{n-i}\)

    最后输出 \(g_0\) 即可。

    代码极为简单,核心代码仅有 \(4\) 行。

    当然从前往后推也是可以的,可能比较不好想一点,代码甚至可以不用数组且 \(2\) 行实现,怎么打的去问 洛天依

点击查看代码(从后往前)
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=1e4+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    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;
double f[N],g[N];
signed main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    for(int i=n-1;i>=0;i--)
        f[i]=f[i+1]+(double)n/(double)(n-i);
    for(int i=n-1;i>=0;i--)
        g[i]=f[i+1]+g[i+1]+(double)i/(double)(n-i)*f[i]+(double)n/(double)(n-i);
    printf("%.2f",g[0]);
}
洛天依的代码(从前往后)
//省略几十行 Fast IO。
int n;
double f,g,p;
signed main(){
	cin>>n;
	for(double x=1;x<=n;++x)
		f+=(g+=n/x)*n/x;
	printf("%.2f",f);
}

复杂度 \(O(n)\)

T2 昌

  • 原题: CF1153D Serval and Rooted Tree

  • 简化题意:

    给定一棵 \(n\) 个节点的数,\(1\) 为根节点。

    对于节点 \(i\) 有对应 \(a_i\)

    • \(a_i=1\) ,该点的值等于其子节点值中的最大值。

    • \(a_i=0\) ,该点的值等于其子节点值中的最小值。

    设树上有 \(k\) 个叶子结点,每个节点的值可以为为 \(1\sim k\) ,每个数仅用一次,求根节点的最大值。

  • 解法:

    树形 \(DP\)

    设节点 \(i\) 的值 \(\geq x_i\)

    • \(a_i=0\) ,则 \(i\) 的子节点的值必须全部 \(\geq x_i\)

    • \(a_i=1\) ,则最少有一个点的值 \(\geq x_i\)

    我们设根节点的值 \(\geq x\)

    为了根节点值最大,我们希望必须 \(\geq x\) 的叶子结点个数尽可能的少。

    那么倘若我们求出必须 \(\geq x\) 的叶子结点个数 \(num\) ,则根节点的最大值即 \(k-num+1\)\(k\) 表示叶子结点的个数。

    利用树形 \(DP\) ,定义 \(f_i\) 表示以 \(i\) 为根节点的子树中必须 \(\geq x\) 的叶子结点的个数。

    那么显然,若 \(i\) 为叶子结点,则 \(f_i=1\)

    接下来分类讨论:

    • \(a_i=0\)

      因为其子节点的值必须全部 \(\geq x_i\) ,所以 \(f_u=\sum\limits_{v\in{son_u}}f_v\)

    • \(a_i=1\)

      因为其子节点的值至少有 \(1\)\(\geq x_i\) ,所以 \(f_u=\min_{\in{son_u}}f_v\)

    由此跑 \(dfs\) ,到最后 \(f_1\) 表示所有叶子结点中必须 \(\geq x\) 的个数。

    所以答案即为 \(k-f_1+1\)

点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=3e5+10;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    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,sum,a[N],f[N];
vector<int>son[N];
void dfs(int x)
{
    if(son[x].empty())
    {
        f[x]=1;
        return ;
    }
    if(a[x]==1)
    {
        f[x]=0x3f3f3f3f;
        for(int y:son[x])
        {
            dfs(y);
            f[x]=min(f[x],f[y]);
        }
    }
    else if(a[x]==0)
    {
        for(int y:son[x])
        {
            dfs(y);
            f[x]+=f[y];
        }
    }
}
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(a[i]);
    int x;
    for(int i=2;i<=n;i++)
        read(x),
        son[x].push_back(i);
    dfs(1);
    for(int i=1;i<=n;i++)
        if(son[i].empty())
            sum++;
    cout<<sum-f[1]+1;
}

复杂度 \(O(n)\)

T3 起

实在不会打了,溜了溜了。

但是附上教主的超级方法:K8He的题解

T4 义

  • 原题: 小 Y 的背包计数问题(loj 6089)

  • 前言:

    官方题解一如既往的不靠谱,丫的有一个地方是错的,害得我调了好半天,后来感觉题解里这个 \(g_{i,j+\sqrt n+1}+=g_{i,j}\) 怎么想这个 \(i\) 也要 \(+1\) 啊!改了就 \(A\)\(qwq\)

  • 简化题意:

    有一大小为 \(n\) 的背包,有 \(n\) 种物品,第 \(i\) 种物品大小为 \(i\) ,数量为\(i\) 个,求将背包装满的方案数为多少。

  • 部分分:

    • \(0pts:\) 暴力背包 \(DP\) ,全 \(T\)
    • \(30pts:\) 阈值分治,但是 \(>\sqrt n\) 的部分从 \(\sqrt n\) 循环到 \(n\) ,所以也会 \(T\)
    • \(60pts:\) 天佑这个长得和正解没什么区别的为啥不卡常就 \(T\) 了?我也没用卡常啊。
  • 正解:

    学习新算法:阈值分治

    1. 对于前 \(\sqrt n\) 的物品,均可以全部取完。

    2. 对于 \(\sqrt n+1\sim n\) 的物品,每一种都不可能全部取完,显然,所有物品加起来最多取 \(\sqrt n\) 个。

    所以对于这两种情况分别用 \(2\)\(DP\) 进行讨论。

    • 对于第一种情况:

      定义 \(f_{i,j}\) ,表示取到第 \(i\) 种物品,背包容量已用 \(j\) 时的方案数。

      转移方程:$$f_{i,j}=f_{i-1,j}+f_{i,j-i}-f_{i-1,j-i\times (i+1)}$$

      • \(f_{i-1,j}\) 表示不拿 \(i\) 物品。

      • \(f_{i,j-i}\) 表示再拿一个 \(i\) 物品。

      • 至于为什么减去 \(f_{i-1,j-i\times(i+1)}\)

        首先该 \(DP\) 统计的是方案数,\(f_{i,j}\) 是从 \(f_{i,j-i}\) 传过来的,同样 \(f_{i,j-i}\) 是由 \(f_{i,j-2i}\) 传过来的。

        反过来,也就是 \(f_{i,j}\) 包含了 \(f_{i,j-i}\)\(f_{i,j-i}\) 包含了 \(f_{i,j-2i}\) ,最后,\(f_{i,j}\) 包含了其之前的所有 \(f_{i,j-k\times i}\)

        首先一件物品最多取 \(i\) 件,所以从 \(i+1\) 件开始往后都是多出的方案数,需要减掉,而 \(f_{i-1,j-i\times (i+1)}\) 包含了所有取的数量 \(\geq i+1\) 的方案,所以减去 \(f_{i-1,j-i\times (i+1)}\) 就相当于减去所有多拿的方案数。

    • 对于第二种情况:

      然后发现从 \(\sqrt n+1\) 循环到 \(n\) 还是会 \(T\) ,就是 \(30pts\) 部分分的情况。

      定义 \(g_{i,j}\) 表示拿了 \(i\)\(\sqrt n+1\sim n\) 中的物品,背包容量占了 \(j\) 时的方案数。

      所以需要一个及其巧妙的做法:

      • 取第 \(\sqrt n+1\) 种物品:

        \[g_{i+1,j+\sqrt n+1}+=g_{i,j} \]

      • 不选第 \(\sqrt n+1\) 种物品,已选中的所有物品的编号 \(+1\)

        \[g_{i,j+i}+=g_{i,j} \]

      由此虽然从始至终都只拿了第 \(\sqrt n+1\) 种物品,实则所有物品都是被统计到的。

      那么就从 \(1\) 循环到 \(\sqrt n\) 即可。

    最后把两个 \(DP\) 的方案相乘再加起来即可。

    具体的,\(ans+=f_{\sqrt n,j}\times g_{i,n-j}\)\(i\)\(1\) 循环到 \(\sqrt n\)

点击查看代码
//小 Y 的背包计数问题
#include<bits/stdc++.h>
#define int long long 
#define endl '\n'
using namespace std;
const int N=1e5+10,M=400,P=23333333;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    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,m,f[M][N],g[M][N],ans;
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    m=sqrt(n);
    for(int i=0;i<=m;i++) f[i][0]=1;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=i)
                (f[i][j]+=f[i][j-i])%=P;
            if(j>=(i+1)*i)
                f[i][j]=(f[i][j]-f[i-1][j-i*(i+1)]+P)%P;//+P不要忘,否则可能出负数。
        }
    g[0][0]=1;
    for(int i=0;i<=m;i++)
        for(int j=0;j<=n;j++)
        {
            if(j+i<=n&&i)
                (g[i][j+i]+=g[i][j])%=P;
            if(j+m+1<=n)
                (g[i+1][j+m+1]+=g[i][j])%=P;
        }
    g[1][0]=1;
    for(int i=0;i<=n;i++)
        for(int j=1;j<=m;j++)
            ans=(ans+f[m][i]*g[j][n-i]%P)%P;
    cout<<ans;
}

复杂度 \(O(n\sqrt n)\)

posted @ 2024-03-16 15:31  卡布叻_周深  阅读(35)  评论(4编辑  收藏  举报