HDU6403 Card Game【基环树 + 树形DP】
HDU6403 Card Game
题意:
给出\(N\)张卡片,卡片正反两面都有数字,现在要翻转一些卡片使得所有卡片的正面的值各不相同,问最小翻转次数和最小翻转情况下的不同方案数
\(N\le 10^5\)
题解:
首先考虑建图,对于每张卡片,从卡片正面的数字向卡片背面的数字连一条边,那么接下来问题就转化为了:翻转最少的边,使得每个有出度的点的度数为\(1\)(也就是每条边要对应一个点)
对于每个连通块分别考虑
- 如果这个连通块中的边比点多,那么必然不存在可行方案,因为肯定有边对应不到点
- 如果这个连通块的边和点一样多,那么就构成了一棵基环树,在这个情况下,可以发现在环上的边只要有一条确定好方向,其他所有环上的边都确定了方向了,而不在环上的边的方向必然要指向环,这样才能保证每个点只有一个出度,所以只有环上边的方向不确定,而环上边的方向只有两种,如果两种方向需要翻转的边的数量一样多,那么对于当前连通块的方案数为\(2\),否则为\(1\)
- 如果这个连通块的边比点少\(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】的更多相关文章
- 洛谷 P1453 城市环路 ( 基环树树形dp )
题目链接 题目背景 一座城市,往往会被人们划分为几个区域,例如住宅区.商业区.工业区等等.B市就被分为了以下的两个区域--城市中心和城市郊区.在着这两个区域的中间是一条围绕B市的环路,环路之内便是B市 ...
- BZOJ 1040 [ZJOI2008]骑士 (基环树+树形DP)
<题目链接> 题目大意: Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的 ...
- 骑士 HYSBZ - 1040(基环树+树形dp)
Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战火绵延五百里,在和平环境中 ...
- BZOJ 1040 骑士 基环树 树形DP
题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=1040 题目大意: Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫 ...
- bzoj 1040: [ZJOI2008]骑士【基环树+树形dp】
没考虑可以连着两个不选--直接染色了 实际上是基环森林,对于每棵基环树,dfs找出一个环边,然后断掉这条边,分别对这条边的两端点做一边treedp,取max加进答案里 treedp是设f[u]为选u点 ...
- day 2 下午 骑士 基环树+树形DP
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #inc ...
- BZOJ2878 [Noi2012]迷失游乐园 【基环树 + 树形dp + 期望dp】
题目链接 BZOJ2878 题解 除了实现起来比较长,思维难度还是挺小的 观察数据范围发现环长不超过\(20\),而我们去掉环上任何一个点就可以形成森林 于是乎我们枚举断掉的点,然后只需求出剩余每个点 ...
- 【BZOJ-3572】世界树 虚树 + 树形DP
3572: [Hnoi2014]世界树 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 1084 Solved: 611[Submit][Status ...
- 【BZOJ-2286】消耗战 虚树 + 树形DP
2286: [Sdoi2011消耗战 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 2120 Solved: 752[Submit][Status] ...
随机推荐
- 【递归】P1706全排列问题
题目相关 题目描述 输出自然数 1 到 n所有不重复的排列,即 n的全排列,要求所产生的任一数字序列中不允许出现重复的数字. 输入格式 一个整数 n**. 输出格式 由 1∼n 组成的所有不重复的数字 ...
- Vue基础语法与指令
项目初始化 用vscode打开终端,输入npm init -y生成package.json 然后安装vue npm install vue 需要注意的是,我遇到了这个问题 出现原因:文件夹名和生成的p ...
- (二)数据源处理4-excel部分封装及数据转换
excel02.py # -*- coding: utf-8 -*-#@File :excel_oper_02.py#@Auth : wwd#@Time : 2020/12/7 8:16 下午impo ...
- Java 使用 commons-fileupload 实现文件上传工具类
依赖包 文件上传可以使用 Apache 文件上传组件, commons-fileupload, 它依赖于 commons-io commons-io.jar: https://repo1.maven. ...
- 【高级排序算法】1、归并排序法 - Merge Sort
归并排序法 - Merge Sort 文章目录 归并排序法 - Merge Sort nlogn 比 n^2 快多少? 归并排序设计思想 时间.空间复杂度 归并排序图解 归并排序描述 归并排序小结 参 ...
- top有用的开关控制命令
[原创]本文为原创博文,转发请注明出处:https://www.cnblogs.com/dingbj/p/top_command.html 今天偶然用到top命令,在动态刷新的界面上输入h顺便看了下帮 ...
- DOCKER 安装步骤-最靠谱的笔记
一.系统环境规划 服务器名 项目名称 docker 操作系统 CentOS Linux release 7.1.1503 (Core) Docker 版本 17.03.2-ce 二.Docker ...
- MySQL索引性能分析
为什么要做性能分析 你有没有这样的情况. 面对一个你没怎么写过的.复杂的业务,你构思了很久,终于开始敲下了第一段代码. 写的过程迷迷糊糊,有的时候还能把自己搞晕了. 但你还是终于把它写完了. 但是点击 ...
- 运行.bat执行sql文件 —— mysql
参考地址:https://www.cnblogs.com/dingjiaoyang/p/9990188.html 运行test.bat 执行 test.sql文件 test.bat: @ECHO OF ...
- 7行代码解决P1441砝码称重(附优化过程)
先贴上最终代码感受一下: #include <bits/stdc++.h> using namespace std; int i, N, M, wi[21], res = 0; int m ...