HDU6403 Card Game

题意:

给出\(N\)张卡片,卡片正反两面都有数字,现在要翻转一些卡片使得所有卡片的正面的值各不相同,问最小翻转次数和最小翻转情况下的不同方案数

\(N\le 10^5\)

题解:

首先考虑建图,对于每张卡片,从卡片正面的数字向卡片背面的数字连一条边,那么接下来问题就转化为了:翻转最少的边,使得每个有出度的点的度数为\(1\)(也就是每条边要对应一个点)

对于每个连通块分别考虑

  1. 如果这个连通块中的边比点多,那么必然不存在可行方案,因为肯定有边对应不到点
  2. 如果这个连通块的边和点一样多,那么就构成了一棵基环树,在这个情况下,可以发现在环上的边只要有一条确定好方向,其他所有环上的边都确定了方向了,而不在环上的边的方向必然要指向环,这样才能保证每个点只有一个出度,所以只有环上边的方向不确定,而环上边的方向只有两种,如果两种方向需要翻转的边的数量一样多,那么对于当前连通块的方案数为\(2\),否则为\(1\)
  3. 如果这个连通块的边比点少\(1\),那么就构成一棵树,由于每条边要对应一个点,而点多了一个,所以必然有个点是没有出度的,而其他点都有且仅有一个出度,考虑以任意一个点为根,枚举每个点\(u\),如果这个点作为无出度的点,那么在它的子树中的边必然向上指,而它的祖边(从根节点到当前节点路径上的边)必然向下指,而其他所有边必然会向上指,所以只要计算出来对于每个点它的子树中边的两种方向和祖边的两种方向还有剩下边的两种方向就能确定需要翻转的边的数量了,最后对于这个连通块,找到最小翻转数量和对应的枚举的节点数即可,也可以用换根\(DP\)来做
view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 1e6+7;
typedef long long int LL;
const LL MOD = 998244353; int n, deg[MAXN], bel[MAXN], ID;
vector<int> G[MAXN],pt[MAXN];
vector<pair<int,int> > es[MAXN]; void init(){
for(int i = 1; i <= 2 * n; i++) G[i].clear();
for(int i = 1; i <= 2 * n; i++) deg[i] = 0;
for(int i = 0; i <= 2 * n; i++) es[i].clear();
for(int i = 1; i <= 2 * n; i++) bel[i] = 0;
for(int i = 1; i <= 2 * n; i++) pt[i].clear();
}
void mark(int u, int id){
bel[u] = id;
pt[id].push_back(u);
for(int v : G[u]) if(!bel[v]) mark(v,id);
}
pair<int,LL> circleTree(int id){
set<int> S;
for(int &x : pt[id]) S.insert(x);
map<pair<int,int>,int> msk;
for(auto &e : es[id]){
deg[e.first]++, deg[e.second]++;
msk[e]++;
}
queue<int> que;
for(int &x : pt[id]) if(deg[x]==1){
S.erase(x);
que.push(x);
}
int __count1 = 0;
while(!que.empty()){
int u = que.front(); que.pop();
for(int v : G[u]){
deg[v]--;
if(deg[v]){
if(msk.count(make_pair(u,v))){
msk[make_pair(u,v)]--;
if(!msk[make_pair(u,v)]) msk.erase(make_pair(u,v));
}
else{
__count1++;
msk[make_pair(v,u)]--;
if(!msk[make_pair(v,u)]) msk.erase(make_pair(v,u));
}
}
if(deg[v]==1){
S.erase(v);
que.push(v);
}
}
}
if(S.size()==1) return make_pair(__count1,1);
if(S.size()==2){
pair<int,int> e1(0,0), e2(0,0);
for(auto &e : es[id]) if(S.count(e.first) and S.count(e.second)){
if(e1==make_pair(0,0)) e1 = e;
else e2 = e;
}
return e1==e2 ? make_pair(__count1+1,2) : make_pair(__count1,1);
}
for(int x : S) G[x].clear();
vector<pair<int,int> > nowE;
for(auto &e : es[id]){
if(S.count(e.first) and S.count(e.second)){
nowE.push_back(e);
G[e.first].push_back(e.second);
G[e.second].push_back(e.first);
}
}
vector<pair<int,int> > circleE;
int u = *S.begin();
int pre = -1;
for(int x : S) deg[x] = 0;
while(true){
deg[u] = 1;
if(G[u][0]==pre) swap(G[u][0],G[u][1]);
int v = G[u][0];
circleE.push_back(make_pair(u,v));
if(deg[v]) break;
pre = u;
u = v;
}
auto cmp = [](pair<int,int> &A, pair<int,int> &B){
int mina = min(A.first,A.second), minb = min(B.first,B.second);
int maxa = max(A.first,A.second), maxb = max(B.first,B.second);
return mina==minb ? maxa < maxb : mina < minb;
};
sort(nowE.begin(),nowE.end(),cmp);
sort(circleE.begin(),circleE.end(),cmp);
int __count = 0;
for(int i = 0; i < (int)nowE.size(); i++){
if(nowE[i] != circleE[i]) __count++;
}
if(__count == (int)S.size() - __count) return make_pair(__count1+__count,2);
else return make_pair(__count1+min(__count,(int)S.size() - __count),1);
}
map<int,int> mp;
set<pair<int,int> > E;
bool tag[MAXN];
int e1, e2;
void dfs1(int u, int f){
if(f){
if(E.count(make_pair(u,f))) e1++, tag[u] = true;
else e2++, tag[u] = false;
}
for(int v : G[u]) if(v!=f) dfs1(v,u);
}
pair<int,int> dfs2(int u, int f, int es1, int es2){
int re = 0, de = 0;
for(int v : G[u]){
if(v==f) continue;
pair<int,int> R;
if(tag[v]) R = dfs2(v,u,es1+1,es2);
else R = dfs2(v,u,es1,es2+1);
re += R.first; de += R.second;
}
mp[de+es1+e2-es2-de] += 1;
return tag[u] ? make_pair(re+1,de) : make_pair(re,de+1);
}
pair<int,LL> treeDp(int id){
mp.clear();
E.clear();
for(auto &e : es[id]) E.insert(e);
for(int &x : pt[id]) tag[x] = false;
e1 = e2 = 0;
dfs1(pt[id][0],0);
dfs2(pt[id][0],0,0,0);
return *mp.begin();
}
void solve(){
scanf("%d",&n);
init();
for(int i = 1; i <= n; i++){
int u, v; scanf("%d %d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
es[0].push_back(make_pair(u,v));
}
ID = 0;
for(int i = 1; i <= 2 * n; i++) if(!bel[i]) mark(i,++ID);
for(auto &e : es[0]) es[bel[e.first]].push_back(e);
for(int i = 1; i <= ID; i++) if(es[i].size() > pt[i].size()){
puts("-1 -1");
return;
}
int num = 0;
LL ret = 1;
for(int i = 1; i <= ID; i++){
if(es[i].size() == pt[i].size()){
auto res = circleTree(i);
num += res.first;
ret = ret * res.second % MOD;
}
else{
if(es[i].empty()) continue;
auto res = treeDp(i);
num += res.first;
ret = ret * res.second % MOD;
}
}
printf("%d %I64d\n",num,ret);
}
int main(){
int tt; for(scanf("%d",&tt); tt; tt--) solve();
return 0;
}

HDU6403 Card Game【基环树 + 树形DP】的更多相关文章

  1. 洛谷 P1453 城市环路 ( 基环树树形dp )

    题目链接 题目背景 一座城市,往往会被人们划分为几个区域,例如住宅区.商业区.工业区等等.B市就被分为了以下的两个区域--城市中心和城市郊区.在着这两个区域的中间是一条围绕B市的环路,环路之内便是B市 ...

  2. BZOJ 1040 [ZJOI2008]骑士 (基环树+树形DP)

    <题目链接> 题目大意: Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的 ...

  3. 骑士 HYSBZ - 1040(基环树+树形dp)

    Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战火绵延五百里,在和平环境中 ...

  4. BZOJ 1040 骑士 基环树 树形DP

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1040 题目大意: Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫 ...

  5. bzoj 1040: [ZJOI2008]骑士【基环树+树形dp】

    没考虑可以连着两个不选--直接染色了 实际上是基环森林,对于每棵基环树,dfs找出一个环边,然后断掉这条边,分别对这条边的两端点做一边treedp,取max加进答案里 treedp是设f[u]为选u点 ...

  6. day 2 下午 骑士 基环树+树形DP

    #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #inc ...

  7. BZOJ2878 [Noi2012]迷失游乐园 【基环树 + 树形dp + 期望dp】

    题目链接 BZOJ2878 题解 除了实现起来比较长,思维难度还是挺小的 观察数据范围发现环长不超过\(20\),而我们去掉环上任何一个点就可以形成森林 于是乎我们枚举断掉的点,然后只需求出剩余每个点 ...

  8. 【BZOJ-3572】世界树 虚树 + 树形DP

    3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1084  Solved: 611[Submit][Status ...

  9. 【BZOJ-2286】消耗战 虚树 + 树形DP

    2286: [Sdoi2011消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 2120  Solved: 752[Submit][Status] ...

随机推荐

  1. Go语言从入门到放弃(设置 go get 为国内源)

    前言 Go语言学到 Gin 框架了, 其实每天学习是比较辛苦的事情, 坚持下去! 在使用 Go 过程中发现, 最无奈的是Go的一些模块下不下来, 即便挂了V, 油管2k不卡的那种, 依旧是 time ...

  2. Head First 设计模式 —— 11. 组合 (Composite) 模式

    思考题 我们不仅仅要支持多个菜单,升值还要支持菜单中的菜单.你如何处理这个新的设计需求? P355 [提示]在我们的新设计中,真正需要以下三点: P354 我们需要某种属性结构,可以容纳菜单.子菜单和 ...

  3. 剑指offer 面试题2:实现Singleton模式

    转自:https://blog.csdn.net/liang19890820/article/details/61615495 Singleton 的头文件(懒汉式/饿汉式公用): // single ...

  4. zookeeper读取事务日志、快照日志

    zookeeper的事务日志的格式如 log.xxx, xxx表示顺序序号 我使用的zookeeper版本:3.5.5 事务日志 执行命令 java -cp .:/tmp/zookeeper-3.5. ...

  5. iconv函数报错 Detected an illegal character in input string

    近日使用php代码导出文件为excel,一直乱码.导出修改编码都无效,最后发现,是需要修改php导出代码本身的编码.首先用记事本打开php代码,另存为,选择ANSI格式.然后打开iconv函数这个ph ...

  6. 【RAC】双节点RAC搭建

    本文主要是双节点的RAC进行搭建,根据黄伟老师的视频进行总结和使用. 搭建环境: 1.两台安装好Linux_x64系统的服务器 2.IP设置 注意:Priv-IP的IP是自己一个网段,而剩下的SCAN ...

  7. 【Oracle】translate函数用法解析

    转自:https://blog.csdn.net/shwanglp/article/details/52814173 基本语法: translate(string,from_str,to_str); ...

  8. DataGridView控件使用Demo

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  9. SpringCloud zuul 网关限流分析

    最近项目中 spring cloud zuul 运用到限流功能,打算配置一下就直接使用,不过在压测与调优过程中遇到一些没有预测到的问题,附上排查与解析结果 yml.pom配置 强烈推荐,按最新gith ...

  10. ubuntu qt5.8 编译qtwebkit

    qtwebkit- 下载地址:http://download.qt.io/community_releases/5.8/5.8.0-final/ 问题一 出现如下错误 解决方法   sudo apt- ...