2017 Multi-University Training Contest

来源:互联网 发布:淘宝小吃排行榜 编辑:程序博客网 时间:2024/06/03 15:51


Matching In Multiplication

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1551    Accepted Submission(s): 470


Problem Description
In the mathematical discipline of graph theory, a bipartite graph is a graph whose vertices can be divided into two disjoint setsU and V (that is, U and V are each independent sets) such that every edge connects a vertex in U to one in V. Vertex sets U and V are usually called the parts of the graph. Equivalently, a bipartite graph is a graph that does not contain any odd-length cycles. A matching in a graph is a set of edges without common vertices. A perfect matching is a matching that each vertice is covered by an edge in the set.



Little Q misunderstands the definition of bipartite graph, he thinks the size ofU is equal to the size of V, and for each vertex p in U, there are exactly two edges from p. Based on such weighted graph, he defines the weight of a perfect matching as the product of all the edges' weight, and the weight of a graph is the sum of all the perfect matchings' weight.

Please write a program to compute the weight of a weighted ''bipartite graph'' made by Little Q.
 

Input
The first line of the input contains an integer T(1T15), denoting the number of test cases.

In each test case, there is an integer
n(1n300000) in the first line, denoting the size of U. The vertex in U and V are labeled by 1,2,...,n.

For the next
n lines, each line contains 4 integers vi,1,wi,1,vi,2,wi,2(1vi,jn,1wi,j109), denoting there is an edge between Ui and Vvi,1, weighted wi,1, and there is another edge between Ui and Vvi,2, weighted wi,2.

It is guaranteed that each graph has at least one perfect matchings, and there are at most one edge between every pair of vertex.
 

Output
For each test case, print a single line containing an integer, denoting the weight of the given graph. Since the answer may be very large, please print the answer modulo998244353.
 

Sample Input
122 1 1 41 4 2 3
 

Sample Output
16
 

题目意思:
给出n个点,就像题目插入一样,位于左侧,然后在给出其与右侧n个点之间的关系,左侧的n个点,每个点的
度数都为2,而右侧的n个点的度数不确定。让计算所有完美匹配的乘积的和值。所谓完美匹配就是每个点都找
到伴侣了。
这个题目就是一个婚姻匹配的问题。
左边有n个男孩子,右边有n个女孩子,每个男孩子都有两个中意的女孩子(两个就够了,男人不能太花心),
而右侧的每个女孩子都有大于1人的追求这,这些边连接男孩子和女孩子,图中不存在同侧节点与同侧节点
相连(这样就是同性恋了,没有同性恋这种情况)。然后在图中找完美匹配,一个男孩子配一个女孩,最后
每个人都找到了伴侣(这样多好啊,大家都不用当单身狗了)。最总计算完美匹配的乘积和。
题目中的册数数据就是下面图中的关系。
图中的绿色线的组合,和红色线的组合,都是完美匹配的组合,对于绿色组合,计算得3*4=12
对于红色组合计算得:1*4 = 4. 最后答案ans = 12+4 = 16.
我们把左侧节点称为男孩子,右侧的节点称为女孩子。
因此我们有如下思路,首先如果右侧节点编号也是1~n得话,会比较乱,所以我们可以用n+1~2*n
给右侧的节点编号,因为每个男孩子都追求两个女孩子,但是女孩子的追求者个数确是不确定的,但是
题目上说了,保证至少有一个,所以如果有些女孩子只有一个追求者的话,那么他们对应的追求者
就必须与他们匹配(这是必须的,否则这些女孩子就不能找到伴侣了)。所以对于右侧度为1的节点。
我们找到与他匹配的男生后,则他们凑成了一对伴侣,因此他们需要断绝和其他人的关系(既然已成
情侣,大家都要对对方忠诚嘛,不能脚踩好几只船)所以就要删除所有与男方相连的边。由于删除了
这些边,所以有可能其他女孩子也变成了只有一个追求者,那么她们也只能与她唯一的追求者匹配。
同样的删除与男方有关系的边。这个过程我们可以用拓扑排序实现,一次拓扑偏序把所有能确定关系
的组合全都计算后,并删除已经找到伴侣的男方的关系。那么剩余的情况就像测试数据这样的图。
每个女生的追求者都是大于等于2的。这样的话我们可以用dfs来寻找匹配关系,寻找的过程遵循交叉寻找的
原则。即如图所示,从1出发选择绿色边到右边的1,再从右边的1出发可以找到左边的2这条红色的边,但是
这个红色的边必然是属于另一种匹配的,然而到左边的2后可以顺着绿色的关系找到右边的2,这个
边也属于当前匹配,然后又会从右边的2顺着红色的边找到左边的1,但是这个红色边是属于另一个匹配
的,所以再搜索过程中,我们交叉着计算就行了。

数组尽量开大点,否则会RE。

AC代码:
#include <iostream>#include <stdio.h>#include <string.h>#include <queue>using namespace std;const int maxn = 1e7;const int mod = 998244353;typedef long long LL;int n,cnt;int vis[maxn];    ///标记某个节点有没有找到伴侣,没找到为0,找到了为1int head[maxn];   ///链表头节点int in[maxn];     ///节点的入度LL ans;LL match[2];      ///最后剩余环状的关系时,用到这两个数组struct Edge{    int to;       ///from->to有一条边,存储末端节点    LL weight;    ///from->to的权重    int next;     ///指向吓一跳以from为起始点的边    bool mark;    ///标记这条边有没有被删除,没有为true,被删除了为false}edge[maxn];///添加边的函数,采用头插法建邻接链表void addEdge(int from,int to,int weight){    ///存from->to这条边    edge[cnt].to = to;    edge[cnt].weight = (LL)weight;    edge[cnt].next = head[from];    edge[cnt].mark = true;    in[to]++;    head[from] = cnt++;    ///存to->from这条边    edge[cnt].to = from;    edge[cnt].weight = (LL)weight;    edge[cnt].next = head[to];    edge[cnt].mark = true;    in[from]++;    head[to] = cnt++;}///拓扑排序void topSort(){    memset(vis,0,sizeof(vis));    queue<int>qu;    for(int i = n+1; i <= 2*n; i++)    {        if(in[i]==1)  ///节点度为1,这些女孩子的伴侣是固定的。            qu.push(i);    }    while(!qu.empty())    {        int female = qu.front();        qu.pop();        vis[female] = 1;        for(int i = head[female]; i != -1; i = edge[i].next)        {            if(edge[i].mark)  ///这条边没有删除            {                edge[i].mark = edge[i^1].mark = false;  ///删除这条边                /**i^1这里异或操作好强大,我们存u-v这条边的时候,                u->v存放再下标为偶数的位置,v->u存放再下标为奇数的位置                当要删除u-v这个边的时候,如果i为偶数,i与1异或就等于i+1,                这样直接把u->v,v->u都删除了,如果i为奇数,i与1异或就等于i-1                这样也是直接把u->v,v->u都删除掉。感觉超牛逼**/                ans = (ans*edge[i].weight)%mod;                int male = edge[i].to;                vis[male] = 1;  ///标记男性找到伴侣                ///下面将所有与该找到伴侣的男性有关系的边都删除掉。                for(int j = head[male]; j != -1; j = edge[j].next)                {                    if(edge[j].mark)                    {                        edge[j].mark = edge[j^1].mark = false;  ///删除去关系                        int woman = edge[j].to;                        in[woman]--;                        if(in[woman]==1)                        {                            qu.push(woman);                        }                    }                }            }        }    }}void dfs(int u,int k){    vis[u] = 1;    for(int i = head[u]; i != -1; i=edge[i].next)    {        if(edge[i].mark)  ///删除边        {            edge[i].mark = edge[i^1].mark = false;            int v = edge[i].to;            match[k] = (match[k]*edge[i].weight)%mod;            if(k==0)                   dfs(v,1);            else                dfs(v,0);        }    }}void calAns(){    ans = 1;    topSort();    for(int i = 1; i <= n; i++)    {        if(vis[i]) continue;  ///节点i已经找到伴侣了        match[0] = match[1] = 1;        dfs(i,0);        ans = ans*((match[0]+match[1])%mod)%mod;    }}int main(){    int t;    scanf("%d",&t);    while(t--)    {        int v1,w1,v2,w2;        scanf("%d",&n);        memset(in,0,sizeof(in));        memset(head,-1,sizeof(head));        cnt = 0;        for(int i = 1; i <= n; i++)        {            scanf("%d%d%d%d",&v1,&w1,&v2,&w2);            addEdge(i,v1+n,w1);            addEdge(i,v2+n,w2);        }        calAns();        printf("%lld\n",ans);    }    return 0;}