P5241 序列(滚动数组+前缀和优化dp)

P5241 序列

挺神仙的一题

看看除了dp好像没什么其他办法了

想着怎么构个具体的图出来,然鹅不太现实。

于是我们想办法用几个参数来表示dp数组

加了几条边肯定要的吧,于是加个参数$i$表示已加了$i$条边

这显然是不够的。于是我们又想:强连通分量.....连通块.......

于是加个$j$表示还有$j$个强连通分量

于是dp数组为$f[i][j]$

这是我们发现一个问题,状态$f[i][j]$不一定是合法的。

那dp不就GG了吗

再次撕烤,我们发现每次加上的边无非就3种情况:

1.把2个强连通分量(或链)连成一条链

2.在某个强连通分量中瞎连(没啥用)

3.在1条链上的某点向回连,形成一个环,缩成一个新强连通分量(可以减少任意个强连通分量

我们设$k-1$条边(dp数组下标$k$为正数较好处理)投入到第3种情况

要生成剩下$j$个强连通的情况,我们最少投入$n-j$条边用于第1种情况

所以$n-j+(k-1)<=i$

我们又发现,要生成剩下$j$个强连通的情况,我们最多共投入的边数$i$是有限制的

最多情况就是1个块有$n-j+1$个点,剩下$j-1$个块只有1个点,蓝后大块每个点连$n-1$条边,小块互相之间弱连通

那么最大边数为$(n-j+1)*(n-1)+(j-2+j-3+j-4+...+1)=(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$条边,有$j$个强连通分量,$k-1$条边向回连的方案数

限制条件:

$n-j+(k-1)<=i$

$i<=(n−j+1)∗(n−1)+(j−1)∗(j−2)/2$

转移:

$f[i][j][k]+=f[i-1][j][k]$(第2种情况)

$f[i][j][k]+=\sum_{h=j+1}^{n}f[i-1][h][k-1]$

显然是可以滚动数组+前缀和优化的辣

然鹅复杂度还是太高,主要因为k很麻烦

仔细观察k,发现

$n-j+(k-1)<=i$

$k<=i+j-n+1$

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

于是我们就可以愉快地缩成2维辣

#include<iostream>
#include<cstdio>
#include<cstring>
#define rint register int
using namespace std;
inline int Min(int a,int b){return a<b?a:b;}
const int mod=1e9+7;
inline int Md(int x){return x<mod?x:x-mod;}
#define N 405
int n,f[2][N][N],sf[2][N][N],g[2][N],sg[N][N],lim[N],ans[N*N];
int main(){
    scanf("%d",&n); int tn=Min(n*(n-1),n<<1),w=0;
    for(rint j=1;j<=n;++j) lim[j]=(n-j+1)*(n-1)+(j-1)*(j-2)/2;
    f[1][n][1]=ans[1]=1;
    for(rint j=1;j<=n;++j) sf[1][n][1]=1;
    for(rint i=2;i<=tn;++i,w^=1){
        for(rint j=1;j<=n;++j)
            for(rint k=1;k<=n;++k)
                f[w][j][k]=0;
        for(rint j=1;j<=n;++j) if(lim[j]>=i)
            for(rint k=1;k<=n;++k) if(i-(k-1)>=n-j)
                f[w][j][k]=Md(f[w^1][j][k]+sf[w^1][j+1][k-1]);
        for(rint j=n;j;--j)
            for(rint k=1;k<=n;++k){
                sf[w][j][k]=Md(sf[w][j+1][k]+f[w][j][k]);
                ans[i]=Md(ans[i]+f[w][j][k]);
            }
    }w=1;
    for(rint j=1;j<=n;++j)
        for(rint k=1;k<=n;++k)
            g[0][j]=Md(g[0][j]+f[0][j][k]);
    for(rint j=n;j;--j) sg[0][j]=Md(sg[0][j+1]+g[0][j]);//降维
    for(rint i=tn+1;i<=n*(n-1);++i,w^=1){
        for(rint j=1;j<=n;++j) g[w][j]=0;
        for(rint j=1;j<=n;++j) if(lim[j]>=i)
            g[w][j]=Md(g[w^1][j]+sg[w^1][j+1]);
        for(rint j=n;j;--j){
            sg[w][j]=Md(sg[w][j+1]+g[w][j]);
            ans[i]=Md(ans[i]+g[w][j]);
        }
    }
    for(rint i=1;i<=n*(n-1);++i) printf("%d ",ans[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/kafuuchino/p/10661304.html

时间: 2024-07-30 10:27:37

P5241 序列(滚动数组+前缀和优化dp)的相关文章

Codeforces 712 D. Memory and Scores (DP+滚动数组+前缀和优化)

题目链接:http://codeforces.com/contest/712/problem/D A初始有一个分数a,B初始有一个分数b,有t轮比赛,每次比赛都可以取[-k, k]之间的数,问你最后A比B大的情况有多少种. dpA[i][j]表示第i轮获得j分的情况数.因为第i轮只和第i-1轮有关,所以这里i用滚动数组优化. 要是普通做法3个for就会超时,所以要用前缀和优化,dpA[i][j]可以由一段连续的dp[i - 1][x]转移过来,所以用sumA数组存取dp[i - 1][x]的前缀

HDU-5332(前缀和优化dp/CDQ+NTT)

HDU-5332(CDQ+NTT/前缀和优化dp) 考虑依次求出\(i\)个点的答案 假设当前有\(i-1\)个点,枚举第\(i\)个点前面的点数\(j\),则\(dp_i=dp_{i-j-1}\cdot (j+1)^2\cdot C(i-1,i-j-1)\cdot j!\) 直接转移是\(O(n^2)\)的,可以看到是一个\(dp\)转移与差值有关,所以可以用\(CDQ\)分治+\(NTT\)解决 关于这种简单粗暴的做法,模板题HDU-5730 题解 以下是暴力的代码 #include<bit

bzoj2431: [HAOI2009]逆序对数列(前缀和优化dp)

2431: [HAOI2009]逆序对数列 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 2312  Solved: 1330[Submit][Status][Discuss] Description 对于一个数列{ai},如果有i<j且ai>aj,那么我们称ai与aj为一对逆序对数.若对于任意一个由1~n自然数组成的 数列,可以很容易求出有多少个逆序对数.那么逆序对数为k的这样自然数数列到底有多少个? Input 第一行为两个整数n,k. Ou

BNU 13064 Dice (I) 前缀和优化DP

Dice (I) You have N dices; each of them has K faces numbered from 1 to K. Now you have arranged the N dices in a line. You can rotate/flip any dice if you want. How many ways you can set the top faces such that the summation of all the top faces equa

bzoj1044 [HAOI2008]木棍分割——前缀和优化DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1044 咳咳...终于A了... 居然没注意到正着找pos是n方会TLE...所以要倒着找pos: 二分还写错了,改了半天... 小心前缀和取模后相减变成负数!!!!!!!!! 代码如下: #include<iostream> #include<cstdio> #include<cstring> using namespace std; int const maxn

【最大M子段和】dp + 滚动数组

题目描述 给定 n 个数求这 n 个数划分成互不相交的 m 段的最大 m 子段和. 给出一段整数序列 A1,A2,A3,A4,...,Ax,...,An ,其中 1≤x≤n≤1,000,000, -32768≤Sx≤32767. 我们定义一种函数 sum(i,j)=Ai + ... + Aj (1≤i≤j≤n,且Ai-Aj 是连续的数). 现在,我们得到一个正整数 m(1≤x≤m≤30),你的工作是寻找 m 对 i 与 j.这 m 对 i 和 j 满足以下条件:  sum(i1,j1)+sum(

poj - 1159 - Palindrome(滚动数组dp)

题意:一个长为N的字符串( 3 <= N <= 5000),问最少插入多少个字符使其变成回文串. 题目链接:http://poj.org/problem?id=1159 -->>状态:dp[i][j]表示第i个字符到第j个字符组成的字符串变成回文串的最少插入次数. 状态转移方程: 若sz[i] == sz[j],则:dp[i][j] = dp[i + 1][j - 1]; 否则:dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1; 提交,5

【算法系列学习】DP和滚动数组 [kuangbin带你飞]专题十二 基础DP1 A - Max Sum Plus Plus

A - Max Sum Plus Plus 1 https://vjudge.net/contest/68966#problem/A 2 3 http://www.cnblogs.com/kuangbin/archive/2011/08/04/2127085.html 4 5 /* 6 状态dp[i][j]有前j个数,组成i组的和的最大值.决策: 7 第j个数,是在第包含在第i组里面,还是自己独立成组. 8 方程 dp[i][j]=Max(dp[i][j-1]+a[j] , max( dp[i-

HDU - 1024 Max Sum Plus Plus(dp+滚动数组优化)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1024 题意:给定n个数字,求其中m段的最大值(段与段之间不用连续,但是一段中要连续) 例如:2 5 1 -2 2 3 -1五个数字中选2个,选择1和2 3这两段. 题解:dp[i][j]从前j个数字中选择i段,然后根据第j个数字是否独立成一段,可以写出 状态转移方程:dp[i][j]=max(dp[i][j-1]+num[j],max(dp[i-1][k])+num[j]) 这里的max(dp[i-