zoukankan      html  css  js  c++  java
  • 洛谷 P4099

    题面传送门

    题意:

    • 有一个有向图 (G),其基图是一棵树
    • 求它拓扑序的个数 (mod (10^9+7))
    • (n in [1,1000])

    如果你按照拓扑排序的方法来做,那恐怕你已经想偏了。因为“求拓扑排序个数”本身就是一个 NP 问题,只能使用指数级的状压 (dp) 一类的算法来解决,而本题数据范围给到 (1000),暗示着我们要充分利用“(G) 的基图是一棵树”这个条件。
    故可以想到树形 (dp)(dp[x][i]) 表示将以 (x) 为根的子树进行拓扑排序后,(x) 位于拓扑序的第 (i) 位的方案数。
    然后,然后……我就不会做了/kk
    考虑转移,也就是要合并两个序列 (x,y),用 (dp[x][i])(dp[y][j]) 更新 (newdp[x][k])。要分 (x) 拓扑序在 (y) 前面和在 (y) 后面两种情况。这里以 (x) 拓扑序在 (y) 前面为例。
    一个非常蠢的想法是,枚举 (x,y) 在新序列中的位置,然后乘法原理转移。但是这样肯定爆炸。
    注意到,当 (i,j) 确定后,(k) 可以取到的范围是一个区间。
    因为原本在 (x) 前面的那 (i-1) 个数现在依旧在 (x) 前面;原本在 (x) 后面的数现在依旧在 (x) 后面。
    而因为 (x) 的拓扑序小于 (y) 的拓扑序,原本在 (y) 后面的数现在也在 (x) 的后面;原本在 (y) 前面的那 (j-1) 个数,现在可以在 (x) 前面,也可以在 (x) 后面。
    故我们有 (k in [i-1+1,i-1+j-1+1]=[i,i+j-1])
    (k) 的范围确定之后,转移当然还要使用组合数。我们考虑将原本 (x) 序列中的数在新序列中的位置确定下来,这样整个序列就已经确定了。
    新序列中,比 (x) 小的那 (k-1) 个数中,必定有 (i-1) 个来自原序列,故这一部分的方案数为 (dbinom{k-1}{i-1})
    同理,填好比 (x) 大的数的方案数为 (dbinom{siz[x]+siz[y]-k}{siz[x]-i})
    因此我们有状态转移方程:

    [newdp[x][k]+=dp[x][i] imes dp[y][j] imes dbinom{k-1}{i-1} imes dbinom{siz[x]+siz[y]-k}{siz[x]-i} ]

    这个转移是 (mathcal O(n^3)),考虑对其进行优化。
    首先给出朴素转移的伪代码:

    for i from 1 to siz[x]:
        for j from 1 to siz[y]:
            for k from i to i+j-1:
                 transfer
    

    注意到我们状态转移方程里 (k) 出现的频率很高,但是 (j) 出现的频率很低。
    这启示我们,可以改变循环的顺序,先枚举 (k) 再枚举 (j),看看会发生些什么。
    至于循环范围的推导……会做这道题的人应该不至于不会推导循环范围吧,实在不行打个表也行啊(
    因此我们得到先枚举 (k) 再枚举 (j) 的伪代码:

    for i from 1 to siz[x]:
        for k from i to i+siz[y]-1:
            for j from k-i+1 to siz[y]:
                 transfer
    

    然后发现 (j) 变化的区间是一个后缀,故可以使用前缀和进行优化。
    以上是 (x)(y) 之前的情况。(x)(y) 之后的情况也同理,只不过把 (k) 的变化范围改为 ([i+j,i+siz[y]]) 而已,其余部分见代码。

    /*
    Contest: -
    Problem: P4099
    Author: tzc_wk
    Time: 2020.8.14
    */
    #include <bits/stdc++.h>
    using namespace std;
    #define fi			first
    #define se			second
    #define pb			push_back
    #define fz(i,a,b)	for(int i=a;i<=b;i++)
    #define fd(i,a,b)	for(int i=a;i>=b;i--)
    #define foreach(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
    #define all(a)		a.begin(),a.end()
    #define fill0(a)	memset(a,0,sizeof(a))
    #define fill1(a)	memset(a,-1,sizeof(a))
    #define fillbig(a)	memset(a,0x3f,sizeof(a))
    #define y1			y1010101010101
    #define y0			y0101010101010
    typedef pair<int,int> pii;
    typedef long long ll;
    inline int read(){
    	int x=0,neg=1;char c=getchar();
    	while(!isdigit(c)){
    		if(c=='-') neg=-1;
    		c=getchar();
    	}
    	while(isdigit(c)) x=x*10+c-'0',c=getchar();
    	return x*neg;
    }
    const int MOD=1e9+7;
    int n,ecnt,head[2005],to[2005],sgn[2005],nxt[2005];
    ll dp[1005][1005],sum[1005][1005],C[1005][1005];
    int siz[1005];
    inline int get(char c){
    	return (c=='>')?1:-1;
    }
    inline void adde(int u,int v,int w){
    	to[++ecnt]=v;sgn[ecnt]=w;nxt[ecnt]=head[u];head[u]=ecnt;
    }
    inline void clear(){
    	ecnt=0;fill0(head);fill0(to);fill0(sgn);fill0(nxt);fill0(dp);
    }
    ll tmp[1005];
    inline void dfs(int x,int fa){
    	siz[x]=1;dp[x][1]=1;
    	for(int i=head[x];i;i=nxt[i]){
    		int y=to[i],z=sgn[i];if(y==fa) continue;dfs(y,x);
    		fz(i,1,siz[x]) tmp[i]=dp[x][i],dp[x][i]=0;
    		if(z==1) fz(i,1,siz[x]) fz(k,i,i+siz[y]-1)
    			dp[x][k]=(dp[x][k]+tmp[i]*(sum[y][siz[y]]-sum[y][k-i]+MOD)%MOD*C[k-1][i-1]%MOD*C[siz[x]+siz[y]-k][siz[x]-i]%MOD)%MOD;
    		else fz(i,1,siz[x]) fz(k,i+1,i+siz[y])
    			dp[x][k]=(dp[x][k]+tmp[i]*sum[y][k-i]%MOD*C[k-1][i-1]%MOD*C[siz[x]+siz[y]-k][siz[x]-i]%MOD)%MOD;
    		siz[x]+=siz[y];
    	}
    //	fz(i,1,siz[x]){
    //		cout<<x<<" "<<i<<" "<<dp[x][i]<<endl;
    //	}
    	fz(i,1,siz[x]) sum[x][i]=(sum[x][i-1]+dp[x][i])%MOD;
    }
    inline void prework(){
    	fz(i,0,1000) C[i][0]=1;
    	fz(i,1,1000) fz(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
    }
    inline void solve(){
    	n=read();clear();
    	fz(i,1,n-1){
    		int u,v;char c;cin>>u>>c>>v;u++;v++;
    		adde(u,v,get(c));adde(v,u,-get(c));
    	}
    	dfs(1,0);
    	ll ans=0;
    	printf("%lld
    ",sum[1][n]);
    }
    int main(){
    	prework();
    	int T=read();
    	while(T--) solve();
    	return 0;
    }
    
  • 相关阅读:
    GRIDVIEW鼠标移动行变色
    如何在网页中实现打字效果
    C#的6种常用集合类
    开发和使用Web用户控件
    C# DataGridView的常用用法
    SQL精妙语句
    Web 调试代理软件-Fiddler
    RegisterStartupScript和RegisterClientScriptBlock的用法
    简单地过一下五个控件(ScriptManager、ScriptManagerProxy、UpdatePanel、 UpdateProgress和Timer
    Android4.0 SDK功能详解
  • 原文地址:https://www.cnblogs.com/ET2006/p/luogu-P4099.html
Copyright © 2011-2022 走看看