• 【BZOJ3167】[HEOI2013]SAO(动态规划)


    【BZOJ3167】[HEOI2013]SAO(动态规划)

    题面

    BZOJ
    洛谷

    题解

    显然限制条件是一个(DAG)(不考虑边的方向的话就是一棵树了)。
    那么考虑树型(dp),设(f[i][j])表示当前点(i)在其子树内的排名为(j)的方案数。
    每次考虑加入一棵子树,即考虑把(f[v][k])加入到(f[u][i])的贡献中。
    分类讨论,如果(v)应当在(u)之前,枚举(v)的子树内一共有多少个点在(u)之前,那么假设(u)当前的序列长度为(x)(v)当前的序列长度为(y),枚举一共有(j)(v)的子树内的点在(u)之前。
    那么此时的方案数就是:(f[u][i+j]leftarrow {i+j-1choose i-1}*f[u][i]*f[v][k]*{y-j+x-ichoose x-i})
    然后我们来学习枚举(来写假的pascal代码)

    for i:=1 to x do
        for k:=1 to y do 
            for j:=k to y do 
                f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]
    

    似乎这个(k)只在(f[v][k])中出现过???
    那么我们换一下

    for i:=1 to x do
        for j:=1 to y do 
            for k:=1 to j do 
                f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]
    

    发现把(f[v])数组前缀和一下底下这个玩意就可以做到(O(1))转移啦。
    而如果(v)要放在(u)之后也是类似的。
    还是枚举有多少个放在(u)之前,那么这一部分的贡献是没变的,只是转移的时候的范围要变一变。

    for i:=1 to x do
        for k:=1 to y do 
            for j:=0 to k-1 do 
                f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]
    

    还是换一下

    for i:=1 to x do
        for j:=0 to y do 
            for k:=i+1 to y do 
                f[u][i+j]+=f[u][i]*f[v][k]*C[i+j-1][i-1]*C[x+y-i-j][x-i]
    

    这样子上面两层循环枚举的都是子树大小,因此全局总的复杂度就是(O(n^2))啦。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define MOD 1000000007
    #define MAX 1010
    void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    struct Line{int v,next,w;}e[MAX<<1];
    int h[MAX],cnt;
    inline void Add(int u,int v,int w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
    void init(){memset(h,0,sizeof(h));cnt=1;}
    int n,C[MAX][MAX];char ch[2];
    int f[MAX][MAX],sz[MAX],tmp[MAX];
    void dfs(int u,int ff)
    {
    	f[u][1]=1;sz[u]=1;
    	for(int i=h[u];i;i=e[i].next)
    	{
    		int v=e[i].v;if(v==ff)continue;
    		dfs(e[i].v,u);
    		for(int a=1;a<=sz[u];++a)
    			for(int b=0;b<=sz[v];++b)
    				if(e[i].w)
    					add(tmp[a+b],1ll*f[u][a]*f[v][b]%MOD*C[a+b-1][a-1]%MOD*C[sz[u]+sz[v]-a-b][sz[u]-a]%MOD);
    				else
    					add(tmp[a+b],1ll*f[u][a]*(f[v][sz[v]]-f[v][b]+MOD)%MOD*C[a+b-1][a-1]%MOD*C[sz[u]+sz[v]-a-b][sz[u]-a]%MOD);
    		sz[u]+=sz[v];
    		for(int j=1;j<=sz[u];++j)f[u][j]=tmp[j],tmp[j]=0;
    	}
    	for(int j=1;j<=sz[u];++j)add(f[u][j],f[u][j-1]);
    }
    int main()
    {
    	for(int i=0;i<MAX;++i)C[i][0]=1;
    	for(int i=1;i<MAX;++i)
    		for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
    	int T=read();
    	while(T--)
    	{
    		init();n=read();
    		for(int i=1,u,v;i<n;++i)
    		{
    			u=read()+1;scanf("%s",ch);v=read()+1;
    			if(ch[0]=='<')Add(u,v,0),Add(v,u,1);
    			else Add(u,v,1),Add(v,u,0);
    		}
    		dfs(1,0);
    		printf("%d
    ",f[1][n]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    JS弹出下载对话框以及实现常见文件类型的下载
    什么是物理像素、虚拟像素、逻辑像素、设备像素,什么又是 PPI, DPI, DPR 和 DIP
    学会git玩转github,结尾有惊喜!有惊喜!有惊喜!
    Menu实现逻辑
    控件保持多种绘图状态的做法
    2个函数宏技巧
    绘图 Painter转接口封装的方式
    DirectUI消息循环的简单封装
    c++以代理的方式来实现接口化编程
    c++对象工厂
  • 原文地址:https://www.cnblogs.com/cjyyb/p/10485535.html
Copyright © 2020-2023  润新知