Kadj Squares
Kadj Squares
(投影法,染色法,坐标变换)
一、问题转化
正方形边与坐标轴成 45° 放置,从正上方(竖直向下)观察。
经过旋转 45° + 均匀缩放的仿射变换后:
- 每个正方形变为轴对齐矩形;
- 其右下角坐标为
(b[i], b[i]),边长为s[i](变换后的边长); - 原始竖直向下的视线方向变为沿
(-1, 1)方向,但本题通过投影法进一步简化:- 由于所有正方形的右下角都在直线
y = x上,且按b递增放置,可见性最终等价于 x 轴上的投影是否被后续正方形完全覆盖。
- 由于所有正方形的右下角都在直线
二、坐标预处理(乘以 2)
原始坐标中可能出现半整数(例如 b[1] = s1/2)。
为避免浮点数,将所有边长乘以 2:
- 令
s[i] = 原边长 * 2; - 此时
b[1] = s[1](因为s1/2 * 2 = s1); - 所有后续计算均为整数。
乘以 2 的另一个好处:闭区间端点不再出现 .5,边界点的归属更清晰,减少冲突。
三、计算每个正方形的 b[i](投影法)
在变换后的坐标系中,正方形 i 在 x 轴上的投影区间为
[b[i] - s[i], b[i] + s[i]](因为右下角为 b[i],边长为 s[i],左右各延伸 s[i])。
放置规则:
S[i] 必须与所有 S[j] (j < i) 内部不相交,且 b[i] 取满足条件的最小值。
几何约束:两个正方形斜向放置时,在 x 轴方向上的最小间隔等于 2 * min(s[i], s[j])(因为乘以 2 后,边长的半长在 x 轴上投影长度为 s[i],二者相切时中心距为 s[i] + s[j] ?需要仔细推导)。
推导过程(关键)
在变换后的轴对齐矩形中,矩形 j 的范围为
[b[j] - s[j], b[j]] × [b[j] - s[j], b[j]],其右下角为 (b[j], b[j])。
矩形 i 的范围为
[b[i] - s[i], b[i]] × [b[i] - s[i], b[i]]。
若要 i 与 j 内部不相交,且 b[i] > b[j](因为按顺序放置,b 递增),则只需保证它们在 x 轴上的投影不重叠,或在 y 轴上的投影不重叠。由于两者均位于直线 y = x 的右上方,且 b[i] > b[j],实际上 x 轴投影的分离即可保证不相交(因为 y 方向也会同步分离)。
所以要求:b[i] - s[i] >= b[j] + s[j] 或 b[j] - s[j] >= b[i] + s[i],但后者不可能(因为 b[i] > b[j]),故必须满足:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class SegmentTree {
private:
int n;
vector<long long> tree, lazy;
vector<bool> hasLazy;
void pushDown(int node) {
if(!hasLazy[node]) return;
lazy[node*2] = lazy[node];
hasLazy[node*2] = true;
tree[node*2] = lazy[node];
lazy[node*2+1] = lazy[node];
hasLazy[node*2+1] = true;
tree[node*2+1] = lazy[node];
hasLazy[node] = false;
}
void rangeAssign(int node, int l, int r, int ql, int qr, long long val) {
if (ql <= l && r <= qr) {
tree[node] = val;
lazy[node] = val;
hasLazy[node] = true;
return;
}
pushDown(node);
int mid = (l + r) / 2;
if (ql <= mid) rangeAssign(node*2, l, mid, ql, qr, val);
if (qr > mid) rangeAssign(node*2+1, mid+1, r, ql, qr, val);
}
long long pointQuery(int node, int l, int r, int pos) {
if (l == r) return tree[node];
pushDown(node);
int mid = (l + r) / 2;
if (pos <= mid) return pointQuery(node*2, l, mid, pos);
else return pointQuery(node*2+1, mid+1, r, pos);
}
public:
SegmentTree(int size) {
n = size;
tree.assign(4*n, 0);
lazy.assign(4*n, 0);
hasLazy.assign(4*n, false);
}
void rangeAssign(int l, int r, long long val) {
if (l > r) return;
rangeAssign(1, 1, n, l, r, val);
}
long long pointQuery(int pos) {
return pointQuery(1, 1, n, pos);
}
};
void solve() {
int n; while(cin >> n) {
if(n == 0) break;
vector<int> s(n + 1), b(n + 1, 0);
int x = 0, y = 0;
//通过拉伸坐标来消除相交点的漏判还有小数
for(int i = 1; i <= n; i ++) {cin >> s[i]; s[i]*= 2;}
b[1] = s[1];
for(int i = 2; i <= n; i ++) {
for(int j = 1; j < i; j ++) b[i] = max(b[i], b[j] + 2 * min(s[i], s[j]));
}
vector<tuple<int, int, int>> pos;
int mx = 0;
for(int i = 1; i <= n; i ++) {
int y = b[i] + s[i];
mx = max(mx, y);
pos.emplace_back(s[i], b[i], i);
}
sort(pos.begin(), pos.end());
SegmentTree tr(mx);
for(int i = 1; i <= n; i ++) {
auto [st, bi, id] = pos[i-1];
int left = bi - st, right = bi + st;
tr.rangeAssign(left, right, id);
}
set<int> sk;
for(int i = 0; i <= mx; i ++) {
int k = tr.pointQuery(i);
if(k == 0 || sk.count(k)) continue;
sk.insert(k);
}
for(auto& e : sk) cout << e << " ";
cout << "\n";
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
int _=1; //cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号