- 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−1pj∗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−1pj∗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+1nfj∗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−1pj∗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−pi1+2pifi+∑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 重要的是怎么计算期望,只有先会怎么计算期望,才有可能写出状态转移方程。
- C题 https://ac.nowcoder.com/acm/contest/11255/C 思维构造,签到
- 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 30 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;
}
总结:对于差分要活用,区间问题转化成前驱问题。
- F 题 https://ac.nowcoder.com/acm/contest/11255/F 博弈,思维,签到
大意:给定一个无环的无向图,不一定全连通,有两种操作方式,(1)选择一条边,删除它,(2)选择一个连通块删除它。两人轮流操作,不能操作的判输。
思路:比赛的时候想复杂了,用SG 想了很久,最后是判断连通块的数量。其实没这么麻烦。对于第一种操作,使得边数 -1,对于第二种操作使得点数-k,边数 -(k-1),但是两种操作使得边数和点数的和都是以奇数方式相减,所以直接判断点数+边数的奇偶就行了。