[CF1242E] Planar Perimeter[题解]
Planar Perimeter
题意
有 \(n\) 个多边形(不一定凸),要求第 \(i\) 个多边形有 \(a_i\) 条边,现在你要将多边形拼接起来变成一个大多边形,要求:
-
多边形两两交集面积为零。
-
每个多边形的边要么与另一个多边形的边重合,要么是大多边形的外围边。
求大多边形边数最小值,并构造一种方案(输出哪些顶点重合了)。
\(1\leq n\leq 10^5,\sum a_i \leq 3\times 10^5\)
分析
考虑将 \(a_i\) 降序排列,考虑最大的一个面 \(a_1\)。如果 \(a_1 - \sum_{i=2}^n(a_i - 2)\geq3\),则最终的边长即为 \(a_1 - \sum_{i=2}^n(a_i - 2)\),一个新加的面最多让周长减少 \(a_i - 2\),这是比较显然的。但我们不可能存在边数小于 \(3\) 的多边形,所以,在某个时刻我们也许不能再让多边形的边数减小,相反,我们甚至有可能使它增加,于是我们只要在构造到第 \(i\) 个面时满足 \(C \ge a_{i+1}\) 的条件下尽可能让周长变小,我们最后的答案就是 \(4 - (\sum_{i = 1}^n a_i)\) \(mod\) \(2\),具体的实现我们可以用双端队列维护当前外面的边。
还有最后一个问题,我们构造出的图形可能存在重边,具体地,即我们在多边形的一条边上同时添加了两个三角形,我们可以在每次加入多边形后右移一位,从下一条边开始添加,就能有效避免这个问题,正确性是显然的。
CODE
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
inline int read()
{
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
return s * w;
}
struct node{ int v, id; };
int n, cnt;
inline bool cmp(node x, node y) { return x.v > y.v; }
vector<node> arr;
vector<int> vec[N];
int main()
{
n = read();
for(register int i = 0; i < n; i++){
int x = read();
arr.push_back((node){x, i});
}
sort(arr.begin(), arr.end(), cmp);
deque<int> q;
for(register int i = 0; i < arr[0].v; i++){
q.push_back(cnt), vec[arr[0].id].push_back(cnt);
cnt++;
}
for(register int i = 1; i < n; i++){
int x = arr[i].v, y = arr[i].id;
int p = min(x - 1, ((int)q.size() + x - (i == n - 1 ? 3 : arr[i + 1].v)) / 2);
for(register int j = 0; j < p - 1; j++)
vec[y].push_back(q.back()), q.pop_back();
vec[y].push_back(q.back());
for(register int j = p; j < x - 1; j++)
vec[y].push_back(cnt), q.push_back(cnt), cnt++;
vec[y].push_back(q.front());
q.push_back(q.front()), q.pop_front();
}
cout << cnt << "\n";
for(register int i = 0; i < n; i++){
for(register int j : vec[i])
cout << j + 1 << " ";
cout << "\n";
}
return 0;
}