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. WPF学习笔记02_布局

    布局原则 WPF窗口只能包含单个元素.如果要放置多个元素,需要放置一个容器,然后在容器中添加元素. 不应显示的设定元素的尺寸 不应该使用屏幕坐标指定元素的位置 布局容器的子元素"共享&quo ...

  2. Unity 编辑器(移除missing)

    移除 Missing(Mono Script) ` private static void FindMissingReferences() { GameObject[] pAllObjects = ( ...

  3. 7.prometheus之查询API

    一.格式概述 二.表达式查询 2.1 Instant queries(即时查询) 2.2 范围查询 三.查询元数据 3.1 通过标签匹配器找到度量指标列表 3.2 获取标签名 3.3 查询标签值 四. ...

  4. Java编译期注解处理器详细使用方法

    目录 Java编译期注解处理器 启用注解处理器 遍历语法树 语法树中的源节点 语法树节点的操作 给类增加注解 给类增加import语句 构建一个内部类 使用方法 chainDots方法 总结 Java ...

  5. 二. SpringCloud基本Rest微服务工程搭建

    1. 父工程构建 1.1 Maven项目搭建 环境 版本 JDK 1.8 Maven 3.6+ Maven模板 maven-archetype-size 删除父工程src文件 1.2 父工程pom文件 ...

  6. 【Android初级】使用setContentView实现页面的转换效果(附源码)

    一提到Android中页面的切换,你是不是只想到了startActivity启动另一个Activity? 其实在Android中,可以直接利用setContentView达到类似页面转换效果的!实现思 ...

  7. 简单的DbContext工厂类(EFCore)

    前言 根据appsettings.json的中配置的数据库类型,使用工厂模式创建DbContext 代码实现 appsettings.json中的配置项 //使用的数据库类型 "Server ...

  8. cfsetispeed、cfsetospeed和cfsetspeed探究

    在我https://www.cnblogs.com/Suzkfly/p/11055532.html这篇博客中有一个疑问,就是在串口设置波特率的域中,没有将输入输出波特率分开,那为什么会有几个不同的设置 ...

  9. centos系统磁盘扩容

    1.查看磁盘空间大小,使用df -h 命令. 2. 增加磁盘空间,例如下图使用VM虚拟机增加的方式.物理机直接安装挂载上去. 3. 使用fdisk /dev/sda, 创建新分区. 4.重启Linux ...

  10. vfd-cloud——一个适合练习上手的云存储网盘springboot项目(开发中)

    vfd-cloud           ​ 一个基于SpringBoot的云存储网盘项目,适合练手学习SpringBoot,用到的技术栈列到了下面.支持用户的注册登陆及修改密码,利用邮箱进行验证.支持 ...