总结
今天这把多校,一开始没睡醒,虽然很快就找到了签到题,但是想了半天都没有想到正解,最后靠着队友把1002,1012,1007签了后,才想出了1009,在2小时开出1009之后,我和队友开始了长达三个小时的罚坐,期间我们同时在看4个题,都没能做出一道,我在指出1003和1011为可做题之后去看没几个人做出来的概率dp(1006)去了,写了两张纸和几十行代码,捣鼓了半天还是没能把样例二推出来,到了最后几分钟,一个队友抱着试一试的态度写了1003的暴力,在4:59:49提交并ac了,五题收场。
题解
1006 - Bowcraft
题意:
有一把弓,初始等级为
0
0
0 ,商店提供一本升级书,在使用这本升级书时,有
a
A
\frac{a}{A}
Aa 的概率将弓提升
1
1
1 级,如果升级失败,就有
b
B
\frac{b}{B}
Bb 的概率(前提是使用了这本书并且升级失败)使这把弓的等级降为
0
0
0 ,当你从商店购买一本升级书时,商店系统会在
[
0
,
A
−
1
]
[0,A-1]
[0,A−1] 中生成一个随机数
a
a
a ,在
[
0
,
B
−
1
]
[0,B-1]
[0,B−1] 中生成一个随机数
b
b
b ,在购买了升级书后,你需要确定是否使用这本书,问:当你使用最佳策略时,将弓从
0
0
0 级升到
K
K
K 级所需要购买的升级书数量的数学期望是多少。
T T T 组数据,每次输入一组 K , A , B K,A,B K,A,B 其中 ( T ≤ 10 , 1 ≤ K ≤ 1000 , 2 ≤ A , B ≤ 100 ) (T\le 10,1\le K \le 1000,2\le A,B \le 100) (T≤10,1≤K≤1000,2≤A,B≤100)
做法:
考虑使用 d p dp dp 解决该问题, d p [ i ] dp[i] dp[i] 表示将弓从 0 0 0 级升到 K K K 级所需要购买升级书数量的数学期望,为了方便表达,我们令 α = a A \alpha=\frac{a}{A} α=Aa ,令 β = b B \beta=\frac{b}{B} β=Bb .
假设当前的等级为 i i i ,买了一本随机数为 ( a , b ) (a,b) (a,b) 的升级书,我们可以对这本书进行两种操作:
1 如果我们选择使用这本书升级,那么将弓升到 i + 1 i+1 i+1 级的期望是 d p [ i ] + 1 + ( 1 − α ) β ⋅ d p [ i + 1 ] + ( 1 − α ) ( 1 − β ) ⋅ ( d p [ i + 1 ] − d p [ i ] ) dp[i]+1+(1-\alpha) \beta \cdot dp[i+1]+(1-\alpha)(1-\beta) \cdot (dp[i+1]-dp[i]) dp[i]+1+(1−α)β⋅dp[i+1]+(1−α)(1−β)⋅(dp[i+1]−dp[i]) ,我们先来解释一下这个式子:
d p [ i ] + 1 dp[i]+1 dp[i]+1 :因为我们确定要使用这本书,所以将弓升到 i + 1 i+1 i+1 的期望一定会先在 d p [ i ] dp[i] dp[i] 的基础上加 1 1 1 ,然后,
( 1 − α ) β ⋅ d p [ i + 1 ] (1-\alpha) \beta \cdot dp[i+1] (1−α)β⋅dp[i+1] :这里 ( 1 − α ) β (1-\alpha) \beta (1−α)β 为升级失败并触发等级降为 0 0 0 情况的概率,当发生这种情况时,需要重新将弓升级到 i + 1 i+1 i+1 级才能满足要求,故这里需要乘 d p [ i + 1 ] dp[i+1] dp[i+1] ,也就是乘以将弓升级到 i + 1 i+1 i+1 级的期望。
( 1 − α ) ( 1 − β ) ⋅ ( d p [ i + 1 ] − d p [ i ] ) (1-\alpha)(1-\beta) \cdot (dp[i+1]-dp[i]) (1−α)(1−β)⋅(dp[i+1]−dp[i]) :这里前面 ( 1 − α ) ( 1 − β ) (1-\alpha)(1-\beta) (1−α)(1−β) 是指弓升级失败但没有触发等级降为零事件的概率,发生这种事件的次数为升级到 i + 1 i+1 i+1 级所需次数与升级到 i i i 级所需次数之差,即使用了升级书但弓的等级不变的次数。
2 如果我们选择不使用这本书升级,那么将弓升到 i + 1 i+1 i+1 级的期望是 d p [ i + 1 ] + 1 dp[i+1]+1 dp[i+1]+1 ,意思是如果不使用这本书,升级到 i + 1 i+1 i+1 的期望依旧要加 1 1 1 。
因此我们可以得到如下的
d
p
dp
dp 方程:
d
p
[
i
+
1
]
=
1
A
B
∑
a
,
b
m
i
n
{
d
p
[
i
+
1
]
+
1
,
d
p
[
i
]
+
1
+
(
1
−
α
)
β
⋅
d
p
[
i
+
1
]
+
(
1
−
α
)
(
1
−
β
)
⋅
(
d
p
[
i
+
1
]
−
d
p
[
i
]
)
}
dp[i+1]= \frac{1}{AB} \sum_{a,b} min \left \{ dp[i+1]+1, dp[i]+1+(1-\alpha) \beta \cdot dp[i+1]+(1-\alpha)(1-\beta) \cdot (dp[i+1]-dp[i])\right \}
dp[i+1]=AB1∑a,bmin{dp[i+1]+1,dp[i]+1+(1−α)β⋅dp[i+1]+(1−α)(1−β)⋅(dp[i+1]−dp[i])}
取 m i n min min 的含义是,对本书进行判断,若使用当前这本书的期望大于不使用当前这本书的期望,则不使用。
对于当前的等级 i i i 和一本书 ( a , b ) (a, b) (a,b) ,如果要使用这本书,那么升到 i + 1 i + 1 i+1 级,不使用的期望 ≥ \ge ≥ 使用的期望
即 d p [ i + 1 ] + 1 ≥ d p [ i ] + 1 + ( 1 − α ) β ⋅ d p [ i + 1 ] + ( 1 − α ) ( 1 − β ) ⋅ ( d p [ i + 1 ] − d p [ i ] ) dp[i+1]+1 \ge dp[i]+1+(1-\alpha) \beta \cdot dp[i+1]+(1-\alpha)(1-\beta) \cdot (dp[i+1]-dp[i]) dp[i+1]+1≥dp[i]+1+(1−α)β⋅dp[i+1]+(1−α)(1−β)⋅(dp[i+1]−dp[i])
化简为: d p [ i + 1 ] ≥ d p [ i ] ⋅ α + β − α β α dp[i+1] \ge dp[i] \cdot \frac{\alpha + \beta -\alpha \beta}{\alpha} dp[i+1]≥dp[i]⋅αα+β−αβ
从上式可见, α + β − α β α \frac{\alpha + \beta -\alpha \beta}{\alpha} αα+β−αβ 值较小的升级书更容易被使用,即 β ( 1 − α ) α \frac{\beta (1-\alpha)}{\alpha} αβ(1−α) 值更小的升级书更容易被使用,因此我们可以先对 A ∗ B A*B A∗B 本书的 β ( 1 − α ) α \frac{\beta (1-\alpha)}{\alpha} αβ(1−α) 值进行排序预处理,假设有 t t t 本书符合使用条件,我们就使用 β ( 1 − α ) α \frac{\beta (1-\alpha)}{\alpha} αβ(1−α) 值前 t t t 小的书,其他的书不使用,那么有:
A B ⋅ d p [ i + 1 ] = ( A B − t ) ⋅ ( d p [ i + 1 ] + 1 ) + ∑ a , b ∈ ( 前 t 小 ) d p [ i ] + 1 + ( 1 − α ) β ⋅ d p [ i + 1 ] + ( 1 − α ) ( 1 − β ) ⋅ ( d p [ i + 1 ] − d p [ i ] ) AB\cdot dp[i+1]=(AB-t)\cdot (dp[i+1]+1)+\sum_{a,b\in (前t小)}dp[i]+1+(1-\alpha) \beta \cdot dp[i+1]+(1-\alpha)(1-\beta) \cdot (dp[i+1]-dp[i]) AB⋅dp[i+1]=(AB−t)⋅(dp[i+1]+1)+∑a,b∈(前t小)dp[i]+1+(1−α)β⋅dp[i+1]+(1−α)(1−β)⋅(dp[i+1]−dp[i])
化简得 d p [ i + 1 ] = A B + d p [ i ] ⋅ ∑ a , b ∈ ( 前 t 小 ) α + β − α β t − ∑ a , b ∈ ( 前 t 小 ) 1 − α dp[i+1]=\frac{AB+dp[i] \cdot \sum_{a,b\in (前t小)} \alpha + \beta -\alpha \beta}{t-\sum_{a,b\in (前t小)}1- \alpha } dp[i+1]=t−∑a,b∈(前t小)1−αAB+dp[i]⋅∑a,b∈(前t小)α+β−αβ
计算:
将符合条件的升级书 ( a , b ) (a,b) (a,b) 按 β ( 1 − α ) α \frac{\beta (1-\alpha)}{\alpha} αβ(1−α) 值从小到大求和,用 s u m 1 sum1 sum1 求和符合条件的 α + β − α β \alpha + \beta -\alpha \beta α+β−αβ ,即 s u m 1 = ∑ i ∈ t α i + β i − α i β i sum1=\sum_{i\in t} \alpha_i + \beta_i -\alpha_i \beta_i sum1=∑i∈tαi+βi−αiβi ,用 s u m 2 sum2 sum2 求和符合条件的 1 − α 1-\alpha 1−α ,即 s u m 2 = ∑ i ∈ t α i sum2=\sum_{i\in t} \alpha_i sum2=∑i∈tαi 。遍历排好序后的 ( α , β ) (\alpha,\beta) (α,β) 对每个 i i i 进行一次判断,判断加上这一对 ( α , β ) (\alpha,\beta) (α,β) 后 d p [ i + 1 ] dp[i+1] dp[i+1] 的预计期望是否变小,若不变小则退出循环,更新 d p [ i + 1 ] dp[i+1] dp[i+1] 的最小值,按照同样的方法递推得到并输出 d p [ K ] dp[K] dp[K]
由于每次递推得到 d p [ i + 1 ] dp[i+1] dp[i+1] 的复杂度为 O ( A B ) O(AB) O(AB) ,总复杂度为 O ( K A B ) O(KAB) O(KAB).
代码:
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
#define f first
#define s second
using namespace std;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t;cin>>t;
while(t--)
{
int k,a1,b1;
cin>>k>>a1>>b1;
double a=a1,b=b1;
vector<double> dp(k+1);
dp[0]=0;//弓箭初始为0级,期望为0
vector<pair<double,double> > ab;
for(int i=1;i<a1;i++)
{
for(int j=0;j<b1;j++)
{
ab.emplace_back((double)i/a,(double)j/b);//预处理求出a/A 与 b/B
}
}
auto cmp = [&](pair<double,double> aa1,pair<double,double> bb1)//从小到大排序
{
double af1=aa1.f,bt1=aa1.s;
double af2=bb1.f,bt2=bb1.s;
return bt1*(1.0-af1)/af1<bt2*(1.0-af2)/af2;
};
sort(ab.begin(),ab.end(),cmp);
for(int i=0;i<k;i++)//k次递推得到dp[K]
{
double tt=0,s1=0,s2=0;
double ab1=a*b;
for(int j=0;j<ab.size();j++)
{
double af=ab[j].f;
double bt=ab[j].s;
double t1=s1+af+bt-af*bt;
double t2=s2+1-af;
if((ab1+dp[i]*t1)/(tt+1-t2)<=(ab1+dp[i]*s1)/(tt-s2))
{
s1=t1;
s2=t2;
tt+=1;
}
}
dp[i+1]=(ab1+dp[i]*s1)/(tt-s2);
}
cout<<fixed<<setprecision(3)<<dp[k]<<endl;//注意保留三位小数
}
return 0;
}
1001 - Static Query on Tree
题意:
给你一棵有 n − 1 n-1 n−1 条边的树和 q q q 次查询,每次查询给出 A A A 、 B B B 、 C C C 三个包含点的集合,问从 A A A 组和 B B B 组中挑选至少一个点到达 C C C 组中的至少一个点,最多总共可以经过多少个点。换句话说,就是由集合 A A A 和集合 B B B 出发的到达集合 C C C 的点中,有多少点同时被从 A A A 和 B B B 出发的点同时经过?
做法:
我们使用树链剖分的方法解决这道题,首先对于集合 A A A 、 B B B 中的点,我们将根到该点打上 a / b a/b a/b 标记,对于集合 C C C 的点,我们对该点及其子树上的点打上 c c c 标记,再统计同时拥有三种标记的点的数量,即为答案,这里打标记和统计的操作可以使用线段树进行维护,复杂度为 l o g n logn logn .
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
typedef unsigned long long ull;
const int maxn=200010;
const int inf=0x3f3f3f3f;
const int mod=998244353;
int t;
int n,m,k;
const int san = 3;
const int si = 4;
struct slpf{
const int nn;
struct node{
int l,r;
array<int,4> sum,lz;
void clear()//清空所有标记
{
for(int i=0;i<4;i++)
sum[i]=lz[i]=0;
}
};
vector<node> tr;
vector<int> fa,dep,son,siz,top,dfn;
vector<vector<int>> g;
int tim;
slpf(int n1):nn(n1),tr((n1+10)*4),fa(n1+1),dep(n1+1),son(n1+1,0),siz(n1+1),top(n1+1),dfn(n1+1),g(n1+1),tim(0){}
void add(int uu,int vv)
{
g[uu].push_back(vv);
}
void dfs1(int now,int fr)
{
fa[now]=fr;
dep[now]=dep[fr]+1;
siz[now]=1;
int max_size=-1;
for(int x:g[now])
{
if(x==fr)
continue;
dfs1(x,now);
siz[now]+=siz[x];
if(siz[x]>max_size)
{
max_size=siz[x];
son[now]=x;
}
}
}
void dfs2(int now,int tp)
{
dfn[now]=++tim;
top[now]=tp;
if(!son[now])
return;
dfs2(son[now],tp);
for(int x:g[now])
{
if(x==fa[now]||x==son[now])
continue;
dfs2(x,x);
}
}
void build(int root,int l,int r)//根节点为1,范围从1-n
{
tr[root].l=l;
tr[root].r=r;
if(l==r)
return;
int mid=(l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
}
void eval(node& tr,int tag)
{
if(tag==3)//标记3为清除标记,优先打
{
tr.clear();
tr.lz[3]=1;
}
else if(tag==0)//A标记
{
tr.sum[0]=tr.r-tr.l+1;
tr.lz[0]=1;
}
else if(tag==1)//B标记要在A标记不为0的时候才能打下
{
tr.sum[1]=tr.sum[0];
tr.lz[1]=1;
}
else//C标记要在B标记不为0的时候才能打上
{
tr.sum[2]=tr.sum[1];
tr.lz[2]=1;
}
}
void push_down(int p)
{
if(tr[p].lz[3])
{
eval(tr[p<<1],3);
eval(tr[p<<1|1],3);
tr[p].lz[3]=0;
}
if(tr[p].lz[0])
{
eval(tr[p<<1],0);
eval(tr[p<<1|1],0);
tr[p].lz[0]=0;
}
if(tr[p].lz[1])
{
eval(tr[p<<1],1);
eval(tr[p<<1|1],1);
tr[p].lz[1]=0;
}
if(tr[p].lz[2])
{
eval(tr[p<<1],2);
eval(tr[p<<1|1],2);
tr[p].lz[2]=0;
}
}
void push_up(int p)
{
for(int i=0;i<3;i++)
tr[p].sum[i]=tr[p<<1].sum[i]+tr[p<<1|1].sum[i];
}
void update(int root,int l,int r,int x)
{
if(l<=tr[root].l&&r>=tr[root].r)
{
eval(tr[root],x);
return;
}
push_down(root);
int mid=(tr[root].l+tr[root].r)>>1;
if(l<=mid)
update(root<<1,l,r,x);
if(r>mid)
update(root<<1|1,l,r,x);
push_up(root);
}
void make_tree(int root)
{
dfs1(root,root);
dfs2(root,root);
build(1,1,nn);
}
void update_son(int x,int z)//x为根结点的子树所有节点值+z
{
update(1,dfn[x],dfn[x]+siz[x]-1,z);
}
void update_chain(int x,int y,int z)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
swap(x,y);
update(1,dfn[top[x]],dfn[x],z);
x=fa[top[x]];
}
if(dep[x]>dep[y])
swap(x,y);
update(1,dfn[x],dfn[y],z);
}
};
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
cin>>n>>k;
slpf solve(n);
for(int i=2;i<=n;i++)
{
cin>>m;
solve.add(m,i);
}
vector<int> w(n,0);
solve.make_tree(1);
int a,b,c,x;
while(k--)
{
cin>>a>>b>>c;
while(a--)
{
cin>>x;
solve.update_chain(1,x,0);
}
while(b--)
{
cin>>x;
solve.update_chain(1,x,1);
}
while(c--)
{
cin>>x;
solve.update_son(x,2);
}
cout<<solve.tr[1].sum[2]<<endl;//由于C标记在A、B标记都打上的基础上才能打上,故查询时只需要查询C标记的数量即可。
solve.tr[1].clear();
solve.tr[1].lz[3]=1;
}
}
return 0;
}
1009 - ShuanQ
题意:
定义一种加密方式为 E = R × P E=R \times P E=R×P $ mod$ M M M, 解密方式为 R = E × Q R=E \times Q R=E×Q $ mod$ M M M
其中 P = Q − 1 P=Q^{-1} P=Q−1 ,且 P × Q = 1 P\times Q =1 P×Q=1 m o d mod mod M M M ,这里 M M M 为质数,问给你一组 P P P 、 Q Q Q 、 E E E , 是否能将其解密,如果能则输出 R R R ,否则输出 “shuanQ”。
做法:如果要用解密公式对其解密,我们需要找到这个质数 M M M ,由于 P P P 、 Q Q Q 互为逆元,故有
P × Q − 1 = k M , k ≥ 1 P\times Q-1=kM,k\ge 1 P×Q−1=kM,k≥1
M M M 是一个比 P P P 、 Q Q Q 大的质因子,如果有多个满足要求质因子 M 1 M1 M1 、 M 2 M2 M2 ,那么 k M = M 1 × M 2 > P ∗ Q kM = M1 × M 2 > P ∗ Q kM=M1×M2>P∗Q 矛盾
代码:
/*
author:wuzx
*/
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
typedef unsigned long long ull;
const int maxn=200010;
const int inf=0x3f3f3f3f;
const int mod=998244353;
int t;
int n,m,k;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
int p,q,e;
cin>>p>>q>>e;
int nb=p*q-1;
vector<int> ans;
for(int i=2;i*i<=nb;i++)
{
if(nb%i==0)
{
while(nb%i==0)
nb/=i;
if(e>=i||p>=i||q>=i)
continue;
else
ans.push_back(i);
}
}
if(nb>1)
ans.push_back(nb);
auto solve = [&]()
{
for(auto x:ans)
{
int r=(e*q)%x;
if(e==(r*p)%x)
{
cout<<r<<endl;
return;
}
}
cout<<"shuanQ"<<endl;
return;
};
solve();
}
return 0;
}