[LGP5241] 序列

分析

题目要求在一共加入若干条边是的不同B序列的个数;方便统计,我们不妨让一个B序列对应一种特定的操作来构造,具体如下

从1开始按从小到大加入点,始终维护一个顺序为加点次序的链;每次加入一条有向边
不会影响SCC个数的
1)连上新点:链末尾与新点连
2)连上废边:加上一条不改变强连通情况的边,不改变链
(两个决策每次必须只选一种,钦定为能加点加点,否则考虑连废边)
会影响的SCC个数
3)把链上与1强连通的部分(钦定为链的前缀)向后扩展若干
应注意到任意时刻SCC的数目为n-(j+1),n是图的点集大小,j是当前与1强连通的点集大小。

这样的构造与B序列是一一对应的,于是我们可以考虑计数dp,设f[i,j,k]为已经加入了i条边,与1强连通的链前缀长度为j+1,除此之外还有长度k的不同的B序列的个数。
转移和一些约束如下

f[0,0,0]=1
f[i,j,k]->f[i+1,j,k+1] j+1+k<n
f[i,j,k]->f[i+1,j,k] j+1+k==n且j*(j+1)+k(k-1)/2+(j+1)*k>i (注意是有向边哦)
f[i,j,k]->f[i+1,j+t,k-t] 0<t<=k 需要前缀和优化

实现

先留坑好了。 以下是暴力代码(80)

#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;

inline void add(int&x,int y) {
	if((x+=y)>=mod) x-=mod;
}

int n,now,nxt=1;
int f[2][401][401],t[401][401];

int main() {
	scanf("%d",&n);
	f[now][0][0]=1;
	for(int i=0; i<n*(n-1); ++i,now=nxt,nxt^=1) {
		for(int j=0; j<n; ++j) {
			for(int k=0; j+k<n; ++k) {
				if(!f[now][j][k]) continue;
				if(j+1+k<n) add(f[nxt][j][k+1],f[now][j][k]);
				else if(j*(j+1)+k*(k-1)/2+(j+1)*k>i) add(f[nxt][j][k],f[now][j][k]);
				if(k>0) add(t[j+1][k-1],f[now][j][k]);
				f[now][j][k]=0;
			}
		}
		for(int j=0; j<n; ++j) {
			for(int k=0; j+k<n; ++k) {
				add(f[nxt][j][k],t[j][k]);
				if(k>0) add(t[j+1][k-1],t[j][k]);
				t[j][k]=0;
			}
		}
		int ans=0;
		for(int j=0; j<n; ++j) {
			for(int k=0; j+k<n; ++k) {
				add(ans,f[nxt][j][k]);
			}
		}
		printf("%d ",ans);
	}
	return 0;
}

优化状态数目并吸氧的代码

// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;

const int N=400+5;
const int mod=1e9+7;

int n;
int now,nxt=1,f[2][N][N],t[N][N];
#define push(c,x,y) sta[c][++top[c]]=make_pair(x,y)
int mx,top[2],tmp[N];
pair<int,int> sta[2][20*N*N];
bool vis[N];

inline void add(int&x,int y) {
    if((x+=y)>=mod) x-=mod;
}
int main() {
    scanf("%d",&n);
    f[now][0][0]=1;
    push(now,0,0);
    for(int i=0; i<n*(n-1); ++i,now=nxt,nxt^=1) {
        top[nxt]=0;
        for(int p=1; p<=top[now]; ++p) {
            int j=sta[now][p].first;
            int k=sta[now][p].second;
//			printf("|%d,%d>,",j,k); 
            if(!f[now][j][k]) continue;
            if(j+1+k<n) {
                add(f[nxt][j][k+1],f[now][j][k]);
                push(nxt,j,k+1);
            } else if(j*(j+1)+k*(k-1)/2+(j+1)*k>i) {
                add(f[nxt][j][k],f[now][j][k]);
                push(nxt,j,k);
            }
            if(k>0) {
                add(t[j+1][k-1],f[now][j][k]);
                vis[j+k]=true;
            }
            f[now][j][k]=0;
        }
        int c=0,pre=top[nxt];
        for(int s=0; s<n; ++s) if(vis[s]) {
            vis[s]=false; tmp[++c]=s;
        } 
        for(int j=0; j<n; ++j) {
            for(int p=c; p; --p) {
                int k=tmp[p];
                if(k<0) break;
                add(f[nxt][j][k],t[j][k]);
                if(f[nxt][j][k]) push(nxt,j,k);
                if(k>0) add(t[j+1][k-1],t[j][k]);
                t[j][k]=0; tmp[p]--;
            }
        }
        merge(sta[nxt]+1,sta[nxt]+pre+1,sta[nxt]+pre+1,sta[nxt]+top[nxt]+1,sta[nxt]+1); //归并
        top[nxt]=unique(sta[nxt]+1,sta[nxt]+top[nxt]+1)-sta[nxt]-1;
        int ans=0;
        for(int p=1; p<=top[nxt]; ++p) {
            add(ans,f[nxt][sta[nxt][p].first][sta[nxt][p].second]); 
        }
        printf("%d ",ans);
    }
    return 0;
}


posted @ 2019-03-04 22:14  nosta  阅读(118)  评论(0编辑  收藏  举报