BZOJ
顾名思义,如果不是暴力分比较多,的确是一道很毒瘤的题。。
题面中所谓的联通操作就是保证互斥的连边后,是一个连通图。我们从树的状态考虑起。不妨设f[x][1/0]表示子树x中x选/不选时合法的方案数。
容易得到转移方程 f[x][1]=∏f[son][0],f[x][0]=∏(f[son][0]+f[son][1]) f [ x ] [ 1 ] = ∏ f [ s o n ] [ 0 ] , f [ x ] [ 0 ] = ∏ ( f [ s o n ] [ 0 ] + f [ s o n ] [ 1 ] )
那么对于加入的非树边应该怎么办呢,我们可以去掉环上的一边,使得它变成树,将这个边的作用看做约束条件,即x,y不能同时选中。注意到此时有合法的三种状态即(1,0),(0,1),(0,0),那么我们对每条非树边枚举状态再DP算贡献,时间复杂度为 O(3m−n+1n) O ( 3 m − n + 1 n )
我们可以把状态合并成两个状态,改成枚举dfs序更小的点选不选,因为当它不选时,另一个点可以选也可以不选,合并起来转移即可做到 O(2m−n+1n) O ( 2 m − n + 1 n ) 。这个时候的暴力分已经很多了。
如果你大力推公式,你可以发现,除了非树边约束的两个点,其它的dp都可以预处理转移的系数。那么我们考虑建立一棵虚树,然后dfs暴力转移出系数,并把系数关系化成虚树上边的权值,然后就可以做了。时间复杂度是 s=m−n+1,O(n+s2s) s = m − n + 1 , O ( n + s 2 s ) 。
#include
#include
#include
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn=100100,mod=998244353;
inline int pls(int x,int y){return x+y>=mod?x+y-mod:x+y;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct data{int v,nxt;}edge[maxn<<1];
struct factor{
int x,y;
factor(){}
factor(int _x,int _y){x=_x;y=_y;}
factor operator + (const factor &t){return factor(pls(x,t.x),pls(y,t.y));}
factor operator * (const int t){return factor((ll)x*t%mod,(ll)y*t%mod);}
}k[maxn][2];
struct data2{int v,nxt;factor a,b;}ed[maxn];
int n,m,ep,edp,ans,dfc,cnt,head[maxn],h[maxn],dfn[maxn],mark[maxn],sz[maxn];
int vis[maxn],p[maxn][2],st[maxn][2],f[maxn][2];
pii e[20];
inline void insert(int u,int v){edge[++ep]=(data){v,head[u]};head[u]=ep;}
inline void insert(int u,int v,factor a,factor b)
{
ed[++edp]=(data2){v,h[u],a,b};h[u]=edp;
}
int dfs(int x,int pre)
{
dfn[x]=++dfc;
for(int i=head[x];i;i=edge[i].nxt)
if(edge[i].v^pre)
{
if(!dfn[edge[i].v]) sz[x]+=dfs(edge[i].v,x);//sz表示有几棵子树中有虚树节点
else
{
mark[x]=1;//mark标记虚树节点
if(dfn[edge[i].v]>dfn[x]) e[++cnt]=make_pair(x,edge[i].v);
}
}
mark[x]|=(sz[x]>1);//这说明x是虚树上的一个lca
return sz[x]||mark[x];
}
int build(int x)
{
int pos=0,w,v;
p[x][0]=p[x][1]=1;vis[x]=1;//p是自己的dp,k用于累加虚树边的系数
for(int i=head[x];i;i=edge[i].nxt)
if(!vis[edge[i].v])
{
v=edge[i].v;w=build(v);
if(!w)
{
p[x][0]=(ll)p[x][0]*pls(p[v][0],p[v][1])%mod;
p[x][1]=(ll)p[x][1]*p[v][0]%mod;
}
else if(mark[x]) insert(x,w,k[v][0]+k[v][1],k[v][0]);
else k[x][0]=k[v][0]+k[v][1],k[x][1]=k[v][0],pos=w;//此时只会有一个虚树节点,否则mark[x]=1
}
if(mark[x]) k[x][0]=factor(1,0),k[x][1]=factor(0,1),pos=x;
else k[x][0]=k[x][0]*p[x][0],k[x][1]=k[x][1]*p[x][1];
return pos;//子树中深度最小的虚树节点
}
void input()
{
int x,y;
read(n);read(m);
for(int i=1;i<=m;i++)
{
read(x);read(y);
insert(x,y);insert(y,x);
}
dfs(1,0);mark[1]=1;build(1);
}
void dp(int x)
{
int f0,f1;
f[x][0]=st[x][1]?0:p[x][0];f[x][1]=st[x][0]?0:p[x][1];
for(int i=h[x];i;i=ed[i].nxt)
{
dp(ed[i].v);f0=f[ed[i].v][0];f1=f[ed[i].v][1];
f[x][0]=(ll)f[x][0]*pls((ll)ed[i].a.x*f0%mod,(ll)ed[i].a.y*f1%mod)%mod;
f[x][1]=(ll)f[x][1]*pls((ll)ed[i].b.x*f0%mod,(ll)ed[i].b.y*f1%mod)%mod;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
input();
int lim=1<for(int i=0;ifor(int j=1;j<=cnt;j++)
{
if(i&(1<1)) st[e[j].first][1]=st[e[j].second][0]=1;
else st[e[j].first][0]=1;
}
dp(1);
ans=pls(ans,pls(f[1][0],f[1][1]));
for(int j=1;j<=cnt;j++)
{
if(i&(1<1)) st[e[j].first][1]=st[e[j].second][0]=0;
else st[e[j].first][0]=0;
}
}
printf("%d\n",ans);
return 0;
}