HDU 6073 Matching In Multiplication (拓撲排序+搜索 求二分圖所有完美匹配價值和)

題目鏈接

2017多校第四場1007

題目大意

給定一個二分圖,集合U和V各有n個點(n 300000),集合U的每個點都連出兩條邊。保證至少有一個完美匹配。對於一個完美匹配,價值是邊權之積,要求所有完美匹配的價值和。

分析

拿到這道題一開始就先從二分圖匹配的算法去思考,但沒想出來OTL。。。
其實可以換個角度思考。
如果一個點的度數爲1的話,那麼它的匹配方案肯定是固定的,因此我們可以先通過拓撲排序去掉集合V中度數爲1的點,對V中度數爲1的點都在U中找一個點與之匹配,也將其刪除。那麼這些對答案ans的貢獻爲ans*common。 (common爲這些唯一確定邊權的乘積)
對於拓撲排序後剩下的點,由於集合U中點的度數都爲2,集合V中點的度數都≥2,所以集合V的每個點的度數肯定也都爲2。由於不存在奇點,所以必是歐拉回路。所以剩下的圖由若干個環組成。
而對於每一個環,它的完美匹配只有兩種情況,即都間隔取邊,設第一種情況的邊權乘積爲Xi ,第二種情況的邊權乘積爲Yi ,則由乘法分配率可知,這些環對答案的貢獻爲ans*cnti=1 (Xi +Yi ) 其中cnt爲環數。
綜上,ans =common * cnti=1 (Xi +Yi )

附官方題解:
這裏寫圖片描述

代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
typedef long long int LL;
const int MAXN=900010;
const int MAXM=1200010;
const int MOD=998244353;
struct Edge
{
    int to,next;
    LL w;
}e[MAXM];
int n,edgenum,head[MAXN],ind[MAXN],cnt,node[MAXN],loop[MAXN];
LL common;
bool vis[MAXN];

void Add_Edge(int u,int v,LL w)
{
    ind[v]++;
    e[++edgenum].to=v;
    e[edgenum].w=w;
    e[edgenum].next=head[u];
    head[u]=edgenum;
}

void TopoSort()///拓撲去掉所有能唯一確定的配對,找出剩下成環的結點
{
    queue<int> Q;
    for (int i=1;i<=2*n;i++)
        if (ind[i]==1)
            Q.push(i);
    while (!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        vis[u]=true;
        for (int t=head[u];t!=-1;t=e[t].next)
        {
            int v=e[t].to;
            if (!vis[v])
            {
                LL w=e[t].w;
                common*=w;
                common%=MOD;
                vis[v]=true;///
                for (int tt=head[v];tt!=-1;tt=e[tt].next)
                {
                    int k=e[tt].to;
                    ind[k]--;
                    if (!vis[k]&&ind[k]==1)
                        Q.push(k);
                }
                //break;
            }
        }
    }
    cnt=0;
    for (int i=1;i<=2*n;i++)
        if (!vis[i])
            node[++cnt]=i;///node[]記錄成環的結點
}

LL dist(int u,int v)///算u到v的距離
{
    for (int t=head[u];t!=-1;t=e[t].next)
        if (e[t].to==v)
            return e[t].w;
    return 0;
}
int next(int u)///找環上與u相鄰的一個點
{
    for (int t=head[u];t!=-1;t=e[t].next)
    {
        int v=e[t].to;
        if (!vis[v])
            return v;
    }
    return 0;
}

void Solve()
{
    LL ans=1;
    for (int i=1;i<=cnt;i++)
    {
        int u=node[i];///u爲某一個環的第一個結點
        if (!vis[u])
        {
            int len=0;
            vis[u]=true;
            loop[++len]=u;///loop[]記錄一個環
            for (int j=next(u);j;j=next(j))///DFS遍歷環
            {
                loop[++len]=j;
                vis[j]=true;
            }
            loop[len+1]=loop[1];
            LL x=1,y=1;
            /*間隔取邊的兩種方案*/
            for (int j=1;j<=len;j+=2)
                x=x*dist(loop[j],loop[j+1])%MOD;
            for (int j=2;j<=len;j+=2)
                y=y*dist(loop[j],loop[j+1])%MOD;
            ans=ans*(x+y)%MOD;
        }
    }
    printf("%I64d\n",ans*common%MOD);
}

int main()
{
    int T,v1,v2,i;
    LL w1,w2;
    scanf("%d",&T);
    while (T--)
    {
         scanf("%d",&n);
         edgenum=0;
         memset(head,-1,sizeof(head));
         memset(ind,0,sizeof(ind));
         for (i=1;i<=n;i++)
         {
             scanf("%d%lld%d%lld",&v1,&w1,&v2,&w2);
             Add_Edge(i,v1+n,w1);///注意二分圖的建圖
             Add_Edge(v1+n,i,w1);
             Add_Edge(i,v2+n,w2);
             Add_Edge(v2+n,i,w2);
         }
         common=1;
         memset(vis,false,sizeof(vis));
         TopoSort();
         Solve();
    }
    return 0;
}

總結:
1.這種用拓撲解決匹配問題的思想值得記錄
2.注意拓撲排序的BFS寫法
3.注意二分圖的建圖(V中結點的編號爲i+n)
4.注意long long型與格式對應符的對應(因爲這個不知道WA了多少次。。。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章