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. Linux下安装Oracle11g服务器【转】

    安装环境 Linux服务器:oracle linux 6.6 64位 Oracle服务器:Oracle11gR2 64位 系统要求 Linux安装Oracle系统要求 系统要求 说明 内存 必须高于1 ...

  2. Nginx+FFmpeg实现RTSP转RTMP

    RTSP转RTMP 本次转流采用Centos+Nginx+FFmpeg实现,具体实现如下: 1. 安装Ngxin 安装详细略(可以选择安装阿里的Tengine,官方[下载路径](Download - ...

  3. node解决跨域和服务器代理详解代码

    node中有很多解决服务器代理的插件,这里简介一个:express-http-proxy 之前网上查的使用node解决跨域的插件,有很多,例如,cors,koa2,这里解决跨域问题我拿原生解决的,ex ...

  4. 在CentOS上安装Singularity高性能容器

    什么是singularity容器 Singularity是劳伦斯伯克利国家实验室专门为大规模.跨节点HPC和DL工作负载而开发的容器化技术.具备轻量级.快速部署.方便迁移等诸多优势,且支持从Docke ...

  5. python学习笔记 | 列表去重

    ''' @author: 人人都爱小雀斑 @time: 2020/3/10 10:29 @desc: ''' L=[1,5,7,4,6,3,0,5,8,4,4] 方法1:for循环 L1=[] for ...

  6. 关联实现下-jsonpath取值(有难度!!耗时长)

    re的使用参考:正则表达式基础及re模块:https://www.cnblogs.com/dream66/p/12953729.html import restr1 = '{"access_ ...

  7. 【Linux】NFS搭建及使用详解

    环境:CentOS release 6.8 server  192.168.25.100 client1 192.168.25.101 client2 192.168.25.102 1.服务端操作 1 ...

  8. 【EXP】根据字段导出数据query

    exp有些时候需要根据字段来进行导出操作 例如:想要导出hr用户中的employees中salary要大于4000的数据 这样的话需要添加where语句,需要用到的参数是query 查看下大于4000 ...

  9. SAP中的F4帮助

    今天在调试标准程序的时候,意外的发现了一个F4帮助的函数,感觉还是挺好用的. F4IF_FIELD_VALUE_REQUEST从函数名就可以看出是给字段添加F4帮助的. F4 help for fie ...

  10. SQLSERVER 修改数据实例的排序规则

    SQL Server服务器修改排序规则的方法 操作及验证步骤: 1 登录数据库后,查看当前安装数据库默认排序规则的两种方式 方式一.使用SQL Server 2014 Management Studi ...