其他分享
首页 > 其他分享> > 2021.牛客暑假多校 第四场 补题

2021.牛客暑假多校 第四场 补题

作者:互联网

  1. B题 https://ac.nowcoder.com/acm/contest/11255/B 期望DP

大意:现有一个随机数生成器,每次生成1~100的数字,概率为 p i p_i pi​​ ,现在有以下阶段,(1)生成一个数 x。 (2) 若生成的数 x 不小于之前任意一个生成的数重复(1)否决进行(3)。(3)计算得分,未生成数的个数的平方并结束生成。

思路:很容易看出是期望dp,比赛的时候题目理解错了,思考方式完全错了。官方解给的是用生成函数写,我们这里用期望dp。对于求平方的期望 E ( x 2 ) E(x^2) E(x2)​,我们一般先求出 E(x)。因为 E ( x 2 ) E(x^2) E(x2) 在进行状态转移时要用到 E(x)。即 E ( ( x + 1 ) 2 ) = E ( x 2 + 2 x + 1 ) = E ( x 2 ) + 2 E ( x ) + 1 E((x+1)^2)=E(x^2+2x+1)=E(x^2)+2E(x)+1 E((x+1)2)=E(x2+2x+1)=E(x2)+2E(x)+1

而且此题是可以无限次的生成随机数的,所以根据步数进行状态定义和状态转移是行不通的.

先求 E(x)的:

我们用 f i f_i fi​​​ 表示当前生成的随机数中最大值为 i 再 生成一个数的期望。

再生成一个数是小于 i 的 ∑ j = 1 i − 1 p j ∗ 1 \sum_{j=1}^{i-1}{p_j} * 1 ∑j=1i−1​pj​∗1

在生成一个数是大于等于 i 的 ∑ j = i n ( f j + 1 ) ∗ p j \sum_{j=i}^{n} (f_j+1)*p_j ∑j=in​(fj​+1)∗pj​​

观察下面那个状态转移方程我们会发现 j 从 i 开始的话每次更新 f i f_i fi​​的时候还要用到 f i f_i fi​​ ,显然是没办法更新的,所以我们把 j=i 的给单独拉出来。

所以下面那个状态转移方程可变为 ( f i + 1 ) p i + ∑ j = i + 1 n ( f j + 1 ) ∗ p j (f_i+1)p_i + \sum_{j=i+1}^{n} (f_j+1)*p_j (fi​+1)pi​+∑j=i+1n​(fj​+1)∗pj​​​

即 f i = ∑ j = 1 i − 1 p j ∗ 1 + ( f i + 1 ) p i + ∑ j = i + 1 n ( f j + 1 ) ∗ p j f_i=\sum_{j=1}^{i-1}{p_j} * 1+ (f_i+1)p_i + \sum_{j=i+1}^{n} (f_j+1)*p_j fi​=∑j=1i−1​pj​∗1+(fi​+1)pi​+∑j=i+1n​(fj​+1)∗pj​

化简得 f i = 1 + ∑ j = i + 1 n f j ∗ p j ( 1 − p i ) f_i=\frac{1+\sum_{j=i+1}^{n} f_j*p_j}{(1-p_i)} fi​=(1−pi​)1+∑j=i+1n​fj​∗pj​​​​​ 显然这样就可以递推更新了

再来求 E ( x 2 ) E(x^2) E(x2)​ 的:

我们用 d p i dp_i dpi​​ 表示当前生成的随机数中最大值为 i 再 生成一个数的期望。

同理: d p i = ∑ j = 1 i − 1 p j ∗ 1 + ( d p i + 2 f i + 1 ) p i + ∑ j = i + 1 n ( d p j + 2 f j + 1 ) ∗ p j dp_i=\sum_{j=1}^{i-1}{p_j} * 1+ (dp_i+2f_i+1)p_i + \sum_{j=i+1}^{n} (dp_j+2f_j+1)*p_j dpi​=∑j=1i−1​pj​∗1+(dpi​+2fi​+1)pi​+∑j=i+1n​(dpj​+2fj​+1)∗pj​​​

化简得: d p i = 1 + 2 p i f i + ∑ j = i + 1 n ( d p j + 2 f j ) ∗ p j 1 − p i dp_i=\frac {1 + 2p_i f_i + \sum_{j=i+1}^{n} (dp_j+2f_j)*p_j}{1-p_i} dpi​=1−pi​1+2pi​fi​+∑j=i+1n​(dpj​+2fj​)∗pj​​​

最终结果:我们假设第一个生成的数 i 显然第一个生成的数 1~n 都有可能,所以就是把所有可能的结果相加:也就是 d p 0 dp_0 dp0​​

所以我们要递推到 i=0.

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=110,M=998244353;
typedef long long LL;
LL qmi(LL a,LL b)
{
    LL res=1;
    while(b)
    {
        if(b&1)res=res*a%M;
        a=a*a%M;
        b>>=1;
    }
    return res%M;
}
LL p[N],f[N],dp[N],n,ps;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>p[i],ps=(ps+p[i])%M;
    ps=qmi(ps,M-2);
    for(int i=1;i<=n;i++)p[i]=p[i]*ps%M;
    LL s=0;
    for(int i=n;i>=1;i--)
    {
        f[i]=(1+s)*qmi((1-p[i]+M)%M,M-2)%M;
        s=(s+f[i]*p[i])%M;
    }
    s=0;
    for(int i=n;i>=0;i--)
    {
        dp[i]=(1+2*p[i]*f[i]+s)%M*qmi((1-p[i]+M)%M,M-2)%M;
        s=(s+(dp[i]+2*f[i])*p[i])%M;
        //cout<<dp[i]<<"\n";
    }
    cout<<dp[0]<<"\n";
    return 0;
}

总结:期望DP 重要的是怎么计算期望,只有先会怎么计算期望,才有可能写出状态转移方程。

  1. C题 https://ac.nowcoder.com/acm/contest/11255/C 思维构造,签到
  2. E题 https://ac.nowcoder.com/acm/contest/11255/E 线段树+异或/ 线段差分+异或,思维

大意:有一颗 n 个节点的树,先给出所有的边权值,边权值代表边两边的点权的异或值,并给出点权的取值范围 [ l i , r i ] [l_i,r_i] [li​,ri​],问一共有多少种符合条件的情况。 n < = 1 e 5 , 0 < = l < = r < = 2 3 0 n<=1e5, 0<=l<=r<=2^30 n<=1e5,0<=l<=r<=230

思路:首先需要想到的一点,一旦我们确定了任意一个点的值,我们进而就可以推出所有点的值,并且对于任意一点异或上x,由于异或的特点就行相当于其他的点也分别异或上x。所以我们不妨先假设根节点的值为0,先求出每个点的值,这样不一定是合法的,但我们可以通过根节点异或上x进行调整,也就将问题转化成了对于 每个点都有 l[i] <=(w[i]异或x)<=r[i](1<=i<=n), 求满足条件的x的个数。进一步转化也就是我们把数看成一个数轴,对于每个i 我们求出合法的若干个x的值就在相应位置加1,看最后有多少个位置的值是n。对于一段区间+1的操作,显然我们可以用差分数组进行,但是x最大能取到 1e9,直接用肯定不行,所以我们用的是“差分数组的思想”。但是每次求满足一段区间的 [ l i , r i ] [l_i,r_i] [li​,ri​]​,所以这里我们用到经典操作,区间问题转化成前驱问题,我们先求满足 [ 0 , l i − 1 ] [0,l_i-1] [0,li​−1]​ 在对应位置标记为 -1,再求 [ 0 , r i ] [0,r_i] [0,ri​]​ 标记为 1,这样也是不会影响我们的最终结果的。接下来的问题就是怎么求合法的x了,即求满足(a异或x)<= b的,x的个数。这部分显然就是之前做字典树经常用到的了,不同的是这里不需要建立字典树,对于某一位显然就 00,01,10,11 四种情况(前面的0或1代表a,后面的代表b),对于b这一位是0的,显然只有当 x 的这一位和 a 的这一位相同才有可能,对于b这一位是1的,x和a这一位相同的一定是可以的,后面的为可以随便取,这一位不同的也是有可能的。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=100010;
typedef pair<int,int> PII;
int h[N],e[N*2],ne[N*2],w[N*2],idx;
int val[N],l[N],r[N],n;
bool st[N];
vector<PII> vol[2];
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
	st[u]=1;
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i],k=w[i];
		if(st[j])continue;
		val[j]=k^val[u];
		dfs(j);
	}
}
void dfs1(int id,int x,int y,int bt,int now)
{
	if(bt==-1)
	{
		vol[id].push_back({now,now});
		return ;
	}
	int a=x>>bt&1,b=y>>bt&1;
	if(b==1)
	{
		if(a==1) vol[id].push_back({now+(1<<bt),now+(1<<(bt+1))-1});
		else vol[id].push_back({now,now+(1<<bt)-1});

		dfs1(id,x,y,bt-1,now+((a^1)<<bt));
	}
	else dfs1(id,x,y,bt-1,now+(a<<bt));

}
void solve()
{	
	cin>>n;
	for(int i=1;i<=n;i++)cin>>l[i]>>r[i];
	for(int i=1;i<n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1);
	for(int i=1;i<=n;i++)
	{
		if(l[i]>0)dfs1(0,val[i],l[i]-1,29,0);
		dfs1(1,val[i],r[i],29,0);
	}
	vector<PII> res;
	for(auto &x:vol[0])
	{
		res.push_back({x.first,-1});
		res.push_back({x.second+1,1});
	}
	for(auto &x:vol[1])
	{
		res.push_back({x.first,1});
		res.push_back({x.second+1,-1});
	}
	sort(res.begin(),res.end());
	int t=0,ans=0;

	for(int i=0;i<res.size();i++)
	{
		t+=res[i].second;
		if(t==n&&i+1<res.size())ans+=res[i+1].first-res[i].first;
	}
    cout<<ans<<"\n";
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	memset(h,-1,sizeof h);
    solve();
	return 0;
}

总结:对于差分要活用,区间问题转化成前驱问题。

  1. F 题 https://ac.nowcoder.com/acm/contest/11255/F 博弈,思维,签到

大意:给定一个无环的无向图,不一定全连通,有两种操作方式,(1)选择一条边,删除它,(2)选择一个连通块删除它。两人轮流操作,不能操作的判输。

思路:比赛的时候想复杂了,用SG 想了很久,最后是判断连通块的数量。其实没这么麻烦。对于第一种操作,使得边数 -1,对于第二种操作使得点数-k,边数 -(k-1),但是两种操作使得边数和点数的和都是以奇数方式相减,所以直接判断点数+边数的奇偶就行了。

标签:第四场,int,res,sum,多校,生成,补题,pj,dp
来源: https://blog.csdn.net/m0_51488693/article/details/119569187