3.动态规划专题

动态规划专题

\(A\) CF494C Helping People

  • 观察到区间只有相离或包含关系,类似线段树的管辖区间,考虑将其构成树形关系。为方便代码书写,将原来的森林构成一棵树,即增加一个区间 \(l_{q+1}=1,r_{q+1}=n,p_{q+1}=0\)

  • 由于对于一个区间 \([l,r]\) 的最大值在经历任意次操作后,一定有 \(\max\limits_{k=l}^{r} \{ a_{k} \} \le \max\limits_{k=l}^{r} \{ a_{k}' \} \le \max\limits_{k=l}^{r} \{ a_{k} \}+q\) ,故可以据此优化空间。设 \(f_{x,i}\) 表示第 \(x\) 个节点对应的区间的最大值 \(\le \max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i\) 的概率,状态转移方程为 \(\begin{cases} f_{x,i}=(1-p_{x}) \times \prod\limits_{y \in Son(x)}f_{y,\min(q+1,\max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i-\max\limits_{k=y_{l}}^{y_{r}} \{ a_{k} \})} & i=0 \\ f_{x,i}=p_{x} \times \prod\limits_{y \in Son(x)}f_{y,\min(q+1,\max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i-\max\limits_{k=y_{l}}^{y_{r}} \{ a_{k} \}-1)}+(1-p_{x}) \times \prod\limits_{y \in Son(x)}f_{y,\min(q+1,\max\limits_{k=x_{l}}^{x_{r}} \{ a_{k} \}+i-\max\limits_{k=y_{l}}^{y_{r}} \{ a_{k} \})} & i \ne 0 \end{cases}\)

  • 假设区间按照左端点升序,右端点降序的方式排序。最终,有 \(f_{1,0} \times \max\limits_{k=1}^{n} \{ a_{k} \}+\sum\limits_{i=1}^{q+1}(f_{1,i}-f_{1,i-1}) \times (i+ \max\limits_{k=1}^{n} \{ a_{k} \})\) 即为所求。

    点击查看代码
    struct edge
    {
        int nxt,to;
    }e[5010];
    struct node
    {
        int l,r,maxx;
        double p;
    }b[5010];
    int head[5010],a[100010],fmaxx[100010][20],cnt=0;
    double f[5010][5010];
    bool cmp(node a,node b)
    {
        return (a.l==b.l)?(a.r>b.r):(a.l<b.l);
    }
    void init(int n)
    {
        for(int j=1;j<=log2(n);j++)
        {
            for(int i=1;i<=n-(1<<j)+1;i++)
            {
                fmaxx[i][j]=max(fmaxx[i][j-1],fmaxx[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int query(int l,int r)
    {
        int t=log2(r-l+1);
        return max(fmaxx[l][t],fmaxx[r-(1<<t)+1][t]);
    }
    void add(int u,int v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(int x,int q)
    {
        double sum1,sum2=1;
        for(int i=head[x];i!=0;i=e[i].nxt)
        {
            dfs(e[i].to,q);
            sum2*=f[e[i].to][min(q,b[x].maxx+0-b[e[i].to].maxx)];
        }
        f[x][0]=(1-b[x].p)*sum2;
        for(int i=1;i<=q;i++)
        {
            sum1=sum2=1;
            for(int j=head[x];j!=0;j=e[j].nxt)
            {
                sum1*=f[e[j].to][min(q,b[x].maxx+i-b[e[j].to].maxx-1)];
                sum2*=f[e[j].to][min(q,b[x].maxx+i-b[e[j].to].maxx)];
            }
            f[x][i]=b[x].p*sum1+(1-b[x].p)*sum2;
        }
    }
    int main()
    {
        int n,q,i,j;
        double ans=0;
        cin>>n>>q;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
            fmaxx[i][0]=a[i];
        }
        init(n);
        for(i=1;i<=q;i++)
        {
            cin>>b[i].l>>b[i].r>>b[i].p;
            b[i].maxx=query(b[i].l,b[i].r);
        }
        q++;
        b[q].l=1;
        b[q].r=n;
        b[q].p=0;
        b[q].maxx=query(1,n);
        sort(b+1,b+1+q,cmp);
        for(i=1;i<=q;i++)
        {   
            for(j=i-1;j>=1;j--)
            {
                if(b[j].l<=b[i].l&&b[i].r<=b[j].r)
                {
                    add(j,i);
                    break;
                }
            }
        }
        dfs(1,q);
        for(i=0;i<=q;i++)
        {
            ans+=(f[1][i]-(i==0?0:f[1][i-1]))*(i+b[1].maxx);
        }
        printf("%.9lf\n",ans);
        return 0;
    }
    

\(B\) CF922E Birds

  • 观察到 \(w\) 极大,若使用正常的背包空间会爆炸。

  • 依据 AT_dp_e Knapsack 2 的经验,考虑将背包“反”着用。设 \(f_{i,j}\) 表示到第 \(i\) 棵树时一共召唤了 \(j\) 只小鸟时剩余的最大魔力值,状态转移方程为 \(f_{i,j}=\min(\max\limits_{k=0}^{\min(j,c_{i})} \{ f_{i-1,j-k}-cost_{i} \times k+x \},w+b \times j)\) ,边界为 \(f_{0,0}=w\)

  • 最终,有 \(\max\limits_{i=0}^{\sum\limits_{j=1}^{n}c_{j}} \{ [f_{n,i} \ge 0] \times i \}\) 即为所求。

    点击查看代码
    ll c[10010],sum[10010],cost[10010],f[1010][10010];
    int main()
    {
        ll n,w,b,x,ans=0,i,j,k;
        cin>>n>>w>>b>>x;
        for(i=1;i<=n;i++)
        {
            cin>>c[i];
            sum[i]=sum[i-1]+c[i];
        }
        for(i=1;i<=n;i++)
        {
            cin>>cost[i];
        }
        memset(f,-0x3f,sizeof(f));
        f[0][0]=w;
        for(i=1;i<=n;i++)
        {
            for(j=0;j<=sum[i];j++)
            {
                for(k=0;k<=min(j,c[i]);k++)
                {
                    if(f[i-1][j-k]-cost[i]*k>=0)
                    {
                        f[i][j]=max(f[i][j],f[i-1][j-k]-cost[i]*k+x);
                    }
                }
                f[i][j]=min(f[i][j],w+b*j);
            }
        }
        for(i=sum[n];i>=0;i--)
        {
            if(f[n][i]>=0)
            {
                ans=i;
                break;
            }
        }
        cout<<ans<<endl;
        return 0;
    }  	  			 	       			 	 	
    

\(C\) CF285E Positions in Permutations

  • 若第 \(i\) 个位置是好的,则有 \(p_{i}=i-1\)\(p_{i}=i+1\)

  • \(f(m)\) 表示 \(n\) 个位置中恰好有 \(m\) 个位置是好的的排列数, \(g(m)\) 表示 \(n\) 个位置中至少有 \(m\) 个位置是好的的排列数。已知 \(g(m)= \sum\limits_{i=m}^{n} \dbinom{i}{m}f(i)\) ,由二项式反演有 \(f(m)= \sum\limits_{i=m}^{n} (-1)^{i-m} \dbinom{i}{m}g(i)\)

  • \(f_{i,j,0/1,0/1}\) 表示前 \(i\) 位,有 \(j\) 个好位置,且 \(i\) 不选/选, \(i+1\) 不选/选的方案数,状态转移方程为 \(\begin{cases} f_{i,j,0,0}=f_{i-1,j-1,0,0}+f_{i-1,j,0,0}+f_{i-1,j,1,0} \\ f_{i,j,1,0}=f_{i-1,j-1,0,1}+f_{i-1,j,0,1}+f_{i-1,j,1,1} \\ f_{i,j,0,1}=f_{i-1,j-1,0,0}+f_{i-1,j-1,1,0} \\ f_{i,j,1,1}=f_{i-1,j-1,0,1}+f_{i-1,j-1,1,1} \end{cases}\) ,边界为 \(f_{1,0,0,0}=f_{1,1,0,1}=1\)

  • 最终,有 \(g(i)=A_{n-i}^{n-i}(f_{n,i,0,0}+f_{n,i,1,0})\)

    点击查看代码
    const ll p=1000000007;
    ll inv[1010],jc[1010],jc_inv[1010],f[1010][1010][3][3],g[1010];
    ll A(ll n,ll m,ll p)
    {
        return (n>=m&&n>=0&&m>=0)?jc[n]*jc_inv[n-m]%p:0;
    }
    ll C(ll n,ll m,ll p)
    {
        return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0;
    }
    int main()
    {
        ll n,m,ans=0,i,j;
        cin>>n>>m;
        inv[1]=1;
        jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
        for(i=2;i<=n;i++)
        {
            inv[i]=(p-p/i)*inv[p%i]%p;
            jc[i]=jc[i-1]*i%p;
            jc_inv[i]=jc_inv[i-1]*inv[i]%p;
        }
        f[1][0][0][0]=f[1][1][0][1]=1;
        for(i=2;i<=n;i++)
        {
            for(j=0;j<=i;j++)
            {
                f[i][j][0][0]=(f[i-1][j][0][0]+f[i-1][j][1][0])%p;
                f[i][j][1][0]=(f[i-1][j][0][1]+f[i-1][j][1][1])%p;
                if(j-1>=0)
                {
                    f[i][j][0][0]=(f[i][j][0][0]+f[i-1][j-1][0][0])%p;
                    f[i][j][1][0]=(f[i][j][1][0]+f[i-1][j-1][0][1])%p;
                    f[i][j][0][1]=(f[i-1][j-1][0][0]+f[i-1][j-1][1][0])%p;
                    f[i][j][1][1]=(f[i-1][j-1][0][1]+f[i-1][j-1][1][1])%p;
                }
            }
        }
        for(i=0;i<=n;i++)
        {
            g[i]=((f[n][i][0][0]+f[n][i][1][0])%p)*A(n-i,n-i,p)%p;
        }
        for(i=m;i<=n;i++)
        {
            ans=(ans+(((i-m)%2==0)?1:-1)*C(i,m,p)*g[i]%p+p)%p;
        }
        cout<<ans<<endl;
        return 0;
    }
    

\(D\) CF1168D Anagram Paths

\(E\) CF573D Bear and Cavalry

  • 考虑如果没有骑士不能骑自己的马的限制,那么就转换成了 普及模拟2 T2 内积 的结论,将 \(w,h\) 分别排序后,有 \(\sum\limits_{i=1}^{n}w_{i}h_{i}\) 即为所求。

  • 结论貌似有点多,详见 @wang54321冲刺NOIP2024专题之dp专题 Bear and Cavalry

  • 对于每次修改,至多对左右两边各 \(3\) 个产生影响,暴力修改即可。

  • \(f_{i}\) 表示当前分配到第 \(i\) 个士兵时的最大力量,状态转移方程为 \(f_{i}=\max \begin{cases} f_{i-1}+w_{i}h_{i} \\ f_{i-2}+w_{i}h_{i-1}+w_{i-1}h_{i} \\ f_{i-3}+\max(w_{i}h_{i-1}+w_{i-1}h_{i-2}+w_{i-2}h_{i},w_{i}h_{i-2}+w_{i-1}h_{i}+w_{i-2}h_{i-1}) \end{cases}\)

    点击查看代码
    struct node
    {
        ll w,id;
    }a[300010],b[300010];
    ll posa[300010],posb[300010],ban[300010],w[300010][4],f[300010];
    bool cmp(node a,node b)
    {
        return (a.w==b.w)?(a.id<b.id):(a.w<b.w);
    }
    int main()
    {
        ll n,m,l,r,x,y,i,j;
        cin>>n>>m;
        memset(w,-0x3f,sizeof(w));
        for(i=1;i<=n;i++)
        {
            cin>>a[i].w;
            a[i].id=i;
        }
        for(i=1;i<=n;i++)
        {
            cin>>b[i].w;
            b[i].id=i;
        }
        sort(a+1,a+1+n,cmp);
        sort(b+1,b+1+n,cmp);
        for(i=1;i<=n;i++)
        {
            posa[a[i].id]=posb[b[i].id]=i;
        }
        for(i=1;i<=n;i++)
        {
            ban[i]=posb[a[i].id];
        }
        for(i=1;i<=n;i++)
        {
            if(i-1>=0&&ban[i]!=i)
            {
                w[i][1]=max(w[i][1],a[i].w*b[i].w);
            }
            if(i-2>=0&&ban[i]!=i-1&&ban[i-1]!=i)
            {
                w[i][2]=max(w[i][2],a[i].w*b[i-1].w+a[i-1].w*b[i].w);
            }
            if(i-3>=0&&ban[i]!=i-2&&ban[i-1]!=i&&ban[i-2]!=i-1)
            {
                w[i][3]=max(w[i][3],a[i].w*b[i-2].w+a[i-1].w*b[i].w+a[i-2].w*b[i-1].w);
            }
            if(i-3>=0&&ban[i]!=i-1&&ban[i-1]!=i-2&&ban[i-2]!=i)
            {
                w[i][3]=max(w[i][3],a[i].w*b[i-1].w+a[i-1].w*b[i-2].w+a[i-2].w*b[i].w);
            }
        }
        for(j=1;j<=m;j++)
        {
            cin>>x>>y;
            swap(ban[posa[x]],ban[posa[y]]);
            l=max(1ll,posa[x]-3);
            r=min(n,posa[x]+3);
            for(i=l;i<=r;i++)
            {
                w[i][1]=w[i][2]=w[i][3]=-0x3f3f3f3f;
                if(i-1>=0&&ban[i]!=i)
                {
                    w[i][1]=max(w[i][1],a[i].w*b[i].w);
                }
                if(i-2>=0&&ban[i]!=i-1&&ban[i-1]!=i)
                {
                    w[i][2]=max(w[i][2],a[i].w*b[i-1].w+a[i-1].w*b[i].w);
                }
                if(i-3>=0&&ban[i]!=i-2&&ban[i-1]!=i&&ban[i-2]!=i-1)
                {
                    w[i][3]=max(w[i][3],a[i].w*b[i-2].w+a[i-1].w*b[i].w+a[i-2].w*b[i-1].w);
                }
                if(i-3>=0&&ban[i]!=i-1&&ban[i-1]!=i-2&&ban[i-2]!=i)
                {
                    w[i][3]=max(w[i][3],a[i].w*b[i-1].w+a[i-1].w*b[i-2].w+a[i-2].w*b[i].w);
                }
            }
            l=max(1ll,posa[y]-3);
            r=min(n,posa[y]+3);
            for(i=l;i<=r;i++)
            {
                w[i][1]=w[i][2]=w[i][3]=-0x3f3f3f3f;
                if(i-1>=0&&ban[i]!=i)
                {
                    w[i][1]=max(w[i][1],a[i].w*b[i].w);
                }
                if(i-2>=0&&ban[i]!=i-1&&ban[i-1]!=i)
                {
                    w[i][2]=max(w[i][2],a[i].w*b[i-1].w+a[i-1].w*b[i].w);
                }
                if(i-3>=0&&ban[i]!=i-2&&ban[i-1]!=i&&ban[i-2]!=i-1)
                {
                    w[i][3]=max(w[i][3],a[i].w*b[i-2].w+a[i-1].w*b[i].w+a[i-2].w*b[i-1].w);
                }
                if(i-3>=0&&ban[i]!=i-1&&ban[i-1]!=i-2&&ban[i-2]!=i)
                {
                    w[i][3]=max(w[i][3],a[i].w*b[i-1].w+a[i-1].w*b[i-2].w+a[i-2].w*b[i].w);
                }
            }
            f[0]=0;
            for(i=1;i<=n;i++)
            {
                f[i]=f[i-1]+w[i][1];
                if(i-2>=0)
                {
                    f[i]=max(f[i],f[i-2]+w[i][2]);
                }
                if(i-3>=0)
                {
                    f[i]=max(f[i],f[i-3]+w[i][3]);
                }
            }
            cout<<f[n]<<endl;
        }
        return 0;
    }
    

\(F\) CF1392H ZS Shuffles Cards

  • \(f_{i}\) 表示有 \(i\) 张数字牌没进入 \(S\) ,即 \(S\) 中只有 \(n-i\) 张数字牌时的期望轮数,有 \(f_{i}= \frac{i}{i+m}f_{i-1}+ \frac{m}{i+m}(f_{i}+1)\) ,解得 \(f_{i}=f_{i-1}+\frac{m}{i}\) ,边界为 \(f_{0}=1\)

  • 由于每一张数字牌在 joker 牌前被抽中的概率为 \(\frac{1}{m+1}\) ,故每一轮的期望牌数为 \(1+ \sum\limits_{i=1}^{n} \frac{1}{m+1}= 1+\frac{n}{m+1}\)

  • 最终,有 \(f_{n}(1+\frac{n}{m+1})\) 即为所求。

    点击查看代码
    const ll p=998244353;
    ll f[2000010];
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b>0)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    int main()
    {   
        ll n,m,i;
        cin>>n>>m;
        f[0]=1;
        for(i=1;i<=n;i++)
        {
            f[i]=(f[i-1]+qpow(i,p-2,p)*m%p)%p;
        }
        cout<<f[n]*((qpow(m+1,p-2,p)*n%p+1)%p)%p<<endl;
        return 0;
    }
    

\(G\) CF838C Future Failure

\(H\) CF536D Tavas in Kansas

  • 分别以 \(s,t\) 为起点,预处理出到其他所有点的最短距离 \(dis_{s,k},dis_{t,k}\)

  • 由于只关注 \(dis\) 的相对大小关系,故可以将 \(dis_{s,k},dis_{t,k}\) 分别离散化成 \(x_{k},y_{k}\) ,形成一个 \(n \times n\) 的矩阵,此时 Tavas 只能取从上到下的若干行, Nafas 只能取从左到右的若干列。

  • \(f_{i,j,0/1}\) 表示当前轮到 Tavas / Nafas 取, Tavas 取到了第 \(i\) 行, Nafas 取到了第 \(j\) 列时 Tavas 的权值减去 Nafas的权值的最大/最小值,状态转移方程为 \(f_{i,j,0}=\begin{cases} f_{i+1,j,0} & val(1,i,j,i,n)=0 \\ \max(f_{i+1,j,0},f_{i+1,j,1})+val(2,i,j,i,n) & val(1,i,j,i,n) \ne 0 \end{cases},f_{i,j,1}=\begin{cases} f_{i,j+1,0} & val(1,i,j,n,j)=0 \\ \min(f_{i,j+1,0},f_{i,j+1,1})-val(2,i,j,n,j) & val(1,i,j,n,j) \ne 0 \end{cases}\) ,其中 \(val(1,x_{1},y_{1},x_{2},y_{2})\) 表示左上角为 \(x_{1},y_{1}\) ,右下角为 \(x_{2},y_{2}\) 的矩形的点的权值之和, \(val(2,x_{1},y_{1},x_{2},y_{2})\) 表示左上角为 \(x_{1},y_{1}\) ,右下角为 \(x_{2},y_{2}\) 的矩形的点的个数之和。

  • 最终,有 \(f_{1,1,0}<0\) 时输出 Cry\(f_{1,1,0}=0\) 时输出 Flowers\(f_{1,1,0}>0\) 时输出 Break a heart

    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[200010];
    ll head[2010],dis[2010],vis[2010],p[2010],diss[2010],x[2010],y[2010],a[2010][2010],b[2010][2010],sum1[2010][2010],sum2[2010][2010],f[2010][2010][2],cnt=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    ll query(ll a[],ll x)
    {
        return lower_bound(a+1,a+1+a[0],x)-a;
    }
    void dijkstra(ll s,ll n,ll a[])
    {
        memset(dis,0x3f,sizeof(dis));
        memset(vis,0,sizeof(vis));
        priority_queue<pair<ll,ll> >q;
        ll x,i;
        dis[s]=0;
        q.push(make_pair(0,-s));
        while(q.empty()==0)
        {
            x=-q.top().second;
            q.pop();
            if(vis[x]==0)
            {
                vis[x]=1;
                for(i=head[x];i!=0;i=e[i].nxt)
                {
                    if(dis[e[i].to]>dis[x]+e[i].w)
                    {
                        dis[e[i].to]=dis[x]+e[i].w;
                        q.push(make_pair(-dis[e[i].to],-e[i].to));
                    }
                }
            }
        }
        for(i=1;i<=n;i++)
        {
            diss[i]=dis[i];
        }
        sort(diss+1,diss+1+n);
        diss[0]=unique(diss+1,diss+1+n)-(diss+1);
        for(i=1;i<=n;i++)
        {
            a[i]=query(diss,dis[i]);
        }
    }
    ll work(ll x1,ll y1,ll x2,ll y2,ll sum[2010][2010])
    {
        return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
    }
    int main()
    {
        ll n,m,s,t,u,v,w,i,j;
        cin>>n>>m>>s>>t;
        for(i=1;i<=n;i++)
        {
            cin>>p[i];
        }
        for(i=1;i<=m;i++)
        {
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
        }
        dijkstra(s,n,x);
        dijkstra(t,n,y);
        for(i=1;i<=n;i++)
        {
            a[x[i]][y[i]]+=p[i];
            b[x[i]][y[i]]++;
        }
        for(i=1;i<=n+1;i++)
        {
            for(j=1;j<=n+1;j++)
            {
                sum1[i][j]=sum1[i-1][j]+sum1[i][j-1]-sum1[i-1][j-1]+a[i][j];
                sum2[i][j]=sum2[i-1][j]+sum2[i][j-1]-sum2[i-1][j-1]+b[i][j];
            }
        }
        for(i=n+1;i>=1;i--)
        {
            for(j=n+1;j>=1;j--)
            {
                if(i!=n+1||j!=n+1)
                {
                    f[i][j][0]=(work(i,j,i,n,sum2)==0)?f[i+1][j][0]:max(f[i+1][j][0],f[i+1][j][1])+work(i,j,i,n,sum1);
                    f[i][j][1]=(work(i,j,n,j,sum2)==0)?f[i][j+1][1]:min(f[i][j+1][0],f[i][j+1][1])-work(i,j,n,j,sum1);
                }
            }
        }
        if(f[1][1][0]<0)
        {
            cout<<"Cry"<<endl;
        }
        if(f[1][1][0]==0)
        {
            cout<<"Flowers"<<endl;
        }
        if(f[1][1][0]>0)
        {
            cout<<"Break a heart"<<endl;
        }
        return 0;
    }
    

\(I\) CF1628D1 Game on Sum (Easy Version)

  • CF1628D1 Game on Sum (Easy Version)
    • \(x_{i}\) 表示第 \(i\) 轮时 Alice 选择的数。

    • \(f_{i,j}\) 表示已经进行了 \(i\) 轮,且使用了 \(j\) 次加法时的最大得分,状态转移方程为 \(f_{i,j}= \max \{ \min(f_{i-1,j}-x_{i},f_{i-1,j-1}+x_{i}) \}=\frac{f_{i-1,j}+f_{i-1,j-1}}{2}\) ,边界为 \(\begin{cases} f_{i,0 \sim \infty}=0 & i=0 \\ f_{i,0}=0, f_{i,i}=i \times k & i \ne 0 \end{cases}\)

      • 由于 Bob 想让结果尽可能小,所以有 \(f_{i,j}= \min(f_{i-1,j}-x_{i},f_{i-1,j-1}+x_{i})\)
      • 由于 Alice 想让结果尽可能大,所以会让 \(\min(f_{i-1,j}-x_{i},f_{i-1,j-1}+x_{i})\) 取到最大值,即 \(f_{i-1,j}-x_{i}=f_{i-1,j-1}+x_{i}\) 时,解得 \(x_{i}= \frac{f_{i-1,j}-f_{i-1,j-1}}{2}\) ,代入原式有 \(f_{i,j}=\frac{f_{i-1,j}+f_{i-1,j-1}}{2}\)
    • 由于 Bob 想让结果尽可能小,所以至多使用 \(m\) 次加法,故最终 \(f_{n,m}\) 即为所求。

    • 另外,由于求解 \(f_{n,m}\) 的过程中只有加法和 \(\times \frac{1}{2}\) 运算,故可以将 \(k\) 缩小至 \(1\) 进行预处理 \(f_{n,m}\) ,询问时再扩大到 \(k\) ,即 \(f_{n,m} \times k\)

      点击查看代码
      const ll p=1000000007;
      ll f[2010][2010];
      ll qpow(ll a,ll b,ll p)
      {
          ll ans=1;
          while(b>0)
          {
              if(b&1)
              {
                  ans=ans*a%p;
              }
              b>>=1;
              a=a*a%p;
          }
          return ans;
      }
      int main()
      {
          ll t,n,m,k,i,j;
          cin>>t;
          for(i=1;i<=2000;i++)
          {
              f[i][0]=0;
              f[i][i]=i;
              for(j=1;j<=i-1;j++)
              {
                  f[i][j]=((f[i-1][j]+f[i-1][j-1])%p)*qpow(2,p-2,p)%p;
              }
          }
          for(i=1;i<=t;i++)
          {
              cin>>n>>m>>k;
              cout<<f[n][m]*k%p<<endl;
          }
          return 0;
      }
      
  • luogu P7137 [THUPC2021 初赛] 切切糕
    • 从贪心的角度分析, Tinytree 的“优先选糕权”要尽量留给 \(a_{i}\) 较大的切糕,故需要先将 \(a\) 按照降序排序。

    • 设第 \(i\) 块切糕 Kiana 切成的切糕大小为 \(x_{i}\)\(a_{i}-x_{i}\) ,规定有 \(x_{i} \ge a_{i}-x_{i}\)

    • \(f_{i,j}\) 表示已经切了 \(i\) 块切糕,且使用了 \(j\) 次“优先选糕权”时 Tinytree 的最大总大小,状态转移方程为 \(f_{i,j}= \max \{ \min(f_{i-1,j}+a_{i}-x_{i},f_{i-1,j-1}+x_{i}),f_{i-1,j} \}=\max(\frac{f_{i-1,j}+f_{i-1,j-1}+a_{i}}{2},f_{i-1,j})\) ,边界为 \(\begin{cases} f_{i,0 \sim \infty}=0 & i=0 \\ f_{i,0}=0, f_{i,i}=\frac{\sum\limits_{j=1}^{i}a_{j}}{2} & i \ne 0 \end{cases}\)

      • 由于算出的 \(x_{i}\) 可能使 \(a_{i}-x_{i}<0\) 成立,故最后需要与 \(f_{i-1,j}\)\(\max\)
    • 由于 Tinytree 想让 Kiana 的总大小尽可能小,所以一定会使用 \(m\) 次“优先选糕权”,使自己的总大小尽可能大,故最终 \(\sum\limits_{i=1}^{n}a_{i}-f_{n,m}\) 即为所求。

      点击查看代码
      ll a[2510],sum[2510];
      double f[2510][2510];
      int main()
      {
          ll n,m,i,j;
          cin>>n>>m;
          for(i=1;i<=n;i++)
          {
              cin>>a[i];
          }
          sort(a+1,a+1+n,greater<ll>());
          for(i=1;i<=n;i++)
          {
              sum[i]=sum[i-1]+a[i];
          }
          for(i=1;i<=n;i++)
          {
              f[i][0]=0;
              f[i][i]=1.0*sum[i]/2;
              for(j=1;j<=i-1;j++)
              {
                  f[i][j]=max((f[i-1][j]+f[i-1][j-1]+1.0*a[i])/2,f[i-1][j]);
              }
          }
          printf("%.6lf",sum[n]-f[n][m]);
          return 0;
      }
      
  • CF1628D2 Game on Sum (Hard Version)
    • 观察到 \(f\) 的转移过程比较像杨辉三角的转移过程。
    • \(n=m\) 时,有 \(f_{n,m}=m \times k\) 即为所求。
    • \(n \ne m\) 时,
      • 考虑计算 \(f_{i,i}\)\(f_{n,m}\) 产生的贡献。

        • \(f_{i,i}\)\(f_{n,m}\) 一共进行了 \(n-i\)\(\times \frac{1}{2}\) 操作。
        • \(f_{i,j}\) 会向 \(f_{i+1,j}\)\(f_{i+1,j+1}\) 进行转移,故等价于 \((i,j)\) 一次可以走到 \((i+1,j)\)\((i+1,j+1)\) ,求不经过形如 \((x,x)\) 的点时从 \((i,i)\) 走到 \((n,m)\) 的方案数,即从 \((i+1,i)\) 走到 \((n,m)\) 的方案数 \(\dbinom{n-(i+1)}{m-i}\)
      • \(\sum\limits_{i=1}^{m}\frac{i \times k \times \binom{n-(i+1)}{m-i}}{2^{n-i}}\) 即为所求。

        点击查看代码
        const ll p=1000000007;
        ll jc[2000010],inv[2000010],jc_inv[2000010],a[2000010];
        ll qpow(ll a,ll b,ll p)
        {
            ll ans=1;
            while(b>0)
            {
                if(b&1)
                {
                    ans=ans*a%p;
                }
                b>>=1;
                a=a*a%p;
            }
            return ans;
        }
        ll C(ll n,ll m,ll p)
        {
            return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0;
        }
        int main()
        {
            ll t,n,m,k,ans=0,i,j;
            cin>>t;
            jc[0]=jc_inv[0]=1;
            for(i=1;i<=1000000;i++)
            {
                a[i]=i;
                jc[i]=jc[i-1]*a[i]%p;
            }
            for(i=1000001;i<=2000000;i++)
            {
                a[i]=qpow(2,i-1000000-1,p);
                jc[i]=jc[i-1]*a[i]%p;
            }
            jc_inv[2000000]=qpow(jc[2000000],p-2,p);
            for(i=2000000-1;i>=1;i--)
            {
                jc_inv[i]=jc_inv[i+1]*a[i+1]%p;
            }
            for(i=1;i<=2000000;i++)
            {
                inv[i]=jc_inv[i]*jc[i-1]%p;
            }
            for(i=1;i<=t;i++)
            {
                cin>>n>>m>>k;
                ans=0;
                if(n==m)
                {
                    cout<<m*k%p<<endl;
                }
                else
                {
                    for(j=1;j<=m;j++)
                    {
                        ans=(ans+((j*k%p)*C(n-(j+1),m-j,p)%p)*inv[1000000+1+n-j]%p)%p;
                    }
                    cout<<ans<<endl;
                }
            }
            return 0;
        }
        
posted @ 2024-04-01 15:42  hzoi_Shadow  阅读(89)  评论(1编辑  收藏  举报
扩大
缩小