A. Fair Bandwidth Sharing

题意

给定 $n$ 个区间 $[l_i, r_i]$,并且给定 $n$ 个权值 $d_i$,和一个常数 $t$。

定义 $y_i = t \frac{d_i}{\sum\limits_{j=1}^n d_j}$。

求一组 $x_i$,使得满足以下所有条件:

  1. $\forall i \in [1,n], x_i \in [a_i, b_i]$。
  2. $\sum\limits_{i=1}^n x_i = t$。
  3. $\sum\limits_{i=1}^n \frac{(x_i-y_i)^2}{y_i}$ 最小。

其中,$n \leq 10^5, t \in [1,10^6], 0 \leq l_i \leq r_i \leq 10^6, r_i > 0, d_i \in [1,10^6]$。

题解

假设对于所有的 $i$,$d_i=1$,那么我们可以二分一个值 $z$,然后对于 $z$ 穿过的区间,就让 $x_i$ 取那个值,剩下的没有穿过的就让 $x_i$ 取边界,然后判断这个 $z$ 的值导致的 $\sum x_i$ 是 $>t$ 还是 $<t$。

img

现在考虑 $d_i$ 不一定为 $1$ 的情况。首先我们处理一下使得 $\sum d_i = 1$。

那么对于式子 $\frac{(x_i-y_i)^2}{y_i}$ 中的变量 $x_i$ 求导可以得到 $\frac{2}{t}(\frac{x_i}{d_i} - t)$。由于 $t$ 是常数我们可以忽略不计。注意到这个导数是随着 $x_i$ 增大而增大的,而如果多个 $x_i$ 的导数相同,那么它们应该是根据 $d_i$ 的值等比例的。

于是衍生出了基于上述方法的新方法:

同样二分 $z$,但是 $x_i = z * d_i$,边界的话仍然取边界值,然后判断 $\sum x_i$ 是 $>t$ 还是 $<t$。

代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+55;

int n;
long double t;
int l[maxn], r[maxn];
double d[maxn], x[maxn];

// 取 
bool check(long double z) {
    long double res = 0.0;
    for (int i = 1; i <= n; i++) {
        x[i] = z * d[i];
        x[i] = max(x[i], (double)l[i]);
        x[i] = min(x[i], (double)r[i]);
        res += x[i];
    }
    return res >= t;
}


int main() {
    fastio;
    cin >> n >> t;
    long double sumd = 0.0;
    for (int i = 1; i <= n; i++) cin >> l[i] >> r[i] >> d[i], sumd += d[i];
    for (int i = 1; i <= n; i++) d[i] /= sumd;

    long double low = 0.0, high = 1e12;
    int T = 100;
    while (high - low > 1e-6 && T--) {
        long double mid = (low + high) * 0.5;
        if (check(mid)) {
            high = mid;
        } else low = mid;
    }
    for (int i = 1; i <= n; i++) {
        cout << setprecision(10) << x[i] << "\n";
    }
}

F. Factor-Free Tree

题意

给定一个长度为 $n$ 的数组 $a_1, a_2, …, a_n$。

是否存在一个二叉树,使得这个二叉树的 in-order 遍历的sequence等于这个数组,且保证对于每一个节点 $i$,它与它所有的祖先节点都互质。

img

样例如上图。

如果有,输出方案。

其中,$n \leq 10^6, a_i \in [1, 10^7]$。

题解

很明显递归解决。对于 solve(l, r) 来说,确定一个根节点以后,保证这个根节点 $i \in [l,r]$ 并且 $i$ 与所有的 $[l,r]$ 内的数都互质即可。

但可能有很多这样的可行根节点。

一个重要猜想:随便选一个满足条件的根节点即可。

证明:假设我们找出了所有可行的根节点,那一个最佳方案是将这些根节点都split掉。这和我们按照顺序来split其实并无区别。所以随便选一个可行根节点即可。

那么只要知道,给定一个index的范围 $[L,R]$,从里面随便找出一个和其他都互质的数字即可。

这个好办,每个数字质因数分解一下,然后对于每一个质因数找到它们所在的位置,这样可以处理出两个数组 nxt[i] 表示 $a_i$ 右侧距离它最近的 $j$ 使得 $a_j$ 与 $a_i$ 不互质。同理可以得到 pre[i] 就是左侧最近的。

然后只要满足 nxt[i] > R && pre[i] < L 即可。


最后一个问题:复杂度是多少?

对于一个长度为 $n$ 的区间,我们最多需要 $O(n)$ 的时间找到这样的根 $i$,而最坏情况下可能会 split 成 $1, n-1$ 两个大小,所以看起来 $T(n) = T(1) + T(n-1) + O(n) = O(n^2)$?

一个很重要的优化:

我们在找这样的根 $i$ 时,不要从 $L$ 遍历到 $R$,而是从两边开始遍历,也就是 $L,R,L+1,R-1,L+2,R-2…$。

这样复杂度来到 $O(n\log n)$。

为什么呢?

因为这样可以最快的找到满足条件的根 $i$。假设 $\min(i-L+1, R-i+1) = k$,也就是根距离两个端点的最近距离,那么有

$$T(n) = T(k) + T(n-k-1) + 2k$$

那么最坏情况下是 $k=\frac{n}{2}$,也就是

$$T(n) = 2T(\frac{n}{2}) + n = O(n\log n)$$

代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+55;
const int maxm = 1e7+5;
int n, a[maxn];
bool isPrime[maxm];
int small[maxm];
vector<int> primes;
void preprocess() {   // 线性筛优化
    memset(isPrime, 1, sizeof(isPrime));
    small[1] = 1;
    for (int i = 2; i <= maxm-5; i++) {
        if (isPrime[i]) primes.push_back(i), small[i] = i;
        for (int j = 0; j < primes.size() && i * primes[j] <= maxm-5; j++) {
            int cur = i * primes[j];
            isPrime[cur] = 0;
            small[cur] = primes[j];
            if (i % primes[j] == 0) break;
        }
    }
}
vector<int> pos[maxm];
int nxt[maxn], pre[maxn];


int par[maxn];

// if ok: return index of root
// if not ok: return 0
// if empty: return -1
int solve(int l, int r) {
    if (l > r) return -1;
    if (l == r) return l;

    // l, r, l+1, r-1, l+2, r-2
    int i = l, j = r;
    while (i <= j) {
        if (pre[i] < l && nxt[i] > r) {
            int lres = solve(l, i-1), rres = solve(i+1, r);
            if (lres == 0 || rres == 0) return 0;
            if (lres > 0) par[lres] = i;
            if (rres > 0) par[rres] = i;
            return i;
        }

        swap(i, j);
        if (pre[i] < l && nxt[i] > r) {
            int lres = solve(l, i-1), rres = solve(i+1, r);
            if (lres == 0 || rres == 0) return 0;
            if (lres > 0) par[lres] = i;
            if (rres > 0) par[rres] = i;
            return i;
        }
        swap(i, j);

        i++, j--;
    }
    return 0;
}

int main() {
    fastio;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    preprocess();
    fill(nxt, nxt+maxn, n+1);
    fill(pre, pre+maxn, 0);

    for (int i = 1; i <= n; i++) {
        int x = a[i];
        while (x > 1) {
            int sp = small[x];
            while (x % sp == 0) x /= sp;
            pos[sp].push_back(i);
        }
    }
    for (int sp = 2; sp <= maxm-5; sp++) {
        for (int i = 0; i < pos[sp].size(); i++) {
            int curp = pos[sp][i];
            if (i > 0) {
                pre[curp] = max(pre[curp], pos[sp][i-1]);
            }
            if (i < pos[sp].size() - 1) {
                nxt[curp] = min(nxt[curp], pos[sp][i+1]);
            }
        }
    }
    if (!solve(1, n)) {
        cout << "impossible\n";
    } else {
        for (int i = 1; i <= n; i++) cout << par[i] << " ";
        cout << "\n";
    }
}