hdu6073二分图完美匹配之权值之积再求和

传送门
题意:给一个二分图,一个完美匹配的权值等于完美匹配的边的权值的乘积,求所有完美匹配权值的和,保证至少有一个完美匹配。
眼里解释:第一行为T,T组数据,然后一行为n,表示有二分图每个集合有n个点,
然后下面有n行,每行四个数,例如第i行的四个数,v1,w1,v2,w2,那么就表示 i 与v1相连,权值为w1,i与v2相连,权值为w2。即左边集合就是i,(1<=i<=n),右边集合为输入的v1,v2.这样一来,我们发现左边集合每个点都有两个出度,右边的点每个点的入度大于等于1。那么我们可以先枚举右边集合的点,如果右边集合的点入度为1的话,那么该点(记作v)必须和与之相连的左边集合的点(记作u)进行匹配,那么与u相连的点的入度都要减去一,因为u被用了,与之相连的点不要再挂念他了,所以与u相连的点的入度都要减去一,那么如果发现入度减一以后度数变成1了,好,对该点再进行刚才操作。所以就是一个拓扑的过程。这些边的权值乘起来,(设乘积为common)重复利用就好了。
那么剩余的点怎么搞呢,不难发现剩余的点的左边集合每个点的出度还都是2,我们去掉的那些左边集合点,每个点出度是2,与之对应右边集合的点也去掉了2的倍数个,设左边去掉了k个点,那么右边的点的入度去掉了2*k个度数,回过头来看左边集合每个点有两个出度,一共有2*n个出度,那么左边集合一定有2*n个入度啊,2*n-2*k,左边剩下的点的度数和还是偶数啊!设剩下m个点,左边m个点每个点出度为2,右边m个点度数和为2*m,每个点度数大于等于2,所以每个点度数为2,左右两边的集合每个点度数都为偶数,那么剩下的图肯定是一个欧拉回路,例图:
这里写图片描述
6 点入度 为1,那么去点3->6,以及与3连的其他所有边,即3->5,剩余的图是一个欧拉回路,1->4->2->5->1。那么这个回路可以恰好肯定形成两个完美匹配,
一:1->4, 2->5
二:1->5, 2->4
我们要的是完美匹配的边的权值之积,设两个完美匹配的权值之积为x,y;x,y初始为1,x=x*val(1->4)*val(2->5), y=y*val(1->5)*val(2->4)。
最终结果ans= common*x+common*y=common*(x+y);然后ans%MOD;

#include <iostream>
#include <queue>
#include <stdio.h>
#include <string.h>

using namespace std;
typedef long long LL;
const int MAXM=1200100;
const int MAXN=600100;
const LL MOD=998244353;
struct Edge
{
    int next,to;
    LL w;
} edge[MAXM];
int head[MAXN],tot;
int in[MAXN];
int vis[MAXN];
LL common;
int vex[MAXN],cnt;
int quan[MAXN];
void init()
{
    memset(head,-1,sizeof(head));
    memset(in,0,sizeof(in));
    memset(vis,0,sizeof(vis));
    cnt=0;
    tot=0;
    common=1;
}
void add(int u,int v,LL w)
{
    in[v]++;
    edge[tot].to=v;
    edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}

void solve1(int n)
{
    queue<int>que;
    for(int i=n/2; i<=n; i++)///枚举右边集合的点
    {
        if(in[i]==1)
            que.push(i);
    }
    int u,v,v2;
    while(!que.empty())
    {
        u=que.front();
        que.pop();
        vis[u]=1;
        for(int i=head[u]; i!=-1; i=edge[i].next)
        {
            v=edge[i].to;
            if(!vis[v])
            {
                vis[v]=1;
                common=(common*edge[i].w)%MOD;
                for(int j=head[v]; j!=-1; j=edge[j].next)
                {
                    v2=edge[j].to;
                    in[v2]--;
                    if(in[v2]==1&&!vis[v2])
                        que.push(v2);
                }
                break;
            }

        }
    }
    for(int i=1; i<=n; i++)
        if(!vis[i])
            vex[cnt++]=i;

}
int find_nx(int u)
{
    int v;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        v=edge[i].to;
        if(!vis[v])
            return v;
    }
    return 0;
}
LL getw(int u,int v)
{
    for(int i=head[u]; i!=-1; i=edge[i].next)
        if(edge[i].to==v)
            return edge[i].w;
    return 1LL;

}
void solve2()
{
    int u,v;
    int top;
    LL x,y;

     LL ans=1;
    for(int i=0; i<cnt; i++)
    {
        u=vex[i];
        if(vis[u]==0)
        {
            top=0;
            vis[u]=1;
            quan[++top]=u;
            for(v=find_nx(u); v ; v=find_nx(v))
            {
                quan[++top]=v;
                vis[v]=1;
            }
             x=y=1;
            quan[top+1]=quan[1];
            for(int j=1; j<=top; j+=2)
                x=(x*getw(quan[j],quan[j+1])%MOD)%MOD;
            for(int j=2; j<=top; j+=2)
                y=(y*getw(quan[j],quan[j+1])%MOD)%MOD;
           ans=ans*(x+y)%MOD;
        }
    }


    printf("%lld\n",common*ans%MOD);
}
int main()
{
    int n,T;
    int v1,v2;
    LL w1,w2;
    cin>>T;
    while(T--)
    {
        scanf("%d",&n);
        init();
        for(int u=1; u<=n; u++)
        {
            scanf("%d%lld%d%lld",&v1,&w1,&v2,&w2);
            add(u,v1+n,w1);
            add(v1+n,u,w1);
            add(u,v2+n,w2);
            add(v2+n,u,w2);
        }
        solve1(n*2);
        solve2();

    }
    return 0;
}

/**

3
2
2 1 1 4
1 4 2 3

*/
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值