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] ...
随机推荐
- 【基础】1001_Hello,World!
题目相关 [题目描述] 编写一个能够输出"Hello,World!"的程序,这个程序常常作为一个初学者接触一门新的编程语言所写的第一个程序,也经常用来测试开发.编译环境是否能够正常 ...
- 【Web】CSS实现抖音风格字体效果(设置文本阴影)
简单记录 -慕课网- 步骤一:抖音风格字体效果 案例:抖音风格的字体特效. 实现这个 需要设置 网页背景颜色 字体颜色 字体大小 文本阴影 重点介绍如何设置文本阴影 CSS的文本阴影text-shad ...
- xtrabackup_binlog_info
文件保存了备份结束时刻binlog的名称和位置
- 超详细 安装VMware Workstation,并安装WIN10操作系统连接外网 步骤指导
首先下载VMware Workstation15.1版本,我保存在迅雷链接里面,下载速度非常可观. 链接:https://pan.xunlei.com/s/VMRSt6hHMZXEmPZCm6gJcG ...
- 面试时通过volatile关键字,全面展示线程内存模型的能力
面试时,面试官经常会通过volatile关键字来考核候选人在多线程方面的能力,一旦被问题此类问题,大家可以通过如下的步骤全面这方面的能力. 1 首先通过内存模型说明volatile关键字的作用 ...
- 关于SQL Server 镜像数据库快照的创建及使用
从SQL Server 2005 SP 起,SQL 开始支持数据库镜像.它的设计目的是试图为SQL Server 提供一个具有实时性数据同步的灾难恢复技术,即能够提供数据冗余备份,切换起来比较方便.每 ...
- 如何使用 Vuepress
项目结构 ├─docs │ ├─.vuepress --vuepress相关文件路径 (主要配置) │ │ ├─dist --build 打包生成路径 (自定义) │ │ ├─nav --导航条配置 ...
- websocket心跳重连 websocket-heartbeat-js
初探和实现websocket心跳重连(npm: websocket-heartbeat-js) 心跳重连缘由 websocket是前后端交互的长连接,前后端也都可能因为一些情况导致连接失效并且相互之间 ...
- java画海报二维码
package cn.com.yitong.ares.qrcode; import java.awt.BasicStroke;import java.awt.Color;import java.awt ...
- Windows 10 安装 JDK14 Java 环境,没有 jre 包
一.下载 JDK Oracle JDK 官网下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.html 二.配置环 ...