其他分享
首页 > 其他分享> > 8.3

8.3

作者:互联网

CF643C

题意:

有一种电子游戏,它由\(n\)个关卡组成,每个关卡都被赋予了一个值\(t_i\)。

现在,你要将这些关卡分成\(k\)个级别,每个级别\(j\)对应了一段连续的关卡\([l_j,r_j]\),且必有\(l_j\leq r_j\)。任何一个关卡在且仅在一个级别中。

然后,一名玩家将会从第\(1\)个关卡,按顺序一直刷到第\(n\)个关卡。当他打到第\(i\)个关卡时,设这个关卡所在的级别是\(j\),则他有 \(\dfrac{t_i}{\sum\limits_{x=l_j}^{i}t_x}\) 的概率在\(1\)小时内AC这个关卡,然后进入下一关;或者没有AC这个关卡(但仍然花了\(1\)小时),还得再次挑战这个关卡。

你需要找到一种划分方法,使得该玩家期望AK该游戏的期望时间最小。输出这个最小的期望时间。

( $ 1<=n<=200000 $ , $ 1<=k<=min(50,n) $ )

( $ 1<=t_{i}<=100000 $ )

题解:

\(k\)很小,所以可以\(O(nk)\)

考虑这种把一个序列划分成几个连续段的动态规划问题,一般都是斜率优化

列式子

设\(dp[i][k]\)表示前\(i\)个数字,分为\(j\)段的方案数

\(s[i]=\sum_{j=1}^it[i]\)

\(sb[i]=\sum_{j=1}^i\frac{1}{t[j]}\)

\(sc[i]=\sum_{j=1}^i\frac{s[j]}{t[j]}\)

\[dp[i][k]=min\{dp[j][k-1]+\sum_{p=j+1}^i\frac{s[p]-s[j]}{t[p]}\}\\ =min\{dp[j][k-1]+(sc[i]-sc[j])-s[j]*(sb[i]-sb[j])\} \]

化成斜率优化的形式

\[dp[j][k-1]-sc[j]+s[j]*sb[j]=sb[i]*s[j]+dp[i][k]-sc[i] \]

其中\(y=dp[j][k-1]-sc[j]+s[j]*sb[j],x=s[j],k=sb[i],b=dp[i][k]-sc[i]\)

观察斜率\(sb[i]\)单调增,而且求最小值,所以维护个下凸壳

千万别把段数放在里面循环,这样会导致要开\(k\)个\(sb\)的队列,放到外面循环就只用开一个了

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
    const int N=200000+10,mod=1e9+7,inv2=5e8+4,inf=1e18;
    const double pi=acos(-1.0);
    int n,m;
    double t[N],s[N];
    double st[N],t1[N];
    double dp[N][51];
    int q[N];
    int head,tail;
    double y(int i,int k)
    {
        return dp[i][k]-st[i]+s[i]*t1[i];
    }
    double x(int i)
    {
        return s[i];
    }
    double slope(int i,int j,int id)
    {
        if(x(i)==x(j))
            return y(i,id)>y(j,id)?inf:-inf;
        return (y(i,id)-y(j,id))/(x(i)-x(j));
    }
    int fcmp(double x,double y)
    {
        return fabs(x-y)<=eps?0:x>y?1:-1;
    }
    void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        //(s[i]-s[j-1])/t[i]

        //dp[i][k]=dp[j][k-1]+st[i]-st[j]-s[j]*(t1[i]-t1[j])

        //dp[j][k-1]-st[j]+s[j]*t1[j]=s[j]*t1[i]+dp[i][k]-st[i]


        cin>>n>>m;
        for(int i=1;i<=n;++i)
        {
            cin>>t[i];
            s[i]=s[i-1]+t[i];
            st[i]=st[i-1]+1.0*s[i]/t[i];
            t1[i]=t1[i-1]+1.0/t[i];
        }
        for(int i=0;i<=n;++i)
            for(int j=0;j<=m;++j) dp[i][j]=1e18;
        dp[0][0]=0;
        for(int k=1;k<=m;++k)
        {
            head=1,tail=0;
            q[++tail]=0;
            for(int i=1;i<=n;++i)
            {
                while(head<tail&& fcmp(slope(q[head+1],q[head],k-1),t1[i] ) <= 0 ) ++head;
                int j=q[head];
                dp[i][k]=dp[j][k-1]+st[i]-st[j]-s[j]*(t1[i]-t1[j]);
                while(head<tail&& fcmp(slope(i,q[tail-1],k-1),slope(q[tail],q[tail-1],k-1)) <= 0 ) --tail;
                q[++tail]=i;
            }
        }
        cout<<fixed<<setprecision(10)<<dp[n][m]<<'\n';
    }
}
signed main()
{
    red::main();
    return 0;
}
/*
20 4
25 66 10 18 67 40 66 49 3 51 61 29 10 72 71 22 63 4 74 67

*/

CF643E

题意:

给你一棵初始只有根为 \(1\) 的树。

共有 \(q\) 次操作。

1 x 表示加入一个以 \(x\) 为父亲的新点。

2 x 表示求以 \(x\) 为根的子树期望最深深度。

每条边都有 \(\frac{1}{2}\) 的概率断裂。

\(1\leq q\leq 5\times 10^5\)。

误差小于\(1e-6\)

题解:

这题的\(trick\)是误差有限,所以,深度大于\(60\)的概率远小于\(1e-6\),所以答案在\(60\)以内。

设计\(dp\):

\(dp[x][k]\)表示\(x\)节点为根的子树深度不超过\(k\)的概率,不妨认为\(dp[x][60]=1\)

\[dp[x][k]=\prod_{y\in son(x)} \frac{1}{2}(dp[y][k-1]+1) \]

这个式子的含义是:要么节点\(x\)的子节点\(y\)深度不超过\(k-1\),要么节点\(x\)和节点\(y\)之间的边断掉,那么深度不超过\(k\)的概率是\(1\)

节点\(x\)的期望深度就是

\[E(x)=\sum_{i=1}^{60}i*(dp[x][i]-dp[x][i-1]) \]

每次增加一个点只会修改它的不超过\(60\)级的祖先。

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
    const int N=5e5+10,mod=1e9+7,inv2=5e8+4,inf=1e18;
    const double pi=acos(-1.0);
    int n,m,idx;
    double dp[N][62];
    int f[N];
    void main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);cout.tie(0);
        cin>>n;m=61;idx=1;
        for(int i=1;i<=m;++i) dp[1][i]=1;
        for(int i=1;i<=n;++i)
        {
            int opt,x;
            cin>>opt>>x;
            if(opt==1)
            {
                f[++idx]=x;
                for(int k=1;k<=m;++k) dp[idx][k]=1;
                vector<int> tmp;
                int k=0,now=idx;
                while(k<=60&&now)
                {
                    tmp.emplace_back(now);
                    now=f[now];
                    ++k;
                }
                for(int i=tmp.size()-2;i>0;--i)
                {
                    dp[tmp[i+1]][i+1]/=(dp[tmp[i]][i]+1)/2.0;
                    //cout<<tmp[i+1]<<"!!"<<'\n';
                }
                for(int i=0;i+1<(int)tmp.size();++i)
                {
                    dp[tmp[i+1]][i+1]*=(dp[tmp[i]][i]+1)/2.0;
                }
            }
            else
            {
                double ans=0;
                for(int i=1;i<=60;++i)
                {
                    ans+=i*(dp[x][i]-dp[x][i-1]);
                }
                cout<<fixed<<setprecision(10)<<ans-1<<'\n';
            }
        }
    }
}
signed main()
{
    red::main();
    return 0;
}
/*
20 4
25 66 10 18 67 40 66 49 3 51 61 29 10 72 71 22 63 4 74 67

*/

CF1603C

题意:

对于一个数列,定义一次操作为选择数列中任何一个元素 \(a_i\) ,将这个位置替换成两个正整数 \(x,y\) 满足 \(x+y=a_i\)

定义一个数列的极端值为将这个数列变成不降数列的最小操作次数

给定一个长度为 \(n\) 的数列 \(a\) ( \(1\leq a_i\leq 10^5\) ),让你求它的所有非空子段的极端值之和,对 \(\text{998244353}\) 取模

一个测试点中有多组数据,保证所有数据 \(n\) 的规模总和不超过 \(10^5\)

题解:

考虑一个数字\(x\),如果要划分成最大值不超过\(v\),至少要分\(\lceil\frac{x}{v}\rfloor\)个

然后是尽可能让第一个数字最大

如果要把数字分成\(k\)份,最小值最大是\(\lfloor\frac{x}{k}\rfloor\)

设\(dp[i][x]\)表示从后往前第\(i\)个数字,最前面的元素是\(x\)的答案。

这样可以推出\(O(n^2)\)的做法,就是每次从后往前递推

假设\(y\in[l,r]\),满足\(\lfloor\frac{a[i]}{\lceil\frac{a[i]}{y}\rceil}\rfloor=x\)

那么有转移

\[dp[i][x]=\sum_{y=l}^rdp[i+1][y]*i*(\lceil\frac{a[i]}{y}\rceil-1) \]

解释下式子,前面还有\(i\)个子区间包含这个位置,这个位置贡献了\((\lceil\frac{a[i]}{y}\rceil-1)\)次分裂

因为上式是一个向下取整的形式,所以每次转移来的不同\(x\)其实只有\(\sqrt{10^5}\)个,转移的复杂度应该是\(O(n\sqrt{N})\)

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define double long double
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define lowbit(i) ((i)&(-i))
#define mid ((l+r)>>1)
#define eps (1e-15)
    const int N=1e5+10,mod=998244353,inv2=5e8+4,inf=1e18;
    const double pi=acos(-1.0);
    int n;
    vector dp(2,vector<int>(N));
    void main()
    {
        cin>>n;
        vector<int> a(n+1);
        for(int i=1;i<=n;++i)
        {
            cin>>a[i];
        }
        int opt=0,ans=0;
        vector v(2,vector<int>());
        for(int i=n;i>=1;--i)
        {
            opt^=1;
            v[opt].emplace_back(a[i]);
            dp[opt][a[i]]=1;
            int pre=a[i];
            for(auto x:v[opt^1])
            {
                int y=dp[opt^1][x];
                int t1=(a[i]+x-1)/x;
                int t2=a[i]/t1;
                ans=(ans+i*(t1-1)%mod*y%mod)%mod;
                dp[opt][t2]+=y;
                dp[opt][t2]%=mod;
                if(t2!=pre) v[opt].emplace_back(t2),pre=t2;
            }
            for(auto x:v[opt^1]) dp[opt^1][x]=0;
            v[opt^1].clear();
        }
        for(auto x:v[opt]) dp[opt][x]=0;
        cout<<ans<<'\n';
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int qwq=1;cin>>qwq;
    while(qwq--) red::main();
    return 0;
}
/*
20 4
25 66 10 18 67 40 66 49 3 51 61 29 10 72 71 22 63 4 74 67

*/

标签:opt,8.3,int,double,t1,dp,define
来源: https://www.cnblogs.com/knife-rose/p/16552193.html