一道基环树+树形\(DP\)

原题链接

显然输入的是内向基环树森林,且我们可以单独考虑每一棵基环树。

既然是基环树,自然先\(dfs\)找环,然后随便找环上的一点\(r\),将其与\(A[r]\)的边断开,建反边,这时就会形成一棵以\(r\)为根的树,且每个点的子节点都是能限制它的元素。

于是我们可以在这棵树上跑树形\(DP\)。

定义\(f[x][0]\)表示不投放元素\(x\)的时候,以\(x\)为根的子树中能投放元素的最大值;\(f[x][1]\)表示投放元素\(x\)的时候,以\(x\)为根的子树中能投放元素的最大值。

  1. 不投放\(x\)时,它的子节点可以投放,也可以不投放。

\(\qquad\qquad f[x][0]=\sum\limits_{A[y]=x}\max\{f[y][0],f[y][1]\}\)

  1. 投放\(x\)时,至少有一个子节点是不投放的,以限制\(x\)。

\(\qquad\qquad f[x][1]=\max\limits_{A[y]=x}\{f[y][0]+\sum\limits_{A[z]=y,z\ne y}\max\{f[z][0],f[z][1]\}\}\)

\(DP\)完成后,用\(\max\{f[r][0],f[r][1]\}\)来更新答案。

然后考虑断边的影响,即导致\(r\)无法限制\(A[r]\),所以我们强制令\(r\)限制\(A[r]\),然后再进行一遍树形\(DP\)。当\(DP\)中计算\(f[A[r]][1]\)时,其子节点可以投放,也可以不投放,因为\(A[r]\)已经被\(r\)限制,所以不需要再有子节点限制它,而对于其它点的转移方程依旧不变。最后再用\(f[r][0]\)去更新答案,因为我们强制让\(r\)去限制\(A[r]\),所以\(r\)不能被投放。

注意这题使用普通的递归会\(MLE\),所以要打手工栈。

#include<cstdio>
using namespace std;
const int N = 1e6 + 10;
int fi[N], di[N << 1], ne[N << 1], a[N], f[N][2], st_x[N], st_i[N], st_y[N], st_s[N], cb, l;
bool v[N];
inline int re()
{
int x = 0;
char c = getchar();
bool p = 0;
for (; c<'0' || c>'9'; c = getchar())
p |= c == '-';
for (; c >= '0'&&c <= '9'; c = getchar())
x = x * 10 + (c - '0');
return p ? -x : x;
}
inline void add(int x, int y)
{
di[++l] = y;
ne[l] = fi[x];
fi[x] = y;
}
inline int maxn(int x, int y)
{
return x > y ? x : y;
}
void dfs(int x)
{
int y;
v[x] = 1;
for (y = a[x]; y; y = a[y])
{
if (v[y])
{
cb = y;
return;
}
v[y] = 1;
}
}
void dp(int x)
{
int k = 1;
st_x[1] = x;
start:
st_s[k] = 0;
v[st_x[k]] = 1;
f[st_x[k]][0] = f[st_x[k]][1] = 0;
for (st_i[k] = fi[st_x[k]]; st_i[k]; st_i[k] = ne[st_i[k]])
{
st_y[k] = di[st_i[k]];
if (st_y[k] ^ cb)
{
st_x[k + 1] = st_y[k];
k++;
goto start;
end:
st_s[k] += maxn(f[st_y[k]][0], f[st_y[k]][1]);
}
}
f[st_x[k]][0] = st_s[k];
for (st_i[k] = fi[st_x[k]]; st_i[k]; st_i[k] = ne[st_i[k]])
{
st_y[k] = di[st_i[k]];
if (st_y[k] ^ cb)
f[st_x[k]][1] = maxn(f[st_x[k]][1], 1 + st_s[k] - maxn(f[st_y[k]][0], f[st_y[k]][1]) + f[st_y[k]][0]);
}
if (--k)
goto end;
}
void dp_2(int x)
{
int k = 1;
st_x[1] = x;
start:
st_s[k] = 0;
f[st_x[k]][0] = f[st_x[k]][1] = 0;
for (st_i[k] = fi[st_x[k]]; st_i[k]; st_i[k] = ne[st_i[k]])
{
st_y[k] = di[st_i[k]];
if (st_y[k] ^ cb)
{
st_x[k + 1] = st_y[k];
k++;
goto start;
end:
st_s[k] += maxn(f[st_y[k]][0], f[st_y[k]][1]);
}
}
f[st_x[k]][0] = st_s[k];
if (!(st_x[k] ^ a[cb]))
{
f[st_x[k]][1] = st_s[k] + 1;
if (--k)
goto end;
return;
}
for (st_i[k] = fi[st_x[k]]; st_i[k]; st_i[k] = ne[st_i[k]])
{
st_y[k] = di[st_i[k]];
if (st_y[k] ^ cb)
f[st_x[k]][1] = maxn(f[st_x[k]][1], 1 + st_s[k] - maxn(f[st_y[k]][0], f[st_y[k]][1]) + f[st_y[k]][0]);
}
if (--k)
goto end;
}
int main()
{
int i, n, ma, s = 0;
n = re();
for (i = 1; i <= n; i++)
{
a[i] = re();
add(a[i], i);
}
for (i = 1; i <= n; i++)
if (!v[i])
{
ma = 0;
dfs(i);
dp(cb);
ma = maxn(f[cb][0], f[cb][1]);
dp_2(cb);
ma = maxn(ma, f[cb][0]);
s += ma;
}
printf("%d", s);
return 0;
}

JoyOI1940 创世纪的更多相关文章

  1. Poetize4 创世纪

    3037: 创世纪 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 123  Solved: 66[Submit][Status] Description ...

  2. 为创世纪图书馆(Library Genesis)作镜像

    简介 Library Genesis的Wikipedia条目中的介绍是: Library Genesis or LibGen is a search engine for articles and b ...

  3. 编程哲学之C#篇:01——创世纪

    我们能否像神一样地创建一个世界? 对于创建世界而言,程序员的创作能力最接近于神--相对于导演,作家,漫画家而言,他们创建的世界(作品)一旦完成,就再也不会变化,创建的角色再也不会成长.而程序员创建的世 ...

  4. 【BZOJ3037/2068】创世纪/[Poi2004]SZP 树形DP

    [BZOJ3037]创世纪 Description applepi手里有一本书<创世纪>,里面记录了这样一个故事……上帝手中有着N 种被称作“世界元素”的东西,现在他要把它们中的一部分投放 ...

  5. [bzoj3037/2068]创世纪[Poi2004]SZP_树形dp_并查集_基环树

    创世纪 SZP bzoj-3037/2068 Poi-2004 题目大意:给你n个物品,每个物品可以且仅可以控制一个物品.问:选取一些物品,使得对于任意的一个被选取的物品来讲,都存在一个没有被选取的物 ...

  6. CH6401 创世纪

    6401 创世纪 0x60「图论」例题 描述 上帝手中有 N(N≤10^6) 种世界元素,每种元素可以限制另外1种元素,把第 i 种世界元素能够限制的那种世界元素记为 A[i].现在,上帝要把它们中的 ...

  7. 图形学创世纪——写在SIGGRAPH 40年的边上

    40年的边上" title="图形学创世纪--写在SIGGRAPH 40年的边上"> 前言: SIGGRAPH是由ACM SIGGRAPH(美国计算机协会计算机图形 ...

  8. JZOJ 3929. 【NOIP2014模拟11.6】创世纪

    3929. [NOIP2014模拟11.6]创世纪 (Standard IO) Time Limits: 1000 ms Memory Limits: 65536 KB Description 上帝手 ...

  9. T1创世纪(原创)

    创世纪 这是我的第一道原创题 题解: 这道题的核心算法是:加维度的最短路+贪心 状态:\(dis[i][j][t][a]\)表示在 \(t\) 时,到达 \((i,j)\) ,当前共造\(a\)只&q ...

随机推荐

  1. hdu3579-Hello Kiki-(扩展欧几里得定理+中国剩余定理)

    Hello Kiki Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  2. SAP RFC函数

    RFC 接口 RFC是对一个函数模块的调用,但是调用者的系统与被调函数所在的系统是不一样的. RFC也可以在系统内被调用,但是通常调用和被调用是在不同的系统中的. 在sap系统中,远程调用的能力是有R ...

  3. c 语言的复杂声明

    简化的声明语法: dcl: optional *'s direct-dcl direct-dcl: name (dcl)  direct-dcl() direct-dcl[optional size] ...

  4. 使用idea+gradle建立SSM项目

    目录: 一.创建一个gradle项目   二 .在gradle中创建SSM项目 一 .创建一个gradle项目 第一步: 打开我们的IDEA工具,选择创建一个新项目 第二步:这里会让你选择创建一个什么 ...

  5. mysql5.7.10开启慢查询

    MySql提供慢SQL日志的功能,能够记录下响应时间超过一定阈值的SQL查询,以便于我们定位糟糕的查询语句. 首先,查询当前mysql数据库是否开启了慢查询日志功能: show VARIABLES l ...

  6. Java中的字节流,字符流,字节缓冲区,字符缓冲区复制文件

     一:创建方式 1.建立输入(读)对象,并绑定数据源 2.建立输出(写)对象,并绑定目的地 3.将读到的内容遍历出来,然后在通过字符或者字节写入 4.资源访问过后关闭,先创建的后关闭,后创建的先关闭 ...

  7. I/O复用之select

    作用: 实现I/O的多路复用 该函数允许进程指示内核等待多个事件中的任何一个发生,并只有在一个或多个事件发生时或经历一段指定的时间后才唤醒它.进程将于select处阻塞,直到被检测的描述符有一个或多个 ...

  8. cef研究

    // Copyright (c) 2010 The Chromium Embedded Framework Authors. All rights // reserved. Use of this s ...

  9. Flask之before_request、after_request

    1.@app.before_request在请求(request)|在视图函数   之前做出响应 解决所有问题 from flask import Flask from flask import re ...

  10. Android使用VideoView播放本地视频及网络视频Demo

    1.xm文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:and ...