码迷,mamicode.com
首页 > 其他好文 > 详细

序列Counting

时间:2019-07-02 21:29:36      阅读:129      评论:0      收藏:0      [点我收藏+]

标签:简单   ott   printf   tarjan   顺序   ups   namespace   不同   tor   

  模拟的时候切掉的,感觉这道题还是很好的。(虽然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

标签:简单   ott   printf   tarjan   顺序   ups   namespace   不同   tor   

原文地址:https://www.cnblogs.com/popo-black-cat/p/11122798.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!