一、题意
给出N个卡牌,卡牌的正反两面具有两个数字,取值范围为[1,2*n],给出若干个默认正面向上的卡牌,求最小反转多少张卡牌可以使得,每张卡牌朝上的面上都有一个不同的数字,同时满足最小反转次数的反转方法有多少个?
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!
二、题解
考虑每张卡牌都有两个数字,要求必须选一个数字,则实际上可以考虑将卡牌表示成两个有向边——具有不同权重的边,权重表示反转的代价。具体来说,如果建立了边之后,对于每个联通快,如果有n个节点,且具有n-1或n个边,则可以给每条边分配一个节点——物理意义可以表示为给每张卡牌分配一个数字。对于其他情况则直接是不合法的。
考虑,n节点,n条边实际上是基环树,则分配时,可选的范围只有环的旋转方向(2个情况)。对于树结构,则可以自由地选择放弃使用哪个数字(n个情况)。
考虑暴力计算树DP统计以某个点为根的子树的权值和,实际上是一个O(N)的算法,则,直接求解复杂度为O(N2),不可接受。
考虑子树的定义,如果求出了父节点的DP值,且当前的DP值不包含父节点,则可以发现一个简单的状态转移方程:DP[NOW] = DP[FATHER] - EDGE(FATHER,NOW) - DP[NOW] + DP[CHILD] + EDGE[NOW,CHILD];这样做两遍遍历即可求出来我们要求的,以给定节点为根节点的权重值和,复杂度O(N)。
#include<bits/stdc++.h> using namespace std; #define ll long long const int MAXN = 300233; const int MOD = 998244353; #define pp pair<int,int> #define veci vector<int> #define vecp vector<pp> int fa[MAXN],vis[MAXN],dp[MAXN]; vecp G[MAXN]; int n; ll ans_cntt,ans_step,minn_cntt,minn_step; int cnt_e,cnt_v; void init_dfs(int now) { vis[now] = 1; int len = G[now].size(); for(int i=0;i<len;++i) { int tar = G[now][i].first; if(!vis[tar])init_dfs(tar); } cnt_v ++; cnt_e += len; } void tree_dp(int now,int father){ fa[now] = father; int len = G[now].size(); dp[now] = 0; for(int i=0;i<len;++i){ int tar = G[now][i].first; int val = G[now][i].second; if(father == tar)continue; tree_dp(tar,now); dp[now] += val + dp[tar]; } } void dp_dfs(int now){ if(fa[now] == 0){ if(minn_step > dp[now]){ minn_step = dp[now]; minn_cntt =1; }else if(minn_step == dp[now])minn_cntt ++; int len = G[now].size(); for(int i=0;i<len;++i){ int tar = G[now][i].first; dp_dfs(tar); } }else{ dp[now] = -dp[now]; int len = G[now].size(); for(int i=0;i<len;++i){ int tar =G[now][i].first; int val = G[now][i].second; dp[now] += dp[tar] + val; if(tar == fa[now])dp[now] -= !val; } if(minn_step > dp[now]){ minn_step = dp[now]; minn_cntt =1; }else if(minn_step == dp[now])minn_cntt ++; for(int i=0;i<len;++i){ int tar =G[now][i].first; if(tar == fa[now])continue; dp_dfs(tar); } } } veci cir_edge; pp cir_vector; int cir_val; bool find_circle(int now,int last){ vis[now] = 2; int len = G[now].size(); int cnt_last = 0; for(int i=0;i<len;++i){ int tar = G[now][i].first; int val = G[now][i].second; if(tar == last && !cnt_last){ cnt_last++; continue; } if(vis[tar] == 2){ cir_vector = make_pair(now,tar); cir_val = !val; return true; } if(find_circle(tar,now))return true; }return false; } bool deal_circle(int now,int last,int target,int last_val){ int len =G[now].size(); int cnt_last = 0; int next = 0; for(int i=0;i<len;++i){ int tar =G[now][i].first; int val = G[now][i].second; if(tar == last && cnt_last == 0 && val == !last_val){ cnt_last++; continue; } if(tar == target){ cir_edge.push_back(val); next = tar; break; } if(deal_circle(tar,now,target,val)){ cir_edge.push_back(val); next = tar; break; } } if(next == 0)return false; for(int i=0;i<len;++i){ int tar = G[now][i].first; int val = G[now][i].second; if(tar == last || tar == next ||tar == now)continue; tree_dp(tar,now); minn_step += dp[tar] + val; } return true; } void init(){ memset(vis,0,sizeof(vis)); for(int i=0;i<2*n+23;++i)G[i].clear(); int succ = 1; for(int i=0;i<n;++i){ int a,b; cin>>a>>b; G[a].push_back(make_pair(b,1)); G[b].push_back(make_pair(a,0)); } ans_cntt = 1; ans_step = 0; for(int i=1;i<=2*n;++i){ if(G[i].empty())continue; if(vis[i])continue; cnt_e = 0; cnt_v = 0; init_dfs(i); cnt_e/=2; // cout<<"check_cnt: "<<cnt_e<<" "<<cnt_v<<endl; if(cnt_e == cnt_v-1){ minn_step = INT_MAX; minn_cntt = 0; tree_dp(i,0); dp_dfs(i); ans_step += minn_step; ans_cntt *= minn_cntt; ans_cntt %=MOD; continue; } if(cnt_e == cnt_v){ minn_step = 0; cir_edge.clear(); find_circle(i,0); deal_circle(cir_vector.first,cir_vector.second,cir_vector.first,cir_val); int tmp1 = 0,tmp2 = 0; int len = cir_edge.size(); for(int i=0;i<len;++i){ tmp1 += cir_edge[i]; tmp2 += !cir_edge[i]; } // cout<<"check_tmp: "<<tmp1<<" "<<tmp2<<endl; ans_step += minn_step + min(tmp1,tmp2); if(tmp1 == tmp2 )ans_cntt *= 2; ans_cntt %= MOD; continue; } succ = 0; break; } if(succ){ cout<<ans_step<<" "<<ans_cntt<<" "; }else{ cout<<"-1 -1 "; } } int main(){ cin.sync_with_stdio(false); int t; cin>>t; while(cin>>n)init(); return 0; }