HDU-6403:Card Game(基环树+DP)

版权声明:http://blog.csdn.net/Mitsuha_。 https://blog.csdn.net/Mitsuha_/article/details/81907377

Card Game
Time Limit: 3000/3000 MS (Java/Others)
Memory Limit: 131072/131072 K (Java/Others)

Problem Description
Alice and Bob are playing a card game. In this card game, a number is written on each face of the playing card. The rule of the game is described as follows:

  • Alice arranges the cards in a row, and for each of the cards, she chooses one of its faces to place it up;
  • Bob turns over minimum number of cards to make all numbers on the front faces unique.

They play the game some times, and Bob always succeeds making the numbers unique. However, both of them are not sure whether the number of cards flipped is minimum. Moreover, they want to figure out the number of different ways of turning over minimum number of cards to make the numbers unique. Two ways are considered equal if and only if the sets of flipped cards are equal. Please write a program to help them!

Input
The first line of the input is a single integer T ( 1 T 50 ) , the number of test cases.

Each test case begins with a single line of integer n ( 1 n 10 5 ) , the number of cards on the table. Then follow n lines, specifying the cards that Alice arranges. Each of these n lines contains two integers x , y ( 1 x , y 2 n ) , meaning that Alice places a card with number x on the front face and number y on the back face.

It is guaranteed that the sum of n over all test cases does not exceed 10 6 .

Output
For each test case, display two integers in a line, the minimum number of cards to turn over, and the number of different ways of flipping modulo 998244353, respectively. If it is impossible to turn over cards to make all numbers unique, display -1 -1 instead.

Sample Input
3
4
1 2
1 3
4 5
4 6
2
1 1
1 1
3
1 2
3 4
5 6

Sample Output
2 4
-1 -1
0 1
题解:首先建图:每个数字为一个节点,每张卡片反面数字向正面数字连一条有向边。问题转化为:至少要反转多少条边的方向,才能使得每个点的入度不会超过 1。

易知,当底图是树或基环树时,才可能有解。
对于基环树,先把环找出来,然后把它按照树来计算答案,即dfs的时候不经过那条能形成环的边,遍历完了以后,再考虑加这条边形成环的影响。

对于树,可以正反两次 dfs处理出每个点作为根时所需要的反向次数,并统计出最小值以及方案数。最后将答案合并即可

处理形成环的边时,要注意自环的情况和重边的情况。
这种时候只记录形成环的边的端点是不好处理的。

#include<bits/stdc++.h>
using namespace std;
const int MAX=2e5+10;
const int MOD=998244353;
const double PI=acos(-1.0);
typedef long long ll;
vector<pair<int,int> >e[MAX];
int v[MAX];
int siz,edg;
void check(int k)               //判断这个联通块是否是基环树或树
{
    v[k]=1;
    siz++;
    for(int i=0;i<e[k].size();i++)
    {
        edg++;
        if(v[e[k][i].first]==0)check(e[k][i].first);
    }
}
int d[MAX],st,en,dir;
void dfs(int k,int fa)          //首先求出以某点为根时需要反向的dp值,并判断是否有环
{
    v[k]=1;
    d[k]=0;
    for(int i=0;i<e[k].size();i++)
    {
        pair<int,int>nex=e[k][i];
        if(v[nex.first]==0)
        {
            dfs(nex.first,k);
            d[k]+=d[nex.first]+((nex.second%2)^1);
        }
        else if(nex.first!=fa)  //发现有环,记录下形成环的这条边
        {
            st=k;
            en=nex.first;
            dir=nex.second;
        }
    }
}
vector<int>p;
int dp[MAX];
void cal(int k,pair<int,int>pre)//再次遍历求出任意点为根时的dp值
{
    p.push_back(dp[k]);
    for(int i=0;i<e[k].size();i++)
    {
        pair<int,int>nex=e[k][i];
        if(nex==pre)continue;           //碰到父亲节点
        if(nex.second==dir||nex.second==(dir^1))continue;//碰到形成环的边
        dp[nex.first]=dp[k]+(nex.second%2==0?-1:1);
        cal(nex.first,{k,nex.second^1});
    }
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {

        int n;
        scanf("%d",&n);
        for(int i=1;i<=2*n;i++)e[i].clear();
        for(int i=1;i<=n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            e[y].push_back({x,2*i-1});//给每条边编号,容易记录形成环的是哪条边
            e[x].push_back({y,2*i-2});
        }
        memset(v,0,sizeof v);
        int tag=1;
        for(int i=1;i<=2*n;i++)//判断是否有无解的情况
        {
            if(v[i])continue;
            siz=0;
            edg=0;
            check(i);
            if(edg/2>siz){tag=0;break;}
        }
        if(tag==0){puts("-1 -1");continue;}
        memset(v,0,sizeof v);
        int ans=0,tot=1;
        for(int i=1;i<=2*n;i++)
        {
            if(v[i])continue;
            st=en=dir=-1;
            dfs(i,0);       //以i为根节点遍历
            dp[i]=d[i];
            p.clear();
            cal(i,{0,0});   //从i节点开始遍历出以任意点为根的dp值
            int cnt=0;
            if(st==-1)      //无环时
            {
                sort(p.begin(),p.end());                     //存储以每个点为根时的dp值,找到最小值
                for(int j=0;j<p.size()&&p[j]==p[0];j++)cnt++;//记录方案数
                ans+=p[0];
            }
            else            //有环时处理形成环的那条边的影响
            {
                dir%=2;
                if(dp[st]+dir==dp[en]+(dir^1))cnt=2;
                else cnt=1;
                ans+=min(dp[st]+dir,dp[en]+(dir^1));
            }
            tot=1ll*tot*cnt%MOD;
        }
        printf("%d %d\n",ans,tot);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Mitsuha_/article/details/81907377