洛谷省选计划后期 2021 仙人掌 题解

题目传送门 然而大部分人基本不能看……

题目大意: 好像也不方便写……

题解

据说正解好像是 n 2 n^2 n2 的,但是勉强把我这个 n 3 n^3 n3 的给放过去了qwq。

考虑统计以每个节点为好链的最后一个节点的好链数量。先考虑树的情况,令 f i f_i fi 表示以 i i i 为最后一个节点的好链数量,那么转移的时候暴力枚举子树内的点,假如 j < i j<i j<i 那么令 f i f_i fi 加上 f j f_j fj,最后 f i f_i fi 加一 也就是新建一条好链。

然后考虑仙人掌,先建出圆方树便于处理环,那么圆点就不需要考虑转移了,考虑他所有儿子(儿子一定是方点,根据圆方树的性质)的转移即可。

对于一个方点,考虑这个方点对应的环,将这个环提取出来,将他的父亲节点放在最后一位。令 d i d_i di 表示环上顺时针看第 i i i 个点,对于 i , j i,j i,j,枚举 i i i 的子树内的节点 k k k,若 k k k j j j 能产生贡献,那么令 g 1 ( j ) g_1(j) g1(j) 加上 f k f_k fk,假如 k k k 恰好是 i i i,那么 g 1 ( j ) g_1(j) g1(j) 还要加上 g 1 ( k ) g_1(k) g1(k)

逆时针也是这样贡献一次,但是要注意,方点的父亲依然要放在最后,所以实际上就是将 d d d 的除了最后一位的前面的位reverse一下,然后同样递推出一个 g 2 ( i ) g_2(i) g2(i)

这样子我们就不需要担心环上的一个点走重复的边来回贡献这种极其离谱的情况,比如 i , j , k i,j,k i,j,k 在环上的位置顺时针就是 i , j , k i,j,k i,j,k,但是满足 i < k < j i<k<j i<k<j,如果不钦定一个顺序贡献的话,就会出现 i i i 贡献 k k k k k k 贡献 j j j,这样 j , k j,k j,k 之间的边就反复走了,是不符合简单路径这个条件的。

最后令 f d i + = g 1 ( d i ) + g 2 ( d i ) f_{d_i}+=g_1(d_i)+g_2(d_i) fdi+=g1(di)+g2(di) 即可,但是注意,对于方点的父亲 f a fa fa ∀ i , d i < f a \forall i,d_i<fa i,di<fa f d i f_{d_i} fdi 是对 g 1 ( f a ) , g 2 ( f a ) g_1(fa),g_2(fa) g1(fa),g2(fa) 分别贡献了一次的,所以 f f a f_{fa} ffa 需要减去一个 f d i f_{d_i} fdi。不难发现这种问题对于其他 d i d_i di 不会出现,因为一对 d i , d j d_i,d_j di,dj 只可能在一个方向产生一次贡献。

然后一个点 i i i 至多对一个点 j j j 产生 1 1 1 次贡献,特别的,在环上可能产生两次(即上面提到的重复贡献),所以单次dp复杂度是 n 2 n^2 n2 的,总时间复杂度就是 O ( n 3 ) O(n^3) O(n3)

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define maxn 1010
#define mod 998244353
#define pb push_back

int n,m,n_;
struct edge{int y,next;}E[10010];
int first[maxn],len=1;
void buildroad(int x,int y){E[++len]=(edge){y,first[x]};first[x]=len;}
int dfn[maxn],low[maxn],id=0;
int sta[maxn],t=0;
vector<int> e[maxn];
void dfs(int x,int from){
	dfn[x]=low[x]=++id;sta[++t]=x;
	for(int i=first[x];i;i=E[i].next){
		int y=E[i].y;if(i==from)continue;
		if(!dfn[y]){
			dfs(y,i^1);
			low[x]=min(low[x],low[y]);
			if(low[y]>=dfn[x]){
				int xx;n_++;do{
					xx=sta[t--];
					e[xx].pb(n_);e[n_].pb(xx);
				}while(xx!=y);
				e[x].pb(n_);e[n_].pb(x);
			}
		}else low[x]=min(low[x],dfn[y]);
	}
}
void reduce(int &x){x+=x>>31&mod;}
int add(int x){return x>=mod?x-mod:x;}
int f[maxn],g1[maxn],g2[maxn],ans=0;
void go(int x,int fa,vector<int> &c){
	if(x<=n)c.pb(x);
	for(int y:e[x])if(y!=fa)go(y,x,c);
}
vector<int> c[maxn];
void solve(int x,int fa){
	f[x]=1;
	for(int y:e[x])if(y!=fa)solve(y,x);
	if(x>n){
		vector<int> d;
		for(int i=0;i<e[x].size();i++)if(e[x][i]==fa){
			for(int j=(i+1)%(int)(e[x].size());j!=i;j=(j+1)%(int)(e[x].size()))
				d.push_back(e[x][j]),g1[e[x][j]]=g2[e[x][j]]=0;
			break;
		}
		d.push_back(fa);g1[fa]=g2[fa]=0;
		for(int i=0;i<d.size()-1;i++)
			c[i].clear(),go(d[i],x,c[i]);
		for(int i=0;i<d.size();i++)
			for(int j=i+1;j<d.size();j++)
				for(int k:c[i])if(k<d[j]){
					reduce(g1[d[j]]+=f[k]-mod);
					if(k==d[i])reduce(g1[d[j]]+=g1[k]-mod);
				}
		reverse(d.begin(),--d.end());
		reverse(c,c+d.size()-1);
		for(int i=0;i<d.size();i++)
			for(int j=i+1;j<d.size();j++)
				for(int k:c[i])if(k<d[j]){
					reduce(g2[d[j]]+=f[k]-mod);
					if(k==d[i])reduce(g2[d[j]]+=g2[k]-mod);
				}
		for(int i=0;i<d.size()-1;i++)
			for(int j:c[i])if(j<fa)reduce(f[fa]-=f[j]);
		for(int i=0;i<d.size();i++)reduce(f[d[i]]+=add(g1[d[i]]+g2[d[i]])-mod);
	}
}

int main()
{
	scanf("%d %d",&n,&m);n_=n;
	for(int i=1,x,y;i<=m;i++)
		scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
	dfs(1,0);
	for(int i=1;i<=n;i++){
		memset(f,0,sizeof(f));
		solve(i,0);reduce(ans+=f[i]-mod);
	}
	printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值