被教练勒令做题不能看题解后的第一道新题,自行 yy 了好久终于 AC 了(菜啊)……写博客纪念。

题目:

BZOJ1124

分析:

考虑每个人向他要打的人连边。根据题意,所有点都有且只有一条出边。那么这个图一定是由若干个环、和若干个基环内向树组成(如果想不明白这句话,请时刻牢记每个点只有一条出边)。下面分别考虑两问。

存活人数最小:

核心思想:尽量打死已经开枪的人,让每个人的枪充分发挥作用。

对于环,如果是只有一个点的自环(即这货想不开要自杀),则这个人必死无疑。否则,每个人在打死他的后继后被他的前驱(如果还活着)打死,最后只剩下 \(1\) 个人。即按照边的相反方向依次开枪。

对于环套树,首先可以按照环的方法处理基环,让最后剩下的那个人是某棵树的根,再让他被树上的儿子打死。对于树上的点,一定是每个人打死他的父亲后被他的儿子打死,最后剩下的人数是所有树的叶子(定义为入度为 \(0\) 的点)数量之和。

(我的实现方式比较麻烦:先找出所有环的数量,再找出有多少个环上“插”着树,存活的人数就是 “环的数量 - 插着树的环的数量 - 自杀的数量 + 叶子的数量” )

存活人数最大:

核心思想:尽量打死还没有开枪的人,防止他去毒害别人。

对于环,每个人(如果还活着)顺着边的方向依次开枪,这样活下来的是初始点后继的后继、后继的后继的后继的后继……(环长是奇数时初始点会死)这样能使环上存活人数最大(环长的一半向下取整)。

对于环套树,由于叶子一定存活,所以他们的父亲一定会死,所以不让叶子的父亲开枪是比较优的。由此推广,按照从下往上的顺序开枪能使存活人数最大。(环上的树根也是能打就打。由于这样只是避免了树根去打别的环上的点,所以答案不会更劣。)

树上决策完后,环可能被拆成了若干条链(也可能还是完整的环),然后按照类似于环的决策(隔一个活一个)即可。注意链一定要保证链首存活,否则如果链长是奇数会让答案小 \(1\) 。

代码:

细节比较多(当然还有一个原因就是我写得丑,各位大佬可以自己写)。

注意求最大存活人数一定要按照先树再环 / 链的顺序处理,以及要保证对于所有链都必须从链首开始处理。我的做法是处理完树后删除所有基环上的死人为它的后继提供的度数,然后从所有此时度数为 \(0\) 且初始在环上(我的方式是判断处理完树后的度数是否为 \(0\) )的点开始处理(即链),最后从处理所有存活且后继也存活的点开始处理(即环)。


#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <queue>
using namespace std; namespace zyt
{
template<typename T>
inline bool read(T &x)
{
char c;
bool f = false;
x = 0;
do
c = getchar();
while (c != EOF && c != '-' && !isdigit(c));
if (c == EOF)
return false;
if (c == '-')
f = true, c = getchar();
do
x = x * 10 + c - '0', c = getchar();
while (isdigit(c));
if (f)
x = -x;
return true;
}
template<typename T>
inline void write(T x)
{
static char buf[20];
char *pos = buf;
if (x < 0)
putchar('-'), x = -x;
do
*pos++ = x % 10 + '0';
while (x /= 10);
while (pos > buf)
putchar(*--pos);
}
const int N = 1e6 + 10;
int n, to[N], deg[N], in[N], belong[N], cirnum;
bool dead[N], insta[N], vis[N], have_tree[N];
bool dfs(const int u)
{
if (vis[u])
{
have_tree[belong[u]] = true;
return false;
}
if (insta[u])
{
belong[u] = u;
cirnum++;
return true;
}
insta[u] = true;
if (dfs(to[u]))
{
vis[u] = true;
insta[u] = false;
belong[u] = belong[to[u]];
return u != belong[u];
}
else
{
insta[u] = false;
vis[u] = true;
if (belong[to[u]])
have_tree[belong[to[u]]] = true;
return false;
}
}
int solve_max()
{
memset(vis, 0, sizeof(bool[n + 1]));
for (int i = 1; i <= n; i++)
if (!vis[i])
dfs(i);
int ans = cirnum;
for (int i = 1; i <= n; i++)
{
if (!deg[i])
++ans;
if (to[i] == i || (belong[i] == i && have_tree[i]))
--ans;
}
return n - ans;
}
void solve_cir(const int u)
{
vis[u] = true;
if (!dead[u])
dead[to[u]] = true;
if (!vis[to[u]])
solve_cir(to[u]);
}
int solve_min()
{
static queue<int> q;
memset(vis, 0, sizeof(bool[n + 1]));
for (int i = 1; i <= n; i++)
if (!in[i])
q.push(i);
while (!q.empty())
{
int u = q.front();
q.pop();
--in[to[u]];
if (!dead[u] && !dead[to[u]])
dead[to[u]] = true;
if (!in[to[u]])
q.push(to[u]);
}
static int tmp[N];
memcpy(tmp, in, sizeof(int[n + 1]));
int ans = 0;
for (int i = 1; i <= n; i++)
if (dead[i] && tmp[i] && tmp[to[i]])
--in[to[i]];
for (int i = 1; i <= n; i++)
if (tmp[i] && !in[i])
solve_cir(i);
for (int i = 1; i <= n; i++)
if (!dead[i] && !dead[to[i]])
solve_cir(i);
for (int i = 1; i <= n; i++)
ans += dead[i];
return ans;
}
int work()
{
read(n);
for (int i = 1; i <= n; i++)
read(to[i]), ++deg[to[i]], ++in[to[i]];
write(solve_min()), putchar(' '), write(solve_max());
return 0;
}
}
int main()
{
return zyt::work();
}

【BZOJ1124】[POI2008]枪战Maf(基环树_构造)的更多相关文章

  1. BZOJ1124 [POI2008]枪战Maf[贪心(证明未完成)+拓扑排序]

    吐槽:扣了几个小时,大致思路是有了,但是贪心的证明就是不会, 死磕了很长时间,不想想了,结果码代码又不会码.. 深深体会到自己码力很差,写很多行还没写对,最后别人代码全一二十行,要哭了 以下可能是个人 ...

  2. BZOJ1124 POI2008枪战Maf(环套树+贪心)

    每个点出度都为1,可以发现这张图其实是个环套树森林,树中儿子指向父亲,环上边同向. 首先自环肯定是没救的,先抬出去. 要使死亡人数最多的话,显然若一个点入度为0其不会死亡,而一个孤立的环至少会留下一个 ...

  3. bzoj1124[POI2008]枪战maf

    这代码快写死我了.....死人最多随便推推结论.死人最少,每个环可以单独考虑,每个环上挂着的每棵树也可以分别考虑.tarjan找出所有环,对环上每个点,求出选它和不选它时以它为根的树的最大独立集(就是 ...

  4. 【BZOJ 1124】[POI2008] 枪战Maf Tarjan+树dp

    #define int long long using namespace std; signed main(){ 这个题一看就是图论题,然后我们观察他的性质,因为一个图论题如果没有什么性质,就是真· ...

  5. 【BZOJ1124】[POI2008]枪战Maf 贪心+思路题

    [BZOJ1124][POI2008]枪战Maf Description 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开 ...

  6. BZOJ 1124: [POI2008]枪战Maf

    1124: [POI2008]枪战Maf Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 617  Solved: 236[Submit][Status ...

  7. [POI2008]枪战Maf

    [POI2008]枪战Maf 题目 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的 ...

  8. [POI2008]枪战Maf题解

    问题 C: [POI2008]枪战Maf 时间限制: 1 Sec  内存限制: 256 MB 题目描述 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺 ...

  9. bzoj 1124 [POI2008]枪战Maf 贪心

    [POI2008]枪战Maf Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 741  Solved: 295[Submit][Status][Disc ...

随机推荐

  1. CSC

    CSC CSC Table of Contents 1. account 2. Contacts 3. <国家公派留学人员预订回国机票说明> 4. 回国手续 4.1. 申办及开具<留 ...

  2. Wind rotor states

    test test Table of Contents 1. Wind rotor states 1.1. Turbulent Wake State 1.2. Vortex Ring State 1. ...

  3. extract a page from a multiple pages pdf on Ubuntu OS

    extract a page from a multiple pages pdf 1 extract a page from a multiple pages pdf use pdftk packag ...

  4. CF441D

    题目大意 给出一个有n个数的序列 求符合 区间各数或起来的数大于区间最大数 的区间的个数 题解 预处理出每个数每一位是0的那位左边最近的1和右边最近的1,用单调栈找出每个最大值所在的区间的左右端点,统 ...

  5. Python基础(八)装饰器

    今天我们来介绍一下可以提升python代码逼格的东西——装饰器.在学习装饰器之前我们先来复习一下函数的几个小点,方便更好的理解装饰器的含义. 一.知识点复习 1, 在函数中f1和f1()有什么不同,f ...

  6. springMVC 返回中文字符串时乱码

    转载自:https://blog.csdn.net/yaov_yy/article/details/51819567

  7. 操作系统开发之——打开A20

    我们在上一篇文章提到.怎样制作Bootsect.当然,人总是贪婪的,制作完Bootsect又想着做Setup了,Setup主要工作是打开A20,进入保护模式,等等. 一.介绍A20 这是一个历史性问题 ...

  8. 精确计算java中float和double的精度

    [本文相关的代码放在github上.地址为:https://github.com/VigourJiang/StructuredFloat] Java中double类型的格式基本遵循IEEE 754标准 ...

  9. Qt学习之QListWidget删除Item

    将QListWidgetItem从QListWidget列表中删除有两种方法能够做到.但也要依据自己的须要进行选择. 第一种是 QListWidgetItem *takeItem(int row); ...

  10. 【干货】Kafka 事务特性分析

    特性背景 消息事务是指一系列的生产.消费操作可以要么都完成,要么都失败,类似数据库的事务.这个特性在0.10.2的版本是不支持的,从0.11版本开始才支持.华为云DMS率先提供Kafka 1.1.0的 ...