【洛谷2304_LOJ2134】[NOI2015]小园丁与老司机(动态规划_网络流)
题目:
(LOJ 上每个测试点有部分分)
写了快一天 …… 好菜啊
分析:
毒瘤二合一题 ……
注意本题(及本文)使用 \(x\) 向右,\(y\) 向上的「数学坐标系」,而不是 \(x\) 向下,\(y\) 向右的所谓「OI 坐标系」。「同一行」指 \(y\) 相同,「同一列」指 \(x\) 相同。
老司机
注意,只能在 没有经过的 树下转向,并且每棵树只能访问一次。
第一反应是 \(f_{u}\) 表示从点 \(u\) (树 \(u\) )出发能走到的最多的点数(不含本身),记忆化搜索,则答案就是 \(f_0\) (令 \((0,0)\) 为 \(0\) 号点)。然而,这样转移是有环的,因为同一行中互相可达。
那么就考虑逐行转移。先对于所有点 \(u\) 处理出所有不同行的可达的 \(v\) (即 \(u\) 与 \(v\) 的横坐标相等、横纵坐标之和相等或横纵坐标之差相等且 \(u\) 与 \(v\) 的连线上没有其他点)。计算 \(f(u)\) 时,先枚举它在同行走到了哪个点,再枚举它从那个点走到了上方的哪个点。设当前点是该行的第 \(p\) 个点,要走到同行的第 \(k\) 个点。若 \(p<k\) ,则最优策略是先走完左侧的所有点,再向右走到 \(k\) ,然后向上走;若 \(p=k\) ,则只能直接向上走;若 \(p>k\) ,则先走完右侧的所有点,再向左走到 \(k\) 。形式化地(其中 \(p_{i,j}\) 表示 \(y=i\) 的横坐标从小到大第 \(j\) 个点,标号从 \(0\) 开始。\(a_i\) 表示 \(y=i\) 的总点数):
a_y-k-1\ (k<i且p_{y,k}无出边)\\
f_v+a_y-k\ (k<i且p_{y,k}到v有连边)\\
f_v+1\ (p_{y,i}到v有连边)\\
k\ (k>i且p_{y,k}无出边)\\
f_v+k+1\ (k>i且p_{y,i}到v有连边)\\
\end{cases}
\]
(注意要讨论边界情况。状态定义中不含 \(p_{y,i}\) 这点本身的贡献)
时间复杂度 \(O(nm)\) ,其中 \(m\) 表示每行最大的点数。题面上说 \(m\leq 1000\) 但是小恐龙说数据里有到 \(1600\) 多的?
至于输出方案,转移的时候记录一下转移时用的 \(k\) 是哪个,然后在 \(p_{y,k}\) 的出边中找哪个点的 \(f\) 值加上 \(a_y-k-1\) 之类的东西(视 \(k\) 和 \(i\) 的大小关系而定)等于当前点的 \(f\) 值,走过去就 OK 啦。
于是第一问就 愉快地 解决啦。
小园丁
首先用和输出方案类似的方法处理出哪些连边是可能存在于最优路径中的。然后考虑限制:
压路机可以从任意一棵树下出发,在任意一棵树下停止
压路机只能经过可能存在于最优路径中的边,每条边要被经过至少一次\
这不是 …… 网络流??
每条边的权值是 \([1,+\infty)\) ,然后源点向所有点连 \([0,+\infty)\) ,所有点向汇点连 \([0,+\infty)\) ,然后就是一个上下界有源汇最小流板子了。(啥玩意?) 我是照着小恐龙的博客现学的。
为酒肉朋友打广告:有上下界的网络流/费用流 学习笔记 - Little Dino
代码:
写自闭了 ……
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <vector>
#include <map>
#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);
}
typedef pair<int, int> pii;
const int N = 5e4 + 10, M = 1e3 + 10, INF = 0x3f3f3f3f;
int n, X[N], Y[N], head[N], ecnt, ycnt;
pii pos[N], dp[N];
bool vis[N];
vector<int> id[N];
map<int, vector<int> > mp;
struct edge
{
int to, next;
}e[N * 3];
void add(const int a, const int b)
{
e[ecnt] = (edge){b, head[a]}, head[a] = ecnt++;
}
bool cmpx(const int a, const int b)
{
return X[a] < X[b];
}
void init()
{
static map<int, int> x, x_add_y, x_sub_y;
memset(head, -1, sizeof(int[n + 1]));
for (int i = 1; i <= n; i++)
mp[Y[i]].push_back(i);
for (auto it = mp.rbegin(); it != mp.rend(); it++)
{
sort(it->second.begin(), it->second.end(), cmpx);
id[++ycnt] = it->second;
for (auto itt = it->second.begin(); itt != it->second.end(); itt++)
{
int u = *itt;
pos[u] = pii(ycnt, itt - it->second.begin());
if (x.count(X[u]))
add(u, x[X[u]]);
x[X[u]] = u;
if (x_add_y.count(X[u] + Y[u]))
add(u, x_add_y[X[u] + Y[u]]);
x_add_y[X[u] + Y[u]] = u;
if (x_sub_y.count(X[u] - Y[u]))
add(u, x_sub_y[X[u] - Y[u]]);
x_sub_y[X[u] - Y[u]] = u;
}
}
}
template<typename T>
inline void get_max(T &a, const T b)
{
a = max(a, b);
}
int dfs(const int u)
{
int y = pos[u].first, p = pos[u].second;
if (vis[u])
return dp[u].first;
vis[u] = true;
dp[u] = pii(0, 0);
for (int k = 0; k < p; k++)
{
int now = id[y][k], res = 0;
for (int i = head[now]; ~i; i = e[i].next)
{
int v = e[i].to;
get_max(res, dfs(v));
}
get_max(dp[u], pii(res + id[y].size() - k - (head[now] == -1), now));
}
for (int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
get_max(dp[u], pii(dfs(v) + 1, u));
}
for (int k = p + 1; k < id[y].size(); k++)
{
int now = id[y][k], res = 0;
for (int i = head[now]; ~i; i = e[i].next)
{
int v = e[i].to;
get_max(res, dfs(v));
}
get_max(dp[u], pii(res + k + (head[now] != -1), now));
}
return dp[u].first;
}
void work1()
{
int ans = dfs(n);
write(ans), putchar('\n');
int tmp = n;
while (tmp)
{
if (tmp != n)
write(tmp), putchar(' ');
int y = pos[tmp].first, p = pos[tmp].second, nxt = dp[tmp].second, delta = 1, nxtt = 0;
if (!nxt)
break;
if (pos[nxt].second < p)
{
for (int i = p + 1; i < id[y].size(); i++)
write(id[y][i]), putchar(' '), ++delta;
for (int i = p - 1; i >= pos[nxt].second; i--)
write(id[y][i]), putchar(' '), ++delta;
}
else if (pos[nxt].second > p)
{
for (int i = p - 1; i >= 0; i--)
write(id[y][i]), putchar(' '), ++delta;
for (int i = p + 1; i <= pos[nxt].second; i++)
write(id[y][i]), putchar(' '), ++delta;
}
for (int i = head[nxt]; ~i; i = e[i].next)
{
int v = e[i].to;
if (dp[v].first + delta == dp[tmp].first)
{
nxtt = v;
break;
}
}
tmp = nxtt;
}
putchar('\n');
}
namespace Subtask2
{
int s, t, S, T, out[N];
namespace Dinic
{
struct edge
{
int to, w, next;
}e[N * 12];
int cur[N], dis[N], head[N], ecnt, s, t;
void add(const int a, const int b, const int c)
{
e[ecnt] = (edge){b, c, head[a]}, head[a] = ecnt++;
}
void addtw(const int a, const int b, const int c)
{
add(a, b, c), add(b, a, 0);
}
bool bfs()
{
memset(dis, -1, sizeof(int[T + 1]));
memcpy(cur, head, sizeof(int[T + 1]));
static queue<int> q;
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if (e[i].w && dis[v] == -1)
dis[v] = dis[u] + 1, q.push(v);
}
}
return dis[t] != -1;
}
int dfs(const int u, const int minn)
{
if (u == t || !minn)
return minn;
int used = 0;
for (int &i = cur[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if (e[i].w && dis[v] == dis[u] + 1)
{
int w = dfs(v, min(minn - used, e[i].w));
e[i].w -= w, e[i ^ 1].w += w, used += w;
if (used == minn)
break;
}
}
return used;
}
int work(const int _s, const int _t)
{
s = _s, t = _t;
int ans = 0;
while (bfs())
ans += dfs(s, INF);
return ans;
}
}
void work()
{
using Dinic::addtw;
static queue<int> q;
static bool vis[N], mark[N * 3];
s = n + 1, t = n + 2, S = n + 3, T = n + 4;
memset(Dinic::head, -1, sizeof(int[T + 1]));
for (int i = 1; i <= n; i++)
addtw(s, i, INF), addtw(i, t, INF);
q.push(n);
while (!q.empty())
{
int u = q.front();
int y = pos[u].first, p = pos[u].second;
q.pop();
for (int k = 0; k < p; k++)
{
int now = id[y][k], delta = id[y].size() - k;
for (int i = head[now]; ~i; i = e[i].next)
{
int v = e[i].to;
if (!mark[i] && dp[v].first + delta == dp[u].first)
{
++out[now], --out[v], addtw(now, v, INF), mark[i] = true;
if (!vis[v])
q.push(v), vis[v] = true;
}
}
}
for (int i = head[u]; ~i; i = e[i].next)
{
int v = e[i].to;
if (!mark[i] && dp[v].first + 1 == dp[u].first)
{
++out[u], --out[v], addtw(u, v, INF), mark[i] = true;
if (!vis[v])
q.push(v), vis[v] = true;
}
}
for (int k = p + 1; k < id[y].size(); k++)
{
int now = id[y][k], delta = k + 1;
for (int i = head[now]; ~i; i = e[i].next)
{
int v = e[i].to;
if (!mark[i] && dp[v].first + delta == dp[u].first)
{
++out[now], --out[v], addtw(now, v, INF), mark[i] = true;
if (!vis[v])
q.push(v), vis[v] = true;
}
}
}
}
for (int i = 1; i <= n; i++)
if (out[i] < 0)
addtw(S, i, -out[i]);
else
addtw(i, T, out[i]);
addtw(t, s, INF);
Dinic::work(S, T);
int flow = Dinic::e[Dinic::ecnt - 1].w;
Dinic::e[Dinic::ecnt - 1].w = Dinic::e[Dinic::ecnt - 2].w = 0;
write(flow - Dinic::work(t, s));
}
}
int work()
{
read(n);
for (int i = 1; i <= n; i++)
read(X[i]), read(Y[i]);
++n, X[n] = Y[n] = 0;
init();
work1();
Subtask2::work();
return 0;
}
}
int main()
{
#ifdef BlueSpirit
freopen("2304.in", "r", stdin);
#endif
return zyt::work();
}
【洛谷2304_LOJ2134】[NOI2015]小园丁与老司机(动态规划_网络流)的更多相关文章
- uoj132/BZOJ4200/洛谷P2304 [Noi2015]小园丁与老司机 【dp + 带上下界网络流】
题目链接 uoj132 题解 真是一道大码题,,,肝了一个上午 老司机的部分是一个\(dp\),观察点是按\(y\)分层的,而且按每层点的上限来看可以使用\(O(nd)\)的\(dp\),其中\(d\ ...
- [UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机
[UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机 试题描述 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 \(n\) 棵许愿 ...
- [BZOJ4200][Noi2015]小园丁与老司机
4200: [Noi2015]小园丁与老司机 Time Limit: 20 Sec Memory Limit: 512 MBSec Special JudgeSubmit: 106 Solved ...
- 【BZOJ4200】[Noi2015]小园丁与老司机 DP+最小流
[BZOJ2839][Noi2015]小园丁与老司机 Description 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 nn 棵许愿树,编号 1,2,3,…,n1,2, ...
- luogu P2304 [NOI2015]小园丁与老司机 dp 上下界网络流
LINK:小园丁与老司机 苦心人 天不负 卧薪尝胆 三千越甲可吞吴 AC的刹那 真的是泪目啊 很久以前就写了 当时记得特别清楚 写到肚子疼.. 调到胳膊疼.. ex到根不不想看的程度. 当时wa了 一 ...
- BZOJ4200 & 洛谷2304 & UOJ132:[NOI2015]小园丁与老司机——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=4200 https://www.luogu.org/problemnew/show/P2304 ht ...
- [BZOJ]4200: [Noi2015]小园丁与老司机
Time Limit: 20 Sec Memory Limit: 512 MBSec Special Judge Description 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维 ...
- [Noi2015]小园丁和老司机
来自FallDream的博客,未经允许,请勿转载,谢谢. 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有n棵许愿树,编号1,2,3,…,n,每棵树可以看作平面上的一个点,其中 ...
- 【bzoj4200】[Noi2015]小园丁与老司机 STL-map+dp+有上下界最小流
题目描述 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 nn 棵许愿树,编号 1,2,3,…,n1,2,3,…,n,每棵树可以看作平面上的一个点,其中第 ii 棵树 (1≤ ...
随机推荐
- Java和C++里面的重写/隐藏/覆盖
首先,无关重载. 注:重载是同一个类的各个函数之间的.重写是父类子类之间的.Overload和Overwrite(也叫Override)的区别. 注意:Java里面区分重写(Override/Over ...
- influxDB系列(一)
这个是github上面一个人总结的influxDB的操作手册,还不错:https://xtutu.gitbooks.io/influxdb-handbook/content/zeng.html 1. ...
- Centos7 Samba 独立账户
创建了一个组:smbgrp 和用户srijan通过认证来访问Samba服务器. groupadd smbgrp useradd srijan -G smbgrp smbpasswd -a srijan ...
- 【转】如何查看Oracle客户端版本及位数(Windows系统)
一.方法一 查看oracle客户端版本:sqlplus -v 查看oracle客户端版本:sqlplus /nolog命令 Step 1:运行sqlplus /nolog命令,如果你服务器安装了多个客 ...
- 【转】SQL中的取整函数FLOOR、ROUND、CEIL、TRUNC、SIGN
--------------------------------------------------------------------------1 trunc(value,precision)按精 ...
- eclispe pydev tab改回 空格找到方法了,这个链接:http://stackoverflow.com/questions/23570925/eclipse-indents-new-line-with-tabs-instead-of-spaces
看这个链接: 3down votefavorite 1 I've followed all the suggestions here. When I press return, I get a new ...
- transport transmission
运输层 transport layer 传输控制协议 transmission control protocol
- Lightoj 1002 - Country Roads(prim算法)
I am going to my home. There are many cities and many bi-directional roads between them. The cities ...
- 一些linux嵌入式资源下载地址
linux内核源代码情景分析 非扫描版 上下册合订版 字清楚 带书签 1575页 pdfhttp://download.csdn.net/source/2002579***************** ...
- 整型变量修饰符,char类型数据存储原理,字节数,
//------------------整型变量修饰符 修饰符(int short long longlong signed unsigned)所有修饰符都是用来修整形 int 4short %hd ...