ECFinal2020 题解

ECFinal2020 题解

A.Random Shuffle

每一个时刻每一位可以表示成其余位的异或。

可以用bitset记录每一位和其它位之间的异或方程,记为\(f_i\)

比如对于左移13步,可以令\(f_i\leftarrow f_i\text{xor}f_{i-13}\)

如果知道\(\mod x\)的值,我们也就知道了当前这个时刻的后\(k\)位,\(k\)是最大满足\(2^k|x\)的值。

\(n\geq 50\)的条件下,至少有\(47\)个方程,得到\(17\)个自由元。

枚举这17个判断即可。

时间复杂度为\(O(2^{17}+N)\)

code:

const int MAXN = 1e5 + 233;
typedef bitset<65> bs;
bs f[200];
int n, a[MAXN], pos[MAXN], gen[MAXN];
vector<bs> func;
unsigned LL seed;
bs zzz;
vector<int> fr;
bool val[64];
void gauss() {
    while (func.size() > 64) func.POB();
    while (func.size() < 64) func.PB(zzz);
    rep(i, 64) {
        bool ok = 0;
        for (int j = i; j < 64; ++j) {
            if (func[j][i]) {
                swap(func[j], func[i]);
                ok = 1;
                break;
            }
        }
        if (!ok) {
            fr.PB(i);
            continue;
        }
        rep(j, 64) {
            if (j != i)
                if (func[j][i])
                    func[j] ^= func[i];
        }
    }
}
bool check(unsigned LL Tmp) {
    seed = Tmp;
    rb(i, 1, n) {
        seed ^= seed << 13;
        seed ^= seed >> 7;
        seed ^= seed << 17;
        if (seed % i != gen[i])
            return false;
    }
    return true;
}
int main() {
    rep(i, 64) f[i].set(i);
    scanf("%d", &n);
    rb(i, 1, n) scanf("%d", &a[i]), pos[a[i]] = i;
    rl(i, n, 1) {
        gen[i] = pos[i] - 1;
        int oth = pos[i];
        swap(pos[i], pos[a[i]]);
        swap(a[oth], a[i]);
    }
    rb(T, 1, min(70, n)) {
        rl(i, 63, 13) f[i] ^= f[i - 13];
        rb(i, 0, 63) f[i] ^= f[i + 7];
        rl(i, 63, 17) f[i] ^= f[i - 17];
        rep(i, 64) if ((T >> i) & 1) { break; }
        else {
            bs tmp = f[i];
            if ((gen[T] >> i) & 1)
                tmp.set(64);
            func.PB(tmp);
        }
    }
    gauss();
    rep(mask, 1 << fr.size()) {
        unsigned LL tmp = 0;
        rep(i, fr.size()) {
            if ((mask >> i) & 1) {
                tmp |= (unsigned LL)(1) << fr[i];
            }
            val[fr[i]] = (mask >> i) & 1;
        }
        rep(i, 64) {
            if (func[i][i]) {
                bool va = func[i][64];
                rep(j, 64) if (func[i][j]) va ^= val[j];
                if (va) {
                    tmp |= (unsigned LL)(1) << i;
                }
            }
        }
        if (check(tmp)) {
            cout << tmp << endl;
            return 0;
        }
    }
    return 0;
}

B.City Brain

假设我们已知路径求分配方案。

考虑将\(k\)元,分配到\(y\)个路径:

由于\(\frac{1}{x}\)越往后减少越缓慢,所以分配的一定是越平均越好,这个是可以\(O(1)\)计算的。

其实只需要知道有多少路径经过2人和1人,设为\(x\)\(y\)

对于每一个\(x\),其实只需要找打对应最小的\(y\),这个部分可以直接枚举相遇点和相离点,\(O(N^2)\)预处理。

对于每一个\(x,y\),考虑各分配多少,由于这是两个凸函数相加,所以是单峰的,直接三分即可。

code:

const int MAXN = 5005;
int n, m, k, s1, t1, s2, t2;
vector<int> g[MAXN];
int dis[MAXN][MAXN];
int dp[MAXN];
double calc(int tot, int cnt) {
    double have = tot / cnt;
    double rst = tot % cnt;
    double oth = cnt - rst;
    return oth * (1 - 1 / (1 + have)) + rst * (1 - 1 / (1 + (have + 1.0)));
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    rb(i, 1, m) {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].PB(v);
        g[v].PB(u);
    }
    memset(dis, 63, sizeof(dis));
    rb(i, 1, n) {
        queue<int> q;
        q.push(i);
        dis[i][i] = 0;
        while (!q.empty()) {
            int now = q.front();
            q.pop();
            for (auto it : g[now])
                if (dis[i][it] == INF) {
                    dis[i][it] = dis[i][now] + 1;
                    q.push(it);
                }
        }
    }
    scanf("%d%d%d%d", &s1, &t1, &s2, &t2);
    memset(dp, 63, sizeof(dp));
    rb(i, 1, n) rb(j, 1, n) if (dis[i][j] != INF && dis[s1][i] != INF && dis[s2][i] != INF &&
                                dis[j][t1] != INF && dis[j][t2] != INF)
        check_min(dp[dis[i][j]], dis[s1][i] + dis[s2][i] + dis[j][t1] + dis[j][t2]);
    dp[0] = dis[s1][t1] + dis[s2][t2];
    double rest = 1e18;
    rb(i, 0, n) {
        if (dp[i] != INF) {
            double tmp = 2.0 * i + dp[i];
            if (!i && dp[i] == 0) {
                rest = 0;
                break;
            }
            if (i == 0)
                tmp -= calc(k, dp[i]);
            else if (dp[i] == 0)
                tmp -= 2.0 * calc(k, i);
            else {
                int l = 0, r = k;
                while (l < r) {
                    int mid = (l + r) >> 1;
                    if (2.0 * calc(mid, i) + calc(k - mid, dp[i]) <
                        2.0 * calc(mid + 1, i) + calc(k - (mid + 1), dp[i]))
                        l = mid + 1;
                    else
                        r = mid;
                }
                tmp -= 2.0 * calc(l, i) + calc(k - l, dp[i]);
            }
            check_min(rest, tmp);
        }
    }
    printf("%.15Lf\n", rest);
    return 0;
}

C.Prof. Pang's sequence

离线处理询问。

扫描右端点,遇到一个值\(x\)时翻转当前位置和\(x\)上一次出现的位置之间的奇偶状态,若是奇数则带来1的贡献。

对于询问,只需要查询\([l,r]\)中的历史版本和。

可以用线段树实现,具体实现方法:

对于每一个节点记录:

  1. 上一次更新这个节点的时间。
  2. 从上一次pushdown开始有多长时间处于翻转状态,有多长时间处于不翻转状态
  3. 当前区间1和0的个数。
  4. 历史和:ans

pushdown:

  1. 计算从上一次更新经过的时间,加到对应状态上。

  2. 根据各状态的时间算对历史答案的贡献。

  3. 将各状态的时间传递到儿子,需要更具儿子处是否翻转考虑是否取反。

  4. 传递tag。

  5. 将最新的更新时间设为当前时间。

pushup:

​ ans和1,0的个数直接继承儿子的值。

code:

const int N = 1 << 19;
int nowt;
struct node {
    int tag, sum[2], cnt[2], t;
    LL ans;
    node() { memset(sum, 0, sizeof(sum)), memset(cnt, 0, sizeof(cnt)), tag = ans = t = 0; }
};
node tree[N + N];
LL ans[N];
vector<mp> que[N];
map<int, int> pre;
void pd(int now) {
    tree[now].sum[tree[now].tag] += nowt - tree[now].t;
    tree[now].t = nowt;
    tree[now].ans += 1ll * tree[now].cnt[1] * tree[now].sum[0] + 1ll * tree[now].cnt[0] * tree[now].sum[1];
    if (now < N) {
        bool lt, rt;
        lt = tree[now << 1].tag;
        rt = tree[now << 1 | 1].tag;
        rep(i, 2) {
            tree[now << 1].sum[lt ^ i] += tree[now].sum[i];
            tree[now << 1 | 1].sum[rt ^ i] += tree[now].sum[i];
        }
        tree[now << 1].tag ^= tree[now].tag;
        tree[now << 1 | 1].tag ^= tree[now].tag;
        tree[now << 1].t = tree[now << 1 | 1].t = nowt;
    }
    if (tree[now].tag)
        swap(tree[now].cnt[0], tree[now].cnt[1]);
    tree[now].tag = 0;
    tree[now].sum[0] = tree[now].sum[1] = 0;
}
void pu(int now) {
    tree[now].cnt[0] = tree[now << 1].cnt[0] + tree[now << 1 | 1].cnt[0];
    tree[now].cnt[1] = tree[now << 1].cnt[1] + tree[now << 1 | 1].cnt[1];
    tree[now].ans = tree[now << 1].ans + tree[now << 1 | 1].ans;
}
void modify(int a, int b, int now = 1, int l = 1, int r = N + 1) {
    pd(now);
    if (r <= a || l >= b)
        return;
    if (r <= b && l >= a) {
        tree[now].tag = 1;
        pd(now);
        return;
    }
    int mid = (l + r) >> 1;
    modify(a, b, now << 1, l, mid);
    modify(a, b, now << 1 | 1, mid, r);
    pu(now);
}
LL query(int a, int b, int now = 1, int l = 1, int r = N + 1) {
    pd(now);
    if (r <= a || l >= b) {
        return 0;
    }
    if (r <= b && l >= a) {
        return tree[now].ans;
    }
    int mid = (l + r) >> 1;
    LL tmp = query(a, b, now << 1, l, mid) + query(a, b, now << 1 | 1, mid, r);
    return tmp;
}
int n, a[N], m;
int main() {
    rl(i, N + N - 1, 1) if (i >= N) tree[i].cnt[0] = 1;
    else tree[i].cnt[0] = tree[i << 1].cnt[0] << 1;
    scanf("%d", &n);
    rb(i, 1, n) scanf("%d", &a[i]);
    scanf("%d", &m);
    rb(i, 1, m) {
        int l, r;
        scanf("%d%d", &l, &r);
        que[r].PB(II(l, i));
    }
    rb(i, 1, n) {
        modify(pre[a[i]] + 1, i + 1);
        nowt++;
        for (auto it : que[i]) {
            ans[it.SEC] = query(it.FIR, i + 1);
        }
        pre[a[i]] = i;
    }
    rb(i, 1, m) printf("%lld\n", ans[i]);
    return 0;
}
posted @ 2021-04-27 20:36  WWW~~~  阅读(1422)  评论(0编辑  收藏  举报