汉堡肉
你听说过奇异物品公司(Just Odd inventions, Ltd.)吗?这家公司以生产奇异物品出名。在本题中,我们简称它为 JOI 公司。
JOI 公司将要举办一场盛大的年会,主厨正在一块由 (10^9 imes 10^9) 个网格组成的超大的网格形烤架上烤着 (N) 块汉堡肉。为了方便,我们用 ((x,~y)) 表示从左往右数第 (x) 列,从下往上数第 (y) 行的网格。
我们将汉堡肉编号为 (1ldots N),其中第 (i) 块汉堡覆盖了左下角 ((L_i,~D_i)) 到右上角 ((R_i,~U_i)) 的矩形区域,注意这些区域可能重叠。
你是 JOI 公司的萌新,现在你有 (K) 根竹签,你只要把竹签插到一个格子里就可以知道那格的肉有没有熟,你的任务是检查所有的肉是否已经煮熟。你可以把竹签插到一个没有肉的格子里,也可以把几根竹签插到同一格里。
形式化地说,你的任务是寻找一个由 (K) 个二元组 ((x_1,y_1),ldots,(x_n,y_n)) 组成的 (K) 元组(其中元素可以重复),使得:
- 对于任意 (iin[1,N]),存在 (j) 使得 (L_ile x_jle R_i) 且 (D_ile y_jle U_i) 成立。
- 对于任意 (jin[1,K]),有 (1le x_j,~y_j le 10^9)。
编写一个程序,给出汉堡肉覆盖的区域和竹签的数量,找出一个插竹签的方法。数据保证有解。
题解
https://blog.csdn.net/zxyoi_dreamer/article/details/105251535
分类讨论
求出左边界最大值(max L),右边界最小值(min R),上边界最小值(min U)和下边界最大值(max D)。接下来的分析会说明我们为什么要观察这四个值。
-
(max Lleq min R,max Dleq min U)
这时所有矩形都包含了((max L,max D,min R,min U))这个子矩形。那么往这个矩形中随便丢一根竹签就行了。
-
(max L> min R,max Dleq min U)(以及跟它对称的另一种情况)
注意到(max L)右侧一定有一根竹签,(min R)左侧一定有一根竹签,且这两根竹签一定不同。那么在(x=max L~(max Dleq yleq min U))和(x=min R~(max Dleq yleq min U))上分别插一根竹签,然后DFS递归剩余的中间部分即可。
-
(max L> min R,max D> min U)
同样的,(max L)右侧一定有一根竹签,(min R)左侧一定有一根竹签,(max D)上方一定有一根竹签,(min U)下方也一定有一根竹签。
显然若存在不用在((min R,min U,max L,max D))这个矩形内部插竹签的解,它们都可以通过平移走到这四条线段上。
-
当(Kleq 3)时,由抽屉原理可知必然有至少一个点在这几条线的交点处,暴力DFS即可。
-
当(K=4)时,我们还是选择先暴力DFS。如果没有找到答案,说明四根竹签分别在这四条线段上,且不会重合。这就是不好一眼看出解的最坏情况。
-
最坏情况
接下来我们会说明一块汉堡肉最多会影响两条线段上的竹签选择情况。
由于题目保证有解,所以不存在完全在((min R,min U,max L,max D))这个矩形内部的汉堡肉。
如果某块汉堡肉与这四条线段所在直线中的至少三条相交,说明它完全包含了某条线段。此时它一定会被插中,所以我们可以不考虑它。
剩下的矩形都与这四条线段所在直线中的一条或者两条相交。
-
如果只与一条相交,则该线段的上的竹签必然存在于某个确定的区间中。
-
否则如果与两条直线相交,我们可以将这个转化为2-SAT问题。
设这两条线上的点未确定的坐标为(x_1,x_2),则要求某一个矩形包含至少一个点可以转化为(x_1in [l_1,r_1]vee x_2in [l_2,r_2])。对于每个直线上的限制,离散化后前缀连边即可。
前缀连边的方法——变量(z_i)表示([1,i])中是否有竹签。
考虑怎么判断某块汉堡肉的有解。对于这块汉堡肉,建立两个变量(y_1,y_2),分别表示它在两条直线上的限制是否满足。要求(y_1vee y_2=1)。由于题目保证有解,所以我们实际上不用建立这两个变量。
CO int N=2e6+10,inf=1e9;
struct node {int L,R,D,U;};
vector<node> go(CO vector<node>&rec,int x,int y){
vector<node> ans;
for(CO node&a:rec)
if(a.L>x or a.R<x or a.D>y or a.U<y)
ans.push_back(a);
return ans;
}
bool dfs(CO vector<node>&rec,int K){
if(rec.empty()){
while(K--) puts("0 0");
return 1;
}
if(!K) return 0;
int L=1,R=inf,D=1,U=inf;
for(CO node&a:rec)
chkmax(L,a.L),chkmin(R,a.R),chkmax(D,a.D),chkmin(U,a.U);
if(dfs(go(rec,L,D),K-1)){
printf("%d %d
",L,D);
return 1;
}
if(dfs(go(rec,L,U),K-1)){
printf("%d %d
",L,U);
return 1;
}
if(dfs(go(rec,R,D),K-1)){
printf("%d %d
",R,D);
return 1;
}
if(dfs(go(rec,R,U),K-1)){
printf("%d %d
",R,U);
return 1;
}
return 0;
}
node rec[N];
int id[N][2],num; // {id[i][0],id[i][1]}={i<<1,i<<1|1}
vector<int> to[N];
int pos[N],low[N],dfn;
int stk[N],top,ins[N];
int scc[N],idx;
void tarjan(int u){
pos[u]=low[u]=++dfn;
stk[++top]=u,ins[u]=1;
for(int v:to[u]){
if(!pos[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]) low[u]=min(low[u],pos[v]);
}
if(low[u]==pos[u]){
++idx;
do scc[stk[top]]=idx,ins[stk[top]]=0;
while(stk[top--]!=u);
}
}
int main(){
int n=read<int>(),K=read<int>();
for(int i=1;i<=n;++i)
read(rec[i].L),read(rec[i].D),read(rec[i].R),read(rec[i].U);
if(dfs(vector<node>(rec+1,rec+n+1),K)) return 0;
int L=1,R=inf,D=1,U=inf;
for(int i=1;i<=n;++i)
chkmax(L,rec[i].L),chkmin(R,rec[i].R),chkmax(D,rec[i].D),chkmin(U,rec[i].U);
// {L,R,D,U} = {0,1,2,3}
for(int i=1;i<=n;++i){
int f[4]={rec[i].R>=L,rec[i].L<=R,rec[i].U>=D,rec[i].D<=U};
if(f[0]+f[1]+f[2]+f[3]>2){
id[i][0]=id[i][1]=-1;
continue;
}
int c=-1;
for(int t=0;t<4;++t)if(f[t])
id[i][++c]=t;
if(c==0)
id[i][1]=-1,to[2*i+1].push_back(2*i);
}
num=2*n+1;
for(int t=0;t<4;++t){
vector<int> P;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > Q;
for(int i=1;i<=n;++i)
if(id[i][0]==t or id[i][1]==t)
P.push_back(i);
sort(P.begin(),P.end(),[&](int i,int j)->bool{
return t<2?rec[i].D<rec[j].D:rec[i].L<rec[j].L;
});
int lst=-1,ilst=-1; // last poped prefix r
for(int i:P){
Q.push({t<2?rec[i].U:rec[i].R,i});
while(Q.top().first<(t<2?rec[i].D:rec[i].L)){
int j=Q.top().second;Q.pop();
int c=id[j][1]==t;
if(lst==-1){ // j = prefix 1
lst=j<<1|c,ilst=j<<1|!c;
continue;
}
to[j<<1|c].push_back(++num); // j -> prefix r
to[lst].push_back(num); // prefix r-1 -> prefix r
to[++num].push_back(j<<1|!c); // !(prefix r) -> !j
to[num].push_back(ilst); // !(prefix r) -> !(prefix r-1)
lst=num-1,ilst=num;
}
if(lst!=-1){
int c=id[i][1]==t;
to[i<<1|c].push_back(ilst); // i -> !(prefix l-1)
to[lst].push_back(i<<1|!c); // prefix l-1 -> !i
}
}
}
for(int u=2;u<=num;++u)
if(!pos[u]) tarjan(u);
int ans[4]={};
for(int i=1;i<=n;++i){
int t=id[i][scc[i<<1]>scc[i<<1|1]];
chkmax(ans[t],t<2?rec[i].D:rec[i].L);
}
printf("%d %d
",L,ans[0]);
printf("%d %d
",R,ans[1]);
printf("%d %d
",ans[2],D);
printf("%d %d
",ans[3],U);
return 0;
}