被教练勒令做题不能看题解后的第一道新题,自行 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. mysql function 查询子级机构

    DROP FUNCTION IF EXISTS queryChildOrgList;CREATE FUNCTION queryChildOrgList(id VARCHAR(20))RETURNS V ...

  2. 负载均衡之Ocelot

    Ocelot 负载均衡:   背景知识,ocelot是基于 webapi 的网关框架,要使用ocelot来做路由转发和负载均衡,需要创建一个webapi,然后以这个webapi来做gateway.   ...

  3. Butterfly

    Butterfly 时间限制:C/C++ 2秒,其他语言4秒空间限制:C/C++ 131072K,其他语言262144K64bit IO Format: %lld 题目描述 给定一个n*m的矩阵,矩阵 ...

  4. [luoguP1736] 创意吃鱼法(DP)

    传送门 f[i][j][0] 表示从右下角到左上角,以(i,j)为起点能延伸的最大值 f[i][j][1] 表示从左下角到右上角,以(i,j)为起点能延伸的最大值 up[i][j] 表示(i,j)上面 ...

  5. hdu 1867 kmp匹配

    #include<stdio.h> #include<string.h> #define N 100100 void getnext(int next[],char s[]) ...

  6. A - 不容易系列之(3)―― LELE的RPG难题 简单递推

    人称“AC女之杀手”的超级偶像LELE最近忽然玩起了深沉,这可急坏了众多“Cole”(LELE的粉丝,即"可乐"),经过多方打探,某资深Cole终于知道了原因,原来,LELE最近研 ...

  7. SQL SERVE BASE

    http://www.cnblogs.com/chillsrc/category/49632.html

  8. 复习es6-let和const

    1.声明变量的方法 es5 : var   function es6 : var   function   let    const   class 2.let(const)与var 不同 let不能 ...

  9. ngTbale真分页实现排序、搜索等功能

    一. 真分页表格基础 1. 需求:分页,排序,搜索都是需要发API到服务端. 2. JS实现代码: getStorage是localStorage一个工具方法,可以自己写这个方法. API参数如下: ...

  10. HR系统-人员申请单

    部门在人员缺失时,须要进行人员申请, 申请会涉及到单据的建立及审核.单据建立界面例如以下: