「Gym102759L」Steel Slicing 2 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17261967.html ,转载请注明出处。
传送门
题目大意
给定一个只有水平边和竖直边的铁片,每次可以水平或竖直方向把一个铁片分成两个部分。注意只能是两个部分。求最少切多少刀才能使得每一个部分都是长方形。
铁片形状如下图,以输入 \(h,l\) 的方式给出。
思路
手玩几组数据之后,我们可以发现,一个长方形没有向内凹的角,所以我们需要把向内凹的角全部删除。
为了方便,下面我们将向内凹的角称作凹点,\(h\) 称作上方高度,\(l\) 称作下方高度。
下图中圈出的角都是凹点:
可以发现如果我们的切割路径经过了凹点,那么这个凹点就会消失。
因为我们要切割次数尽可能小,所以我们每次的切割经过的凹点要尽可能地多。题目要求只能切成两个部分,所以一次最多消除 \(2\) 个凹点。
考虑先预处理出消除 \(2\) 个凹点的方案。
对于横切,我们对上下高度分别处理。由于上下高度处理方式相同,我们只考虑上方高度。
因为我们只能分成两个部分,所以如果可以消除 \(2\) 个凹点 \(i,j\),那么一定满足 \(h_i = h_j , i + 1 < j , \forall k \in (i,j) , h_k > h_i\)。
所以只需要用一个长度单调递减栈,每次对于栈顶对应位置 \(i\) 和当前位置 \(j\),判断是否满足上方条件。如果满足条件,记录下切的位置。
然后我们再看竖切。发现只要相邻两个位置的上下长度都不相等,那么就可以消除 \(2\) 个凹点。
我们把能消除 \(2\) 个凹点的竖切方案也记录下来。
我们不难想到,如果把这些切割路径看作点,相交的路径代表的点之间连边,因为只有两类点,边不会连接同类点,它会形成一个二分图。
然后因为切割路径不能相交,我们求的就是最大独立集。
根据某定理,最大独立集 = 点数 - 最大匹配,所以我们只需要求最大匹配。
最大匹配可以在求解竖切时直接贪心。
具体来说,每次把当前位置的横切方案的右端点加入一个小根堆,把与当前位置的竖切方案不相交的弹出。如果这时堆不为空,那么表示有与当前竖切方案相交的横切方案。取堆顶的与当前竖切方案匹配。
因为最大独立集中的点都是能消除 \(2\) 个凹点的切割路径,所以最后用凹点个数减去最大独立集就是答案。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N];
ll b[N];
vector<ll>e[N];//边
ll cnt;//总的可以选择的刀数
ll match;//匹配的数量
ll top,s[N];//单调栈
void add(ll a[]){//算横切的方案
top=1,s[1]=1;//将1入栈
For(i,2,n){
while(top&&a[s[top]]>a[i])top--;//大于当前长度就弹出
if(top&&a[s[top]]==a[i]&&s[top]<=i-2){//等长并且不相邻(相邻不会构成凹点)
e[s[top--]].pb(i-1);//记录切的位置,这里左闭右开
cnt++;//统计可选刀数
}
s[++top]=i;//入栈
}
}
priority_queue<ll,vector<ll>,greater<ll>>q;//小根堆
void mian(){
scanf("%lld",&n);
For(i,1,n){
scanf("%lld",&a[i]);
scanf("%lld",&b[i]);
}
ll ans=0;//凹点总数
add(a),add(b);//算横切的方案
For(i,1,n-1){
for(ll x:e[i])q.push(x);//将相连的点加入堆
ll res=(a[i]!=a[i+1])+(b[i]!=b[i+1]);//竖切消除的凹点数量
ans+=res;//统计凹点总数
if(res==2){//横切消除的数量至少为1,竖切更优只可能是消除两个
cnt++;//统计可选刀数
while(q.size()&&q.top()<i)q.pop();//去除不相交的边
if(q.size()){//有相交的边
match++;//匹配数+1
q.pop();
}
}
}
printf("%lld",ans-(cnt-match));//凹点个数-最大独立集
}
int main(){
int T=1;
// scanf("%d",&T);
while(T--)mian();
return 0;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!