#loj6032. 「雅礼集训 2017 Day2」水箱
作者:互联网
#loj6032. 「雅礼集训 2017 Day2」水箱
题目描述
给出一个长度为 \(n\) 宽度为 \(1\) ,高度无限的水箱,有 \(n - 1\) 个挡板将其分为 \(n\) 个 \(1 - 1\) 的小格,然后向每个小格中注水,水如果超过挡板就会溢出到挡板的另一边,这里的水是满足物理定律的(在无挡板阻拦的情况下会向低处流),现在有 \(m\) 个条件 \((i, y, k)\) ,表示从左到右数的第 \(i\) 个格子中,在高度为 \(y + 0.5\) 的地方是否有水,\(k = 1\) 表示有水,\(k = 0\) 表示没有水,请求出这 \(m\) 个条件最多能同时满足多少个条件。本题有多组数据。
输入格式
第一行一个正整数 \(T\),为数据组数。
第二行两个正整数 \(n\)、\(m\),中间用空格隔开。
接下来一行 \(n - 1\) 个整数,表示从左到右每一块隔板的高度。
接下来 \(m\) 行,每行三个整数 \(i\)、\(y\)、\(k\),表示一个条件。
输出格式
共 \(T\) 行,每行对应一组数据的答案。
输入数据 1
2
3 4
3 4
1 3 1
2 1 0
2 2 0
3 3 1
2 2
2
1 2 0
1 2 1
输出数据 1
3
1
数据范围与提示
对于 \(20\%\) 的数据,\(n, m \leq 16\);
对于另外 \(10\%\) 的数据,只存在指明某处有水的条件;
对于另外 \(30\%\) 的数据,\(n, m \leq 1000\);
对于 \(100\%\) 的数据,\(n, m \leq 10 ^ 5, T \leq 5\) 。
Solution
这道题是道好题,做法有很多种。博客上有人提供了平衡树启发式合并的做法,也有用线段树优化 \(\text{DP}\) 的做法,但其实还有另一种类似扫描线的做法,可能时间复杂度上更优。
输入的时候将所有挡板的 \(x,y\) 记录下来,并且用 \(type=-1\) 进行标记,然后记录所有条件,无水用 \(type=0\) 标记,有水用 \(type=1\) 标记,然后对所有的坐标按照 \(y\) 为关键字从小到大排序。
然后就可以从下至上用扫描线一路扫上去,显然这样的耗时是 \(\text O(n+m)\) 的。现在就是讨论如何记录答案。根据 \(type\) 的值分成三种情况讨论:
type=-1
此时的 \(y\) 表示的是挡板的高度,而扫描线扫到当前高度表示水已经可以漫过当前挡板,因此需要将挡板左右的两个区间直接合并,合并操作可以直接用并查集实现。对于每一个区间,可以记录两个信息, \(ans[i]\) 表示当前区间的最优答案, \(f[i]\) 表示当前区间 \(type=1\) 的条件的个数。那么在合并区间的时候,需要将被合并区间的 \(ans\) 和 \(f\) 更新到总区间中,并且尝试用 \(ans\) 去更新答案(如果可以)。
type=0
此时的 \(y\) 表示的是没有水的地方的高度,显然既然没有水,那么它哪里都不会影响,所以它只会对 \(ans\) 贡献一个 \(+1\) ,并尝试更新答案。
type=1
此时的 \(y\) 表示的有水的地方的高度,因为有水,而且比它低的位置的 \(type=0\) 的位置在之前已经讨论过了(因为排了序),所以在 \(y\) 的地方放水会导致 \(y\) 所在的区间全部被水淹掉,此时满足的条件数量全部来自于此区间中的 \(type=1\) 的条件,即 \(f\) 。因此,需要用 \(f\) 去尝试更新 \(ans\) 并用 \(ans\) 尝试更新答案。
其实看整个算法流程,很像树形 \(\text{DP}\) 。因为这样的扫描线相当于是对所有挡板隔成的区间建了一棵二叉树,然后从叶子结点向上按深度逐层扫描上去,最后将答案合并统计在根节点上。区间由线段组成,所以我们就叫它线段树形区间 \(\text{DP}\) 吧。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<limits.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
template<typename T> void read(T &k)
{
k=0;
T flag=1;char b=getchar();
while (b<'0' || b>'9') {flag=(b=='-')?-1:1;b=getchar();}
while (b>='0' && b<='9') {k=(k<<3)+(k<<1)+(b^48);b=getchar();}
k*=flag;
}
const int _SIZE=1e5;
int T,n,m;
struct EVENT{
int type,x,y;
}point[(_SIZE<<1)+5];
int tot;
int fa[_SIZE+5],f[_SIZE+5],ans[_SIZE+5],res;
bool cmp(EVENT a,EVENT b)
{
if (a.y!=b.y) return a.y<b.y;
if (a.type!=b.type) return a.type<b.type;
return a.x<b.x;
}
int find(int x)
{
if (fa[x]) return fa[x]=find(fa[x]);
return x;
}
void solve()
{
tot=0;mem(fa,0);mem(f,0);mem(ans,0);mem(point,0);
read(n),read(m);
for (int i=1;i<n;i++)
{
read(point[++tot].y);
point[tot].type=-1;
point[tot].x=i;
}
for (int i=1;i<=m;i++)
{
read(point[++tot].x);
read(point[tot].y);
read(point[tot].type);
}
sort(point+1,point+tot+1,cmp);
//system("pause");
res=0;
for (int i=1;i<=tot;i++)
{
int type=point[i].type,x=point[i].x;
if (type==-1)
{
int f_1=find(x),f_2=find(x+1);
fa[f_2]=f_1;
f[f_1]+=f[f_2];
ans[f_1]+=ans[f_2];
res=max(ans[f_1],res);
}
else if (!type)
res=max(res,++ans[find(x)]);
else
{
int f_=find(x);
++f[f_];
ans[f_]=max(ans[f_],f[f_]);
res=max(res,ans[f_]);
}
}
printf("%d\n",res);
}
int main()
{
read(T);
while (T--)
solve();
return 0;
}
我到底该把这道题放在什么标签下啊……
标签:loj6032,Day2,雅礼,扫描线,ans,区间,include,type,挡板 来源: https://www.cnblogs.com/hanx16msgr/p/16474216.html