初三奥赛模拟测试2
前言
-
这辈子第一次 \(rk~1\) 。
-
\(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 昌
-
简化题意:
给定一棵 \(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 义
-
前言:
官方题解一如既往的不靠谱,丫的有一个地方是错的,害得我调了好半天,后来感觉题解里这个 \(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\) 了?我也没用卡常啊。
-
正解:
学习新算法:阈值分治。
-
对于前 \(\sqrt n\) 的物品,均可以全部取完。
-
对于 \(\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)\) 。