【XSY3899】切割(思维,模拟二分图匹配)
题面
题解
考虑这么一个边都和坐标轴平行的不规则图形,经过水平或竖直切割后,如何判断切割后的图形是个矩形。
容易发现,如果切出来后的图形没有凹进去的点,它就是一个矩形。
那么假设一开始有 \(ao\) 个凹点,那么你的目标就是让凹点的个数变为 \(0\)。
然后对于一种切法,如果它不经过凹点,我们肯定可以调整这种切法使得它经过凹点,可以证明这样不会更劣。
那么你只需要最多切 \(ao\) 次就可以把这个不规则图形切成一些矩形了。
现在你的目标是每一次切尽量减少多一点凹点,也就是说尽量让每一切都能切到两个凹点。
我们考虑在原来的不规则图形上把所有的一次能多切一个凹点(即能切到两个凹点)的切法给预处理出来。
对于竖着切的预处理很简单,而对于横着切的我们可以用单调栈来预处理。
但是你注意到有时候两种切法不能全部满足,即两种切法相交的时候:
如图,四个圆圈代表着四个凹点的位置,那么显然第一步横着切或竖着切都能多切一个凹点,但剩下的两个凹点就不能一步切完了。
注意到横着切之间是不会相交的,竖着切之间也是不会相交的。
考虑把每种切法用点来代表,然后如果两种切法相交了,我们就在它们之间连一条边,显然这是一个二分图的形式。
我们原本的要求是选最多的切法,使得这些切法之间不相交,那么这些切法每一种多切一个凹点,而这些切法以外的其他切法就只能切一个凹点。
转换到图上,就是说我们要选最多的点,使得这些点之间没有连边。那么这就是一个二分图最大独立集问题。
二分图最大独立集可以用最小割来解决,但即使用 dinic 跑时间复杂度也是 \(O(m\sqrt n)\) 的,不能接受。
考虑模拟:
考虑将竖着切的点按竖着切的位置从左到右排序,那么一个横着切的点连出去的边肯定是竖着切的点的一段区间。
也就是二分图左边一排点中每个点向右边的一排点的一个区间的点连边。
那这显然是可以贪心的。你想到的应该不会是线段树优化建图吧
从左到右考虑每一个竖着切的点向哪一个横着切的点匹配。
把所有满足 其区间包含当前这个竖着切的点 的横着切的点的区间的右端点 \(r_i\) 加入优先队列中,那么显然每次取优先队列中 \(r_i\) 最小的来匹配是最优的,因为 \(r_i\) 小的区间在后面匹配的机会就更少。
直接维护即可。
代码如下:
#include<bits/stdc++.h>
#define N 500010
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
struct data
{
int l,r;
data(){};
data(int a,int b){l=a,r=b;}
};
bool operator < (data a,data b)
{
if(a.l==b.l) return a.r<b.r;
return a.l<b.l;
}
int n,u[N],d[N];
int top,sta[N];
int ao,p,match;
bool line[N];
vector<data>row;
priority_queue<int,vector<int>,greater<int> >q;
int main()
{
n=read();
for(int i=1;i<=n;i++) u[i]=read(),d[i]=read();
for(int i=1;i<n;i++)
{
if(u[i]!=u[i+1]) ao++;
if(d[i]!=d[i+1]) ao++;
if(u[i]!=u[i+1]&&d[i]!=d[i+1]) line[i]=1,p++;
}
for(int i=1;i<=n;i++)
{
while(top&&u[sta[top]]>u[i]) top--;
if(top&&u[sta[top]]==u[i])
{
if(sta[top]!=i-1)
{
p++;
row.push_back(data(sta[top]+1,i));
}
top--;
}
sta[++top]=i;
}
top=0;
for(int i=1;i<=n;i++)
{
while(top&&d[sta[top]]>d[i]) top--;
if(top&&d[sta[top]]==d[i])
{
if(sta[top]!=i-1)
{
p++;
row.push_back(data(sta[top]+1,i));
}
top--;
}
sta[++top]=i;
}
sort(row.begin(),row.end());
int tmp=0;
for(int i=1;i<n;i++)
{
if(line[i])
{
while(tmp<row.size()&&row[tmp].l<=i+1)
{
q.push(row[tmp].r);
tmp++;
}
while(!q.empty()&&q.top()<=i) q.pop();
if(!q.empty())
{
q.pop();
match++;
}
}
}
printf("%d\n",ao-(p-match));
return 0;
}
/*
5
5 3
3 1
1 5
2 2
5 3
*/
/*
5
2 1
1 2
4 2
1 3
1 1
*/