题意:左右两边各有n个点,左边每个点都向右边连出两条边。求图上所有完全匹配的乘积和。
想法:
首先,最简单的情况:左右两边所有点的度数为2,那么该图只会是一个或多个环,对于每一个环,只会有两个解,所以将环中的边间隔放入两个解中,这两个解相加后乘入最终答案。
想法:
首先,最简单的情况:左右两边所有点的度数为2,那么该图只会是一个或多个环,对于每一个环,只会有两个解,所以将环中的边间隔放入两个解中,这两个解相加后乘入最终答案。
对于有度数为1的点的情况:那么该点的匹配是唯一的,那么从度数为1的点开始,拓扑删除所有可能产生或已经有的度数为1的点的匹配边,之后转为上一种情况。
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<deque>
#include<vector>
#include<functional>
using namespace std;
#define LL long long
#define mm(a,b) memset(a,b,sizeof(a))
const double eps=1.0e-6;
const double PI=acos(-1.0);
template<typename T>T gcd(T a,T b){return b==0?a:gcd(b,a%b);}
template<typename T>T lcm(T a,T b){return a/gcd(a,b)*b;}
template<typename T>T _abs(T a){return a>0?a:-a;}
const int maxn=600010;
const LL mod=998244353;
struct abc
{
int to,next,w;
bool f;//标记是否删除
}e[maxn*4];
int cnt,head[maxn],du[maxn];
LL ji;
void add(int x,int y,int z)
{
du[x]++;
du[y]++;
e[cnt].f=0;
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
e[cnt].f=0;
e[cnt].to=x;
e[cnt].w=z;
e[cnt].next=head[y];
head[y]=cnt++;
}
void dfs(int id,int f)
{
for(int k=head[id];k!=-1;k=e[k].next)
if(!e[k].f)
{
int to=e[k].to;
if(f)//搜索时是从一边跳向另一边,在跳回来(“左->右->左”或反过来),只有从起始的那一边(左或右)出发的边才计入答案
ji=ji*e[k].w%mod;
e[k].f=e[k^1].f=1;
du[id]--;
du[to]--;
if(du[to]==1)
dfs(to,f^1);
}
}
LL ans[2];
void dfs2(int id,int now)
{
for(int k=head[id];k!=-1;k=e[k].next)
if(!e[k].f)
{
int to=e[k].to;
ans[now]=ans[now]*e[k].w%mod;
e[k].f=e[k^1].f=1;
du[id]--;
du[to]--;
dfs2(to,now^1);
}
}
int main()
{
int t,n,v1,w1,v2,w2;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
cnt=0;
ji=1;
ans[0]=ans[1]=1;
mm(head,-1);
mm(du,0);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&v1,&w1,&v2,&w2);
add(i,v1+n,w1);
add(i,v2+n,w2);
}
for(int i=1;i<=2*n;i++)
if(du[i]==1)
dfs(i,1);//删除度数为1的点的匹配边
for(int i=1;i<=2*n;i++)
if(du[i]>=2)//成环的情况
{
ans[0]=ans[1]=1;//对于每一个环,匹配情况只有两种
dfs2(i,0);
ji=ji*((ans[0]+ans[1])%mod)%mod;
}
printf("%lld\n",ji);
}
return 0;
}