P5241 序列

传送门

显然不能直接考虑图长什么样

考虑如何构造一个图,让一个 $B$ 序列尽可能合法

发现对于一些点,如果能让它们先弱联通,(一个节点指向下一个节点,形成一条链)

那么对于最后一个节点,它想缩几个联通块都行,可以证明,这样可以包括所有其他情况形成的 $B$ 序列

感性理解因为这样可以消耗最少的边来达到我们想要的联通块数,多出来的边就有更多选择,当然也包括连废边(联通块内的点往联通块连)

然后就只要考虑这一种 '链' 的情况就好了

考虑 $dp$ ,设 $F[i][j]$ 表示到第 $i$ 条边(即 $B$ 序列的第 $i$ 个数),当前联通块个数为 $j$ (即 $B$ 序列第 $i$ 个数值为 $j$ )时的方案数

但是发现这还不够,因为可能在第 $i$ 条边时,不存在联通块个数为 $j$ 的情况

那么就会导致一个合法状态转移到一个废状态,然后再转移给合法状态,导致答案变大

而且又发现对于不同的情况,$i$ 和 $j$ 之间的限制是不一样的

所以考虑多设一维 $k$ 来保证对于一个状态 $i,j$, $k$ 的限制是一样的

设 $F[i][j][k]$ , $i,j$ 意义同上, $k$ 表示此时有 $k-1$ 条边用于往回连减少联通块数量($k-1$只是为了保证 $k$ 为正数,比较方便)

那么用来构造 '链' 的边有 $i-(k-1)$ 条,并且为了让这 $n$ 个变成 $j$ 个联通块,我们要用到链上的 $n-j$ 条边

所以有一个限制: $i-(k-1)>=n-j$

发现对于不同的联通块数量 $j$ ,边数也是有上限的

最多情况就是一个联通块有 $n-j+1$ 个点,剩下 $j-1$ 个联通块只有自己一个点,然后大的联通块每个点都往所有其他点连边,小的联通块就只能它们之间连边

那么最大边数为 $(n-j+1)*(n-1)\ +\ (j-2+j-3+j-4+j-5+...+1)\ =\ (n-j+1)*(n-1)+(j-1)*(j-2)/2$

然后考虑具体转移(以下均为合法状态)

当前边连废边:$F[i][j][k]+=F[i-1][j][k]$

当前边回连用来减少联通块个数: $F[i][j][k]+=\sum_{h=j+1}^{n}F[i-1][h][k-1]$

然后第二个转移显然前缀和优化,再把 $i$ 滚动掉

空间够了,但是时间还是不够,发现这个 $k$ 很麻烦,把之前的式子变一下 $i-(k-1)>=n-j\rightarrow i+1-n+j>=k $

发现当 $i>=2n$ 时,$k$ 总是合法的

所以 $F$ 只要处理到 $2n$ ,然后剩下的部分换一个数组 $G[i][j]$ 来维护,$G[i][j]$ 的意义和一开始设的 $F[i][j]$ 是一样的

然后同样可以前缀和优化

最后复杂度就是 $O(n^3)$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=407,mo=1e9+7;
inline int fk(int x) { return x>=mo ? x-mo : x; }
int n,lim[N],F[2][N][N],G[N][N];
int sumF[2][N][N],sumG[N][N],ans[N*N];
int main()
{
    n=read();
    for(int i=1;i<=n;i++) lim[i]=(n-(i-1))*(n-1)+(i-1)*(i-2)/2;
    F[1][n][1]=ans[1]=1; int Mx=min(n*(n-1),n<<1);
    for(int i=n;i>=1;i--) sumF[1][i][1]=1;
    for(int i=2;i<=Mx;i++)
    {
        int op=i&1;
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++) F[op][j][k]=0;
        for(int j=1;j<=n;j++) if(i<=lim[j])
            for(int k=1;k<=n;k++) if( i-(k-1)>=n-j )
                F[op][j][k]=fk(F[op^1][j][k] + sumF[op^1][j+1][k-1]);
        for(int j=n;j>=1;j--)
            for(int k=1;k<=n;k++)
            {
                sumF[op][j][k]=fk(sumF[op][j+1][k]+F[op][j][k]);
                ans[i]=fk(ans[i]+F[op][j][k]);
            }
    }
    for(int j=1;j<=n;j++)
        for(int k=1;k<=n;k++) G[0][j]=fk(G[0][j]+F[0][j][k]);
    for(int j=n;j>=1;j--) sumG[0][j]=fk(sumG[0][j+1]+G[0][j]);
    for(int i=Mx+1;i<=n*(n-1);i++)
    {
        int op=i&1;
        for(int j=1;j<=n;j++) G[op][j]=0;
        for(int j=1;j<=n;j++) if(i<=lim[j]) G[op][j]=fk(G[op^1][j]+sumG[op^1][j+1]);
        for(int j=n;j>=1;j--)
        {
            sumG[op][j]=fk(sumG[op][j+1]+G[op][j]);
            ans[i]=fk(ans[i]+G[op][j]);
        }
    }
    for(int i=1;i<=n*(n-1);i++) printf("%d ",ans[i]);
    return 0;
}

 

posted @ 2019-04-05 09:56  LLTYYC  阅读(229)  评论(0编辑  收藏  举报