2019.03.08【JSOI2018】【洛谷P4517】【BZOJ5315】防御网络(仙人掌)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/88363626

洛谷传送门

BZOJ传送门


解析:

求仙人掌上所有不同点集的斯坦纳树的边数之和。

显然这道题真正要求的不可能是斯坦纳树,如果真的对于 2 n 2^n 的每一个点集求一次 O ( 2 n n ) O(2^nn) 的斯坦纳树是不可能的。

但是这并不是一般图啊。。。这是仙人掌。。。

先不管它是不是仙人掌,一个稍有常识的OIER都知道,求所有集合的某权值之和,等价于求某权值在多少个合法集合中出现。

我们考虑求出每条边在多少个点集中出现过。

由于这个仙人掌还有点特殊,不是每条边最多在一个简单环中出现,而是每个点最多在一个简单环中出现,所以环边的讨论较为简单。

显然仙人掌图中只有两种边,割边和环边。

对于割边,显然只要在它两侧都有选择的点,它就要计入答案,可以直接在tarjan找环的时候确定下来。

对于环边,我们发现并不可能单独讨论,考虑将整个环拎出来处理。

处理一个环的时候,每个点的外向子图显然可以直接缩到这个点上。

而当环上选择了 k k 个点的时候,这 k k 个点将环切割成了 k k 份,显然我们只需要将最大的那份丢掉就能构造出斯坦纳树。

扫描二维码关注公众号,回复: 5581386 查看本文章

断环为链,同时记录下当前决策的左右端点,方便还原。

d p [ i ] [ j ] [ k ] dp[i][j][k] 表示在环上当前选择的最左端点是 i i ,最右端点是 j j ,不考虑最右端点绕回最左端点的情况,最大间隔为 k k 的方案数。

s i z [ j ] siz[j] 表示 j j 的外向子图的大小,则我们有转移:

d p [ i ] [ j ] [ k ] = ( 2 s i z [ j ] 1 ) ( l = 0 k d p [ i ] [ j k ] [ l ] + t = j k + 1 j 1 d p [ i ] [ t ] [ k ] ) dp[i][j][k]=(2^{siz[j]}-1)(\sum_{l=0}^kdp[i][j-k][l]+\sum_{t=j-k+1}^{j-1}dp[i][t][k])

其实就是考虑选择联通快 j j 中的点有没有更新当前的最大间隔就行了。

发现是个前缀和的形式,就可以做到 O ( n 3 ) O(n^3) 的DP了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<20|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int getint(){
		re char c;
		while(!isdigit(c=gc()));re int num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
}
using namespace IO;

cs int mod=1e9+7,inv2=mod+1>>1;
inline int add(cs int &a,cs int &b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(cs int &a,cs int &b){return a<b?a-b+mod:a-b;}
inline int mul(cs int &a,cs int &b){return (ll)a*b%mod;}
inline int quickpow(int a,int b,int res=1){
	while(b){
		if(b&1)res=mul(res,a);
		a=mul(a,a);
		b>>=1;
	}
	return res;
}

cs int N=203,M=605;
int n,m;
int last[N],nxt[M],to[M],w[M],ecnt=1;
inline void addedge(int u,int v){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}

int siz[N];
int dfn[N],low[N],dfs_clock;
bool cut[M],vis[N];
int loop[N];
int ans,p2[N];
inline void tarjan(int u,int fa){
	dfn[u]=low[u]=++dfs_clock;
	siz[u]=1;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]])
	if(v^fa){
		if(!dfn[v]){
			tarjan(v,u);
			low[u]=min(low[u],low[v]);
			siz[u]+=siz[v];
			if(low[v]>dfn[u]){
				cut[e]=cut[e^1]=true;
				w[e]=siz[v];w[e^1]=n-siz[v];
				ans=add(ans,mul(p2[siz[v]]-1,p2[n-siz[v]]-1));
			}
			else loop[u]=e;
		}
		else low[u]=min(low[u],dfn[v]);
		if(low[v]<dfn[u])loop[u]=e;
	}
}

int q[N],tot;
int val[N];
int sum1[N][N],sum2[N][N];
inline void solve(int cur){
	tot=0;
	for(int re p=cur;p&&!vis[p];p=to[loop[p]])q[++tot]=p,vis[p]=true;
	if(tot==1)return ;
	for(int re i=1;i<=tot;++i)val[i]=1;
	for(int re i=1;i<=tot;++i)for(int re e=last[q[i]];e;e=nxt[e])val[i]+=w[e];
	for(int re i=1;i<=tot;++i)val[i]=p2[val[i]]-1;
	for(int re i=1;i<tot;++i){
		for(int re j=0;j<=tot;++j)sum2[i][j]=val[i];
		for(int re j=i+1;j<=tot;++j)
		for(int re k=1;k<=tot-i;++k){
			int res=0;
			if(k<=j-i){
				res=mul(add(sum2[j-k][k],dec(sum1[j-1][k],sum1[j-k][k])),val[j]);
				ans=add(ans,mul(res,tot-max(tot-j+i,k)));
			}
			sum1[j][k]=add(sum1[j-1][k],res);
			sum2[j][k]=add(sum2[j][k-1],res);
		}
		for(int re j=i;j<=tot;++j)
		for(int re k=0;k<=tot-i;++k)sum1[j][k]=sum2[j][k]=0;
	}
}

signed main(){
	n=getint();m=getint();
	for(int re i=1;i<=m;++i)addedge(getint(),getint());
	for(int re i=p2[0]=1;i<=n;++i)p2[i]=add(p2[i-1],p2[i-1]);
	tarjan(1,0);
	for(int re i=1;i<=n;++i)if(!vis[i])solve(i);
	cout<<mul(ans,quickpow(inv2,n))<<"\n";
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/88363626
今日推荐