2022杭电多校2(总结+补题)

总结

​今天这把多校,一开始没睡醒,虽然很快就找到了签到题,但是想了半天都没有想到正解,最后靠着队友把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,A1] 中生成一个随机数 a a a ,在 [ 0 , B − 1 ] [0,B-1] [0,B1] 中生成一个随机数 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) (T10,1K1000,2A,B100)

做法:

​考虑使用 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]=AB1a,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]+1dp[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 AB 本书的 β ( 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]) ABdp[i+1]=(ABt)(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]=ta,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=itα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=itα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 n1 条边的树和 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=Q1 ,且 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×Q1=kM,k1

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>PQ 矛盾

代码:

/*
 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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值