「NOI2015」小园丁与老司机

要不是这道码农题,去年就补完了NOI2015,其实两问都比较simple,但是写起来很恶心。

先解决第一问,记 \(dp[i]\) 表示老司机到达第 \(i\) 棵树时能许愿的最多的树的数量,考虑没有左右方向的决策时,转移是个DAG,直接做。 对于左右方向的决策,会发现最优解一定是以下两种中的一种。

  1. 对于点 \(x\) ,从左边的某个点 \(y\) 进入这一行,然后先向左走走到最左边的一棵树,然后再调头走到点 \(x\) 。
  2. 对于点 \(x\) ,从右边的某个点 \(y\) 进入这一行,然后先向右走走到最右边的一棵树,然后再调头走到点 \(x\)。

那么就将 \(y\) 坐标相同的点放在一起转移,维护一下前缀和后缀 \(\max\) 即可。

第一个比较恶心的点在于输出方案,单纯记录每个点是由谁转移的话会发现有环,需要记录一下每个点由左右转移过来的方案和由其它转移过来的方案,当发现某一步是由左右转移过来的时候,下一步钦点不能由左右转移过来即可。

考虑第二问,没有了左右的转移边,相当于将所有其它的且在某个最优解上的转移构成的DAG取出来,求这个DAG的最小链覆盖,直接跑有源汇上下界最小流即可。

这里有第二个比较恶心的点,求一个转移是否在最优解上非常麻烦,需要求一个 \(dp'[i]\) 表示老司机从 \(i\) 出发能许愿的最多的树的数量,还是左右方向的决策,这次变成了

  1. 对于点 \(x\) ,先往左走一直走到底,再往右走走到其右边的某一棵树 \(y\) 往上走。

  2. 对于点 \(x\) ,先往右走一周走到底,再往左走走到其左边的某一棵树 \(y\) 往上走。

把式子拆开来发现还是可以前缀和以及后缀和维护的,这个地方比较容易晕,想我这种比较菜的人就要多想想。

code

/*program by mangoyang*/
#include <bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int ch = 0, f = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
#define fi first
#define se second
const int N = 1000005, M = 2000005;
vector<int> ed[N], fx[N];
vector<pair<int, int> > v1[N], v2[N], v3[N], v4[N];
int s[N], ax[N], ay[N], fuck[N], n, len;
inline int D(int x){ return lower_bound(s + 1, s + len + 1, x) - s; }
namespace flow{
queue<int> q;
int a[M], cap[M], nxt[M], head[N], cur[N], dis[N], S, T, cnt = 1;
inline void addedge(int x, int y, int z){
a[++cnt] = y, cap[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;
a[++cnt] = x, cap[cnt] = 0, nxt[cnt] = head[y], head[y] = cnt;
}
inline int bfs(){
memset(dis, -1, sizeof(dis)), dis[S] = 0, q.push(S);
for(; !q.empty(); q.pop()){
int u = q.front();
for(int p = head[u]; p; p = nxt[p]){
int v = a[p];
if(~dis[v] || !cap[p]) continue;
dis[v] = dis[u] + 1, q.push(v);
}
}
return ~dis[T];
}
inline int dfs(int u, int flow){
if(u == T || !flow) return flow;
int used = 0;
for(int &p = cur[u]; p; p = nxt[p]){
int v = a[p];
if(dis[v] != dis[u] + 1 || !cap[p]) continue;
int x = dfs(v, min(flow, cap[p]));
used += x, flow -= x, cap[p] -= x, cap[p^1] += x;
if(!flow) break;
}
return used;
}
inline void setflow(int x, int y){ S = x, T = y; }
inline int getflow(){
int res = 0;
for(; bfs(); res += dfs(S, inf))
for(int i = 1; i <= n + 4; i++) cur[i] = head[i];
return res;
}
}
namespace task2{
int NS, NT, S, T;
inline void init(){
NS = n + 1, NT = n + 2, S = n + 3, T = n + 4;
}
inline void add(int x, int y, int a, int b){
flow::addedge(NS, y, a);
flow::addedge(x, NT, a);
flow::addedge(x, y, b - a);
}
inline void solve(){
for(int i = 1; i <= n; i++){
add(S, i, 0, inf);
add(i, T, 0, inf);
}
flow::setflow(NS, NT);
flow::getflow();
flow::addedge(T, S, inf);
cout << flow::getflow() << endl;
} }
namespace task1{
int f[N], ff[N], st[N], g[N], id[N], gg[N], pr[N], spec[N], ans, top;
inline void chkmax(int &x, int y){ if(y > x) x = y; }
inline bool cmp(int x, int y){
return ay[x] == ay[y] ? ax[x] > ax[y] : ay[x] < ay[y];
}
inline void trans(int x){
for(int i = 0; i < (int) ed[x].size(); i++)
if(!spec[ed[x][i]] && f[x] + 1 > f[ed[x][i]])
f[ed[x][i]] = f[x] + 1, pr[ed[x][i]] = x;
}
inline void trans2(int x){
for(int i = 0; i < (int) fx[x].size(); i++)
if(!spec[fx[x][i]]) chkmax(ff[fx[x][i]], ff[x] + 1);
}
inline void solve(){
for(int i = 1; i <= n; i++) id[i] = i;
for(int i = 1; i <= n; i++) f[i] = g[i] = -inf, ff[i] = 1;
f[n] = ff[n] = 0;
sort(id + 1, id + n + 1, cmp);
for(int i = 1, last; i <= n; i = last + 1){
last = i;
while(last < n && ay[id[last+1]] == ay[id[i]]) last++;
for(int j = i; j <= last; j++) spec[id[j]] = 1;
int Left = -inf, Right = -inf, posL = 0, posR = 0;
for(int j = i; j <= last; j++){
if(Left != -inf) g[id[j]] = Left + j - i, gg[id[j]] = posL;
if(f[id[j]] > Left) Left = f[id[j]], posL = id[j];
}
for(int j = last; j >= i; j--){
if(Right != -inf && Right + last - j > g[id[j]])
g[id[j]] = Right + last - j, gg[id[j]] = posR;
if(f[id[j]] > Right) Right = f[id[j]], posR = id[j];
}
for(int j = i; j <= last; j++){
if(g[id[j]] > f[id[j]]) f[id[j]] = g[id[j]], fuck[id[j]] = 1;
if(f[id[j]] != -inf) trans(id[j]);
}
for(int j = i; j <= last; j++) spec[id[j]] = 0;
}
for(int i = 1; i <= n; i++) ans = max(ans, f[i]);
int pos = n;
for(int i = 1; i <= n; i++) if(f[i] == ans) pos = i;
while(pos != n){
if(fuck[pos]){
int l = lower_bound(v1[D(ay[pos])].begin(), v1[D(ay[pos])].end(), make_pair(ax[pos], pos)) - v1[D(ay[pos])].begin();
int r = lower_bound(v1[D(ay[pos])].begin(), v1[D(ay[pos])].end(), make_pair(ax[gg[pos]], gg[pos])) - v1[D(ay[pos])].begin();
if(l < r){
for(int i = l; i < r; i++) st[++top] = v1[D(ay[pos])][i].se;
for(int i = (int) v1[D(ay[pos])].size() - 1; i > r; i--) st[++top] = v1[D(ay[pos])][i].se;
}
else{
for(int i = l; i > r; i--) st[++top] = v1[D(ay[pos])][i].se;
for(int i = 0; i < r; i++) st[++top] = v1[D(ay[pos])][i].se;
}
pos = gg[pos], fuck[pos] = 0;
}
else st[++top] = pos, pos = pr[pos];
}
printf("%d\n", ans);
for(int i = top; i >= 1; i--) printf("%d ", st[i]);
puts("");
reverse(id + 1, id + n + 1);
for(int i = 1; i <= n; i++) g[i] = -inf;
for(int i = 1, last; i <= n; i = last + 1){
last = i;
while(last < n && ay[id[last+1]] == ay[id[i]]) last++;
for(int j = i; j <= last; j++) spec[id[j]] = 1;
int Left = -inf, Right = -inf;
for(int j = i; j <= last; j++){
g[id[j]] = Left + last;
chkmax(Left, ff[id[j]] - j);
}
for(int j = last; j >= i; j--){
g[id[j]] = Max(g[id[j]], Right - i);
chkmax(Right, ff[id[j]] + j);
}
for(int j = i; j <= last; j++){
ff[id[j]] = Max(ff[id[j]], g[id[j]]);
if(ff[id[j]] != -inf) trans2(id[j]);
}
for(int j = i; j <= last; j++) spec[id[j]] = 0;
}
for(int i = 1; i <= n; i++)
for(int j = 0; j < (int) ed[i].size(); j++){
int v = ed[i][j];
if(ay[v] == ay[i] || v == n) continue;
if(f[i] + ff[v] == ans)
task2::add(i, v, 1, inf);
} }
}
inline void linkpre(vector<pair<int, int> > &A, pair<int, int> x){
vector<pair<int, int> >::iterator it;
it = lower_bound(A.begin(), A.end(), x);
if(it != A.begin()) --it, ed[x.se].push_back(it->se);
}
inline void linknxt(vector<pair<int, int> > &A, pair<int, int> x){
vector<pair<int, int> >::iterator it;
it = upper_bound(A.begin(), A.end(), x);
if(it != A.end()) ed[x.se].push_back(it->se);
}
int main(){
read(n), s[++len] = 0, ++n;
for(int i = 1; i < n; i++){
read(ax[i]), read(ay[i]);
s[++len] = ax[i], s[++len] = ay[i];
s[++len] = ax[i] + ay[i], s[++len] = ax[i] - ay[i];
}
sort(s + 1, s + len + 1);
len = unique(s + 1, s + len + 1) - s - 1;
for(int i = 1; i <= n; i++){
v1[D(ay[i])].push_back(make_pair(ax[i], i));
v2[D(ax[i])].push_back(make_pair(ay[i], i));
v3[D(ax[i]-ay[i])].push_back(make_pair(ay[i], i));
v4[D(ax[i]+ay[i])].push_back(make_pair(ay[i], i));
}
for(int i = 1; i <= len; i++){
sort(v1[i].begin(), v1[i].end());
sort(v2[i].begin(), v2[i].end());
sort(v3[i].begin(), v3[i].end());
sort(v4[i].begin(), v4[i].end());
}
for(int i = 1; i <= n; i++){
linkpre(v1[D(ay[i])], make_pair(ax[i], i));
linknxt(v1[D(ay[i])], make_pair(ax[i], i));
linknxt(v2[D(ax[i])], make_pair(ay[i], i));
linknxt(v3[D(ax[i]-ay[i])], make_pair(ay[i], i));
linknxt(v4[D(ax[i]+ay[i])], make_pair(ay[i], i));
}
for(int i = 1; i <= n; i++)
for(int j = 0; j < (int) ed[i].size(); j++)
fx[ed[i][j]].push_back(i);
task2::init();
task1::solve();
task2::solve();
return 0;
}

「NOI2015」小园丁与老司机的更多相关文章

  1. *LOJ#2134. 「NOI2015」小园丁与老司机

    $n \leq 5e4$个平面上的点,从原点出发,能从当前点向左.右.上.左上或右上到达该方向最近的给定点.问三个问:一.最多经过多少点:二.前一问的方案:三.其所有方案种非左右走的边至少要开几辆挖掘 ...

  2. 【BZOJ4200】【NOI2015】小园丁与老司机(动态规划,网络流)

    [BZOJ4200][NOI2015]小园丁与老司机(动态规划,网络流) 题面 BZOJ权限题,洛谷链接 题解 一道二合一的题目 考虑第一问. 先考虑如何计算六个方向上的第一个点. 左右上很好考虑,只 ...

  3. UOJ132 【NOI2015】小园丁与老司机

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  4. [BZOJ4200][Noi2015]小园丁与老司机

    4200: [Noi2015]小园丁与老司机 Time Limit: 20 Sec  Memory Limit: 512 MBSec  Special JudgeSubmit: 106  Solved ...

  5. [UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机

    [UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机 试题描述 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 \(n\) 棵许愿 ...

  6. 【BZOJ4200】[Noi2015]小园丁与老司机 DP+最小流

    [BZOJ2839][Noi2015]小园丁与老司机 Description 小园丁 Mr. S 负责看管一片田野,田野可以看作一个二维平面.田野上有 nn 棵许愿树,编号 1,2,3,…,n1,2, ...

  7. luogu P2304 [NOI2015]小园丁与老司机 dp 上下界网络流

    LINK:小园丁与老司机 苦心人 天不负 卧薪尝胆 三千越甲可吞吴 AC的刹那 真的是泪目啊 很久以前就写了 当时记得特别清楚 写到肚子疼.. 调到胳膊疼.. ex到根不不想看的程度. 当时wa了 一 ...

  8. [LOJ 2134][UOJ 132][BZOJ 4200][NOI 2015]小园丁与老司机

    [LOJ 2134][UOJ 132][BZOJ 4200][NOI 2015]小园丁与老司机 题意 给定平面上的 \(n\) 个整点 \((x_i,y_i)\), 一共有两个问题. 第一个问题是从原 ...

  9. 【洛谷2304_LOJ2134】[NOI2015]小园丁与老司机(动态规划_网络流)

    题目: 洛谷 2304 LOJ 2134 (LOJ 上每个测试点有部分分) 写了快一天 -- 好菜啊 分析: 毒瘤二合一题 -- 注意本题(及本文)使用 \(x\) 向右,\(y\) 向上的「数学坐标 ...

随机推荐

  1. vue通过Blob实现下载文件

    需求是这样的...... 具体实现,前端拿到后端返回回来的数据,然后通过Blob实现下载,文件内容样式啥的都是后端写的 script代码: 这里的data就是后端返回回来的数据,此方法兼容IE dow ...

  2. Maven项目配置Logback输出JSON格式日志

    最近,项目提出需求,日志需要固定输出为JSON格式,以便后端Flink程序解析. 项目背景 项目为简单的Maven项目,日志由Filebeat采集,因此不需要配置输出至Logstash. 下面为pom ...

  3. 6.redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?

    作者:中华石杉 面试题 redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的? 面试官心理分析 redis 如果仅仅只是将数据缓存在内存里面,如果 redi ...

  4. nginx 配置相关解析

    nginx模块处理流程一般是这样的: 客户端发送HTTP请求 –> Nginx基于配置文件中的位置选择一个合适的处理模块 ->(如果有)负载均衡模块选择一台后端服务器 –> 处理模块 ...

  5. javascript之DOM(二Document对象)

    javascript通过Document类型来表示文档.在浏览器中document是HTMLDocument对象(继承自Document)的一个实例,表示整个html页面.而且在浏览器中documen ...

  6. 《linux就该这么学》课堂笔记13 网络会话、ssh、远程会话

    1.常见的网卡绑定驱动有三种模式—mode0.mode1和mode6 mode0(平衡负载模式):平时两块网卡均工作,且自动备援,但需要在与服务器本地网卡相连的交换机设备上进行端口聚合来支持绑定技术. ...

  7. 基于KVM的虚拟机创建

    KVM基本介绍:   KVM是Kernel-based Virtual Machine的简称,是一个开源的系统虚拟化模块,自Linux 2.6.20之后集成在Linux的各个主要发行版本中,KVM目前 ...

  8. php观察者模式(observer pattern)

    ... <?php /* The observer pattern implements a one-too-many dependency between objects. The objec ...

  9. update mantis_bug_table

    update mantis_bug_table set mantis_bug_table.original_due_date=mantis_bug_table.due_date ,) ,);

  10. 三星固态Dell版的960g的sm863a硬盘

    smart参数 CrystalDiskMark测试 AS SSD 测试 HD Tune Pro测试 DiskGenius查看 总结: 按我的测试,性能比sm865的还好,不知道咋回事,按三星给的参数这 ...