序列Counting

Posted popo-black-cat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了序列Counting相关的知识,希望对你有一定的参考价值。

  模拟的时候切掉的,感觉这道题还是很好的。(虽然T1期望啥也不会积分瞎搞拿了个二十五分,T2好不容易搞了个字符串dp,最后数组还开小了……不过终于狗进前五)

  题面描述:

  构建一个N个点的有向图G,初始没有任何边。接下来构建一个长度为E的边的序列A,序列中每条边都是满足1≤s,t≤N且s≠t的有向边(s,t),且序列中的边互不相同。按照顺序把这些边加入到G中,每次加入后计算当前图的强连通分量个数并记录下来,得到一个新的长度为E的正整数序列B。如果两个边的序列得到的B相同则称它们本质相同。

  请问有多少种本质不同的边的序列,你只要求出答案对10^9+7取模后的结果。

  很显然不能一遍一遍跑Tarjan(笑哭)

  我们先分析一下这道题:

  首先我们考虑一个合法的B序列,一定是单调不增的。我们将一条链作为图的主干考虑,对于我们加的每一条边,我们可以了考虑是继续拓展这条链,还是在原有的链的基础之上随便连边。那么常规的我们设计一个状态 f [ i ] [ j ] 表示前 i 条边,现在有 j 个强连通分量的方案数。

  那么很显然对于 i 和 j 是有一些关 系的,不是任何一对 i , j 都是合法的。

  那么我们考虑一波:

  假设现在序列B被分为 k 段,每段的大小相等(连了一些废边),那么作为在链的基础上加的边至少有 k-1 条,那么也就是说在链上的边最多有 i - k +1条。

  设Bi = j ,也就是说现在有 j 个强连通分量,那么显然用到链上的边最少为 n - j 条(一个环,j - 1 个孤立点,n - j + 1 个环上点,n - j + 1 条环上边,其中有一条反向的边)。

  那么显然我们要满足 n - j <= i - k + 1 ,多、也就是说对于一对 i,j 我们必须满足 i + j >= n + k -1 才行。

  这说明情况是否合法收到 k 的限制。也就是说对于一对 i , j ,其对应的情况并不都合法,i ,j 的合法情况是其所有情况的一个子集。

  那么我们就要加一维限制,f [ i ] [ j ] [ k ] 代表对于加的 i 条边,有 j 个强连通分量,分成了 k 段的方案数。

  然后我们又发现,由于这是简单图,所以当强连通分量个数为 j 时,边数是有上限的,最多为 ( n - j + 1 ) * ( n - 1 ) + ( j - 1 ) * ( j - 2 ) / 2 。

  所以也必须满足条件 i <= ( n - j + 1 ) * ( n - 1 ) + ( j - 1 ) * ( j - 2 ) / 2 。

  那么 f [ i ] [ j ] [ k ] 就很好转移了,只要考虑第 i 条边是否是废边即可 : f [ i ] [ j ] [ k ] += f [ i - 1 ] [ h ] [ k - 1 ] + f [ i - 1 ] [ j ] [ k ] 。  

  通过维护前缀和为我们可以把复杂度降到 n3,但是显然我们还是不满意的>///<

  我们观察 i + j >= n + k -1 ,发现在 i >= 2n 的时候总是成立的,所以我们就不需要 k 这一维了,f [ i ] [ j ] 就可以了,转移也一样。

  这样就大功告成啦!当然由于模拟的时候 n 是100,所以不需要后来的二维转移,三维n4就是可以接受的。

  代码:

  1.模拟代码:

#include <iostream>
#include <cstdio>
#define maxn 102
#define mod 1000000007
using namespace std;
int dp[maxn * maxn][maxn][maxn];
int add(int x, int y)

    x += y;
    return x >= mod ? x - mod : x;

int main()

    int n;
    cin >> n;
    for (int i = n; i >= 0; -- i)
        dp[0][i][0] = 1;
    for (int e = 1; e <= n * (n - 1); ++ e)
    
        int ans = 0;
        for (int v = 1; v <= n; ++ v)
            for (int m = 0; m < n; ++ m)
                if (e >= (n - v) + m && e <= (n - v + 1) * (n - 1) + (v - 1) * (v - 2) / 2)
                
                    dp[e][v][m] = add(dp[e][v][m], (mod + dp[e - 1][v][m] - dp[e - 1][v + 1][m]) % mod);
                    dp[e][v][m] = add(dp[e][v][m], dp[e - 1][v + 1][m - 1]);
                    ans = add(ans, dp[e][v][m]);
                
        for (int m = 0; m <= n; ++ m)
            for (int v = n - 1; v >= 0; -- v)
                dp[e][v][m] = add(dp[e][v][m], dp[e][v + 1][m]);
        printf("%d ",ans);
    
    return 0;

  2. n <= 400 二维优化代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;
#define mod 1000000007
const int maxn = 500;
int lim[maxn], f[2][maxn][maxn], sf[2][maxn][maxn], g[2][maxn], sg[2][maxn],ans[maxn*maxn];
int n;
int main()

    scanf("%d",&n);
    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;
    for (int i = 1; i <= n; i++)
        sf[1][i][1] = 1;
    for (int i = 2; i <= min(n * (n - 1), n << 1); 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 + j >= n + k - 1)
                        f[op][j][k] = (f[op ^ 1][j][k] + sf[op ^ 1][j + 1][k - 1]) % mod;
        for (int j = n; j >= 1; j--)
            for (int k = 1; k <= n; k++)
            
                sf[op][j][k] = (sf[op][j + 1][k] + f[op][j][k]) % mod;
                ans[i] = (ans[i] + f[op][j][k]) % mod;
            
    
    for (int j = 1; j <= n; j++)
        for (int k = 1; k <= n; k++)
            g[0][j] = (g[0][j] + f[0][j][k]) % mod;
    for (int j = n; j >= 1; j--)
        sg[0][j] = (sg[0][j + 1] + g[0][j]) % mod;
    for (int i = (n << 1) + 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] = sg[op ^ 1][j];
        for (int j = n; j >= 1; j--)
        
            sg[op][j] = (sg[op][j + 1] + g[op][j]) % mod;
            ans[i] = (ans[i] + g[op][j]) % mod;
        
    
    for (int i = 1; i <= n * (n - 1); i++)
        printf("%d ", ans[i]);
    printf("\n");
    return 0;

   注:感谢 yfl 和 lyn 以及 wty 大佬们的悉心讲解,第一题部分分积分算法已经明白:

  对于一条链的情况,len(p)=sigama(i=1,边的个数)* sigama P * ( xi ) = sigama(i=1,边的个数)* 1/n * ( 0 + 1 ) * n / 2= ( n - 1 ) / 2 。

  

以上是关于序列Counting的主要内容,如果未能解决你的问题,请参考以下文章

数字信号处理序列表示与运算 ( 序列乘以常数 | 序列相加 | 序列移位 | 序列尺度变换 )

什么是序列化? 如何实现(反)序列化 序列化的应用

什么是序列化? 如何实现(反)序列化 序列化的应用

什么是序列化? 如何实现(反)序列化 序列化的应用

什么是序列化? 如何实现(反)序列化 序列化的应用

SPSS时间序列 应用时间序列模型