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;
}
posted @ 2026-10-03 09:40  thenrify  阅读(7)  评论(0)    收藏  举报