zoukankan      html  css  js  c++  java
  • AtCoder agc024_e

    tzc:“AtC风格的计数DP。”我就想问了,AtC的题怎么可能不是AtC风格的呢?

    洛谷题目页面传送门 & AtC题目页面传送门

    给定(n,m,p),求有多少个值域为([1,m]capmathbb Z)的序列(n+1)元组((a_0,a_1,cdots,a_n)),满足:

    1. (forall iin[0,n],|a_i|=i)
    2. (forall iin[0,n))(a_i)(a_{i+1})的子序列;
    3. (forall iin[0,n),a_i<a_{i+1})

    答案对(p)取模。

    (n,min[1,300],pinleft[2,10^9 ight])

    显然,原问题等价于,一个序列(a)一开始为空,(n)次每次往里任意位置塞一个([1,m])内的数,使得字典序大于原来的序列,求(n)次组成的序列组的个数(注意这里不能说操作的数量,因为在不同位置插入相同数可能得到的序列一样)。

    考虑在位置(x)插入(y)合法的充要条件。若(x=|a|+1)显然合法。剩下的显然要满足([y]+a_{x,|a|}>a_{x,|a|})。若(y eq a_x),那么字典序相对大小决定在它们身上,所以(y>a_x)。否则的话,,,注意到若把(y)插入到往右数第一个满足(y eq a_{x'})的位置(x'),那么得到的序列是一样的,为了去重,我们可以直接不考虑这种情况,反而更方便了。于是充要条件就是(x=|a+1|)(y>a_x)

    分析完之后还是不会做。这是一个动态的插入问题,每次的(a)都会变。不妨将时间定格在最后一刻,所有数都各就各位了,再来考虑每个位置(i)上的值(v_i)与被插入的时间(时间戳)(dfn_i)

    稍微转化一下,此时一个((v,dfn))序列合法当且仅当,(forall iin[1,n],v_i>v_{minlimits_{j>i,dfn_j<dfn_i}{j}})(为了方便,设(v_{n+1}=dfn_{n+1}=0),合理性显然)。考虑(v_i)(>)的那个东西固定的一个,就是使得(dfn_x=1)(x)。它的条件是(v_x>v_{n+1}=0)。然后不难发现,(forall iin[1,x),minlimits_{j>i,dfn_j<dfn_i}{j}in[1,x]),对应的,(forall iin(x,n],minlimits_{j>i,dfn_j<dfn_i}{j}in[x+1,n+1])。这样就可以把问题分解成(x)左右两半相似的问题,其中左边的值域变成((v_x,m]),右边值域不变。问题就迎刃而解了。

    考虑DP。设(dp_{i,j})表示((v,dfn))序列的大小为(i),值域为([j,m])的方案数。边界(dp_{0,j}=1),目标(dp_{n,1})。枚举(dfn_x=1)(x)(v_x)两个东西,就可以转移了,注意到若左右各找一个方案,那么左右两半内部的(dfn)相对大小固定,而跨越左右两半的(dfn)相对大小無·所·謂,将它们的(dfn)值域共同扩展,还要乘上一个组合数。

    状态转移方程:

    [dp_{i,j}=sum_{k=1}^isum_{o=j}^mdp_{k-1,o+1}dp_{i-k,j}inom{i-1}{k-1} ]

    不过这样时间复杂度是(mathrm O!left(n^4 ight))的(视(n,m)同阶),然鹅很好优化,移一下(sum)得到

    [dp_{i,j}=sum_{k=1}^idp_{i-k,j}inom{i-1}{k-1}sum_{o=j}^mdp_{k-1,o+1} ]

    则实时维护(forall iin[0,n],dp_i)的前缀和,后面那个(sum)调用一下前缀和复杂度就少一个(n)了。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=300,M=300;
    int n,m,mod;
    int comb[N+1][N+1];
    int dp[N+1][M+2];
    int Sum[N+1][M+2];
    int main(){
    	cin>>n>>m>>mod;
    	comb[0][0]=1;
    	for(int i=1;i<=n;i++)for(int j=0;j<=i;j++)comb[i][j]=((j?comb[i-1][j-1]:0)+(j<i?comb[i-1][j]:0))%mod;//预处理组合数 
    	for(int i=1;i<=m+1;i++)Sum[0][i]=(Sum[0][i-1]+(dp[0][i]=1))%mod;//边界 
    	for(int i=1;i<=n;i++){//转移 
    		for(int j=1;j<=m;j++){
    			for(int k=1;k<=i;k++)(dp[i][j]+=1ll*dp[i-k][j]*comb[i-1][k-1]%mod*(Sum[k-1][m+1]-Sum[k-1][j])%mod)%=mod;//转移方程 
    			Sum[i][j]=(Sum[i][j-1]+dp[i][j])%mod;//维护前缀和 
    //			printf("dp[%d][%d]=%d
    ",i,j,dp[i][j]);
    		}
    		Sum[i][m+1]=Sum[i][m];//维护前缀和 
    	}
    	cout<<(dp[n][1]+mod)%mod;//目标 
    	return 0;
    }
    
  • 相关阅读:
    vi 编辑内容中查找字符位置
    使用 Oracle GoldenGate 在 Microsoft SQL Server 和 Oracle Database 之间复制事务
    EBS R12 更改SYSADMIN密码
    两种步骤 更改 EBS R12界面LOGO以及内容
    Get Current LOV Query SQL
    Oracle Gateways透明网关访问SQL Server
    Oracle 数据集成的实际解决方案
    将SQLServer2005中的数据同步到Oracle中
    ogg实现oracle到sql server 2005的同步
    wince和window mobile winphone
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/AtCoder-agc024-e.html
Copyright © 2011-2022 走看看