W
H
X

Codeforces Round #612 (Div. 1)

Codeforces Round #612 (Div. 1)

A

把已经填好的位置取出来构成子序列。如果相邻两个数奇偶性相同,要么这段中填满同样奇偶性的数,贡献为 \(0\),要么贡献为 \(2\)。如果相邻两个不同,怎么填最优贡献都是 \(1\)。那么贪心的考虑相同的情况,填补尽量多的段。注意头上和末尾的两段要特判

B

无解的情况就是某个 \(x\) 的子树大小不够大( \(\leq c_x\) )。否则进行一遍 \(dfs\),处理出每棵子树中的数的相对大小(排序)。合并的时候不同子树间顺序随意(因为相对独立),填到第 \(c_x\) 个的时候把 \(x\) 插入就好了。最后用根节点处的序列给每个位置赋值

void dfs (int u) {
    sz[u] = 1; if (!a[u]) p[u][++c[u]] = u;
    for (int v : g[u]) {
        dfs (v), sz[u] += sz[v];
        for (int i = 1; i <= c[v]; ++i) {
            p[u][++c[u]] = p[v][i];
            if (c[u] == a[u]) p[u][++c[u]] = u;
        }
    }
    if (a[u] > sz[u] - 1) tag = 1;
}
signed main() {
    read (n);
    for (int i = 1, fa; i <= n; ++i) {
        read (fa), read (a[i]);
        if (fa) g[fa].push_back (i); else rt = i;
    }
    dfs (rt);
    if (tag) return puts ("NO"), 0;
    puts ("YES");
    for (int i = 1; i <= n; ++i) id[p[rt][i]] = i;
    for (int i = 1; i <= n; ++i) printf ("%d ", id[i]);
    return 0;
}

C

如果询问两次 \((l,r),(l,r+1)\),两次询问的不同就是第二次多出了子串 \([r+1,r+1],[r,r+1],[r-1,r+1]\dots[l,r+1]\) 这些区间足够我们确定 \([l,r+1]\) 内的每一个字符。对于 C1 直接询问 \((1,n-1),(1,n)\) 就搞定了

对于 C2,还需要更厉害的结论:假设我们已经确定了 \([1,x]\),再询问一次 \((1,2x)\) 即可确定 \([1,2x]\)。Why and How?

先从 \(2x\) 开始,拿出获得的长度为 \(2x-1\) 的两个串。其中一个是 \([2,2x]\),因为 \(1\) 已经确定,也知道 \([1,2x]\) 内每个字母的个数,自然也可以得出 \([2,2x]\) 内每个字母的个数,然后剩下的那个就是 \([1,2x-1]\),再跟 \([1,2x]\) 对比即可得出 \(2x\)

然后处理 \(2x-1\),三个长度为 \(2x-2\) 的串为 \([1,2x-2],[2,2x-1],[3,2x]\),可以用类似方法得出 \([2,2x-1],[3,2x]\),剩下的即为 \([1,2x-2]\),和 \([1,2x-1]\) 对比即可得到 \(2x-1\)

\(\dots\dots\) 依次类推可以确定每一位

那么我们只需先询问 \((1,\frac{n}{2}),(1,\frac{n}{2}-1)\) 确定 \([1,\frac{n}{2}]\),然后询问 \([1,n]\) 确定全部

struct qwq {
    int len, c[26];
    void init (char *a) {
        memset (c, 0, sizeof (c)); len = strlen (a);
        for (int i = 0; i < len; ++i) ++c[a[i] - 'a'];
    }
    void del (char x) { --c[x - 'a']; }
    bool operator < (const qwq &p) const {
        for (int i = 0; i < 26; ++i)
            if (c[i] != p.c[i]) return c[i] < p.c[i];
        return c[0] < p.c[0];
    }
    void debug () {
        for (int i = 0; i < 26; ++i)
            for (int j = 1; j <= c[i]; ++j) putchar (i + 'a'); puts ("");
    }
} o[N];
map<qwq, int> mp;
vector<qwq> a, b, c[N]; char p[N], res[N]; int n;
void ask (int l, int r, int o) {
    printf ("? %d %d\n", l, r);
    fflush (stdout); int len = r - l + 1;
    for (int i = 1; i <= len * (len + 1) / 2; ++i) {
        scanf ("%s", p); qwq tmp; tmp.init (p);
        if (o == 1) a.push_back (tmp);
        else if (o == 2) b.push_back (tmp);
        else c[tmp.len].push_back (tmp);
    }
}
int delta (qwq a, qwq b) {
    for (int i = 0; i < 26; ++i) if (a.c[i] != b.c[i]) return i + 'a';
}
#define pc putchar
void print () {
    pc ('!'), pc (' ');
    for (int i = 1; i <= n; ++i) pc (res[i]); fflush (stdout);
}
signed main() {
    cin >> n; int mid = (n + 1) / 2;
    if (n < 3) {
        for (int i = 1; i <= n; ++i) {
            printf ("? %d %d\n", i, i);
            fflush (stdout); scanf ("%s", p);
            res[i] = p[0];
        }
        return print (), 0;
    }
    ask (1, mid - 1, 1), ask (1, mid, 2), ask (1, n, 3);
    for (qwq x : a) ++mp[x];
    for (qwq x : b) if (mp[x] > 0) --mp[x]; else o[x.len] = x;
    for (int i = 1; i <= mid; ++i) res[mid - i + 1] = delta (o[i], o[i - 1]);
    qwq lst = c[n][0];
    for (int i = 1; i <= n - mid; ++i) {
        mp.clear ();
        for (int j = 2; j <= i + 1; ++j) {
            qwq tmp = c[n][0];
            for (int k = 1; k < j; ++k) tmp.del (res[k]);
            for (int k = j + n - i; k <= n; ++k) tmp.del (res[k]);
            ++mp[tmp];
        }
        for (qwq x : c[n - i])
            if (mp[x] > 0) --mp[x]; else res[n - i + 1] = delta (lst, x), lst = x;
    }
    return print (), 0;
}

D

第一次碰撞一定是相邻的两个球相撞(多球碰撞可看作两球),这样的碰撞事件最多 \(2n\) 个,把他们弄出来,按照碰撞时间排序,依次计算第 \(x\) 个事件是第一次碰撞的概率。要满足的条件为前面的事件不发生且第 \(x\) 个必发生。可用 \(dp\) 处理:\(f_{i,j}\) 表示前 \(i\) 个球,第 \(i\) 个球方向为 \(j\) 时满足条件的概率。这个 \(dp\) 的转移可以写成矩阵乘法的形式,并且当 \(x\to x+1\) 时,只有一个位置的转移矩阵发生改变,那么可以用线段树来维护。

int qpow (int x, int y) {
    int t = 1;
    while (y) {
        if (y & 1) t = t * x % mod;
        x = x * x % mod, y >>= 1;
    } return t;
}
#define Inv(x) qpow (x, mod - 2)
int n, res, x[N], v[N], pp[N][2], ok[N][2][2];
struct qwq {
    int id, ka, kb, len, V;
    bool operator < (const qwq &x) const {
        return len * x.V < x.len * V;
    }
} o[N << 1]; int m;
void insert (int id, int ka, int kb) {
    int V = (ka ? v[id] : -v[id]) + (kb ? -v[id - 1] : v[id - 1]);
    o[++m] = {id, ka, kb, x[id] - x[id - 1], abs (V)};
}
struct mat {
    int a[2][2];
    void clear () { memset (a, 0, sizeof (a)); }
    void init (int xa, int xb, int xc, int xd) {
        a[0][0] = xa, a[1][0] = xb, a[0][1] = xc, a[1][1] = xd;
    }
    friend mat operator * (mat x, mat y) {
        mat res; res.clear ();
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 2; ++j)
                for (int k = 0; k < 2; ++k)
                    (res.a[i][j] += x.a[i][k] * y.a[k][j]) %= mod;
        return res;
    }
	int sum () { return a[0][0] + a[0][1]; }
} c[N << 2];
struct yxl {
    int x, ka, kb, v;
    #define ls (p << 1)
    #define rs (p << 1 | 1)
    void build (int p, int l, int r) {
        if (l == r) {
            c[p].init (pp[l][0], pp[l][0], pp[l][1], pp[l][1]);
            return;
        } int mid (l + r >> 1);
        build (ls, l, mid), build (rs, mid + 1, r);
        c[p] = c[ls] * c[rs];
    }
    void modify (int p, int l, int r) {
        if (l == r) {
            c[p].clear ();
            if (v) c[p].a[ka][kb] = pp[l][kb];
            else {
                for (int i = 0; i < 2; ++i)
                    for (int j = 0; j < 2; ++j)
                        if (!ok[l][i][j]) c[p].a[i][j] = pp[l][j];
            } return;
        }
        int mid (l + r >> 1);
        x <= mid ? modify (ls, l, mid) : modify (rs, mid + 1, r);
        c[p] = c[ls] * c[rs];
    }
} w;
signed main() {
    read (n); int inv = Inv (100);
    for (int i = 1, p; i <= n; ++i) {
        read (x[i]), read (v[i]), read (p);
        pp[i][1] = inv * p % mod;
        pp[i][0] = mod + 1 - pp[i][1];
    }
    for (int i = 1; i < n; ++i) {
        insert (i + 1, 1, 0);
        if (v[i] > v[i + 1]) insert (i + 1, 1, 1);
        if (v[i] < v[i + 1]) insert (i + 1, 0, 0);
    }
    w.build (1, 1, n); sort (o + 1, o + m + 1);
    for (int i = 1; i <= m; ++i) {
        qwq t = o[i];
        w = {t.id, t.ka, t.kb, 1}, w.modify (1, 1, n);
        (res += t.len * Inv (t.V) % mod * c[1].sum ()) %= mod;
        w.v = 0, ok[t.id][t.ka][t.kb] = 1, w.modify (1, 1, n);
    }
    printf ("%lld\n", res);
}

E

加入字符 \(s_i\) 后,用 \(kmp\) 算法处理,新增的有效区间 对应的前缀 的右端点 的集合就是 \(\{next_i,next_{next_i},\dots,1\}\),设这个集合为 \(P_i\)

但如果暴力处理 \(next\) 链上对应的权值似乎有点慢,我们看看能否继承上一次的信息来加速。容易发现 \(P_i\) 中的元素 \(x\) 可以分为两类:\(x-1 \in P_{i-1}\)\(x=1\)。后者只需判断 \(s_1==s_i\) 即可,而前者成功的和 \(P_{i-1}\) 扯上了关系:若 \(x\in P_{i-1}\&\&s_i==s_{x+1}\),则 \(x+1\in P_i\)

依旧不能暴力遍历 \(P_{i-1}\)。但如果只遍历 \(P_{i-1}\) 中不满足 \(s_i==s_{x+1}\)\(x\) 并将其删除,那么删除的次数是 \(O(n)\) 的(因为每次最多加入一个元素,总共只有 \(O(n)\) 个)。怎么做呢?不妨在计算 \(next_x\) 的时处理一个 \(pre_x\),表示从 \(x\) 开始跳 \(next\) 链时碰到的第一个 \(p\) 满足 \(s_{x+1}!=s_{p+1}\),那么如果当前的 \(s_i==s_{x+1}\),就跳一步 \(pre\),否则跳一步 \(next\),这样就避免了遍历大量无用元素。

int j = nxt[i - 1];
while (j && s[i] != s[j + 1]) j = nxt[j];
nxt[i] = j + (s[i] == s[j + 1]);
pre[i - 1] = (s[i] == s[nxt[i - 1] + 1]) ? pre[nxt[i - 1]] : nxt[i - 1];
for (int k = i - 1; k;) {
    if (s[k + 1] == s[i]) k = pre[k];
    else {
        int val = query (i - k); // 查询区间权值,可用线段树或单调栈二分
        --cnt[val], sum -= val;
        if (!cnt[val]) cnt.erase (val);
        k = nxt[k];
    }
}

剩下的问题就是权值的处理了,用一个 \(map\) 记录每个权值对应的个数,先执行上述的删除过程,然后考虑权值的变化:需要把 \(map\)\(>w_i\) 的修改为 \(w_i\)。这个过程可以暴力执行,因为每次将一个权值 \(W>w_i\) 的修改为 \(w_i\) ,相当于合并了两个集合,那么最多合并 \(n\) 次,复杂度 \(O(nlogn)\)

for (it = cnt.upper_bound (w[i]); it != cnt.end ();) {
    sum -= (it -> first - w[i]) * it -> second;
    num += it -> second;
    ii = next (it), cnt.erase (it), it = ii;
}

注意点:1、答案较大,可用两个 long long 拼接表示 2、别忘了每个前缀的贡献(前缀一定合法)

完整代码:

const int N = 6e5 + 5, MX = (1 << 30) - 1, mod = 1e18;
int n, sum, ans_, ans, resl, resr, tp;
int st[N], pre[N], nxt[N], w[N]; char s[N], t[2];
void print () {
    if (!resl) printf ("%lld\n", resr);
    else printf ("%lld%018lld\n", resl, resr);
}
int query (int pos) {
    return w[st[lower_bound (st + 1, st + tp + 1, pos) - st]];
}
map<int, int> cnt; map<int, int>::iterator it, ii;
signed main() {
    scanf ("%lld %s %lld", &n, t, &w[1]);
    ans_ = w[1] % 26; s[1] = t[0] - 'a';
    resr = ans = w[1]; print (); st[++tp] = 1;
    for (int i = 2; i <= n; ++i) {
        scanf ("%s", t); read (w[i]);
        s[i] = (t[0] - 'a' + ans_) % 26; w[i] ^= ans;
        int j = nxt[i - 1];
        while (j && s[i] != s[j + 1]) j = nxt[j];
        nxt[i] = j + (s[i] == s[j + 1]);
        pre[i - 1] = (s[i] == s[nxt[i - 1] + 1]) ? pre[nxt[i - 1]] : nxt[i - 1];
        for (int k = i - 1; k;) {
            if (s[k + 1] == s[i]) k = pre[k];
            else {
                int val = query (i - k);
                --cnt[val], sum -= val;
                if (!cnt[val]) cnt.erase (val);
                k = nxt[k];
            }
        }
        if (s[1] == s[i]) ++cnt[w[i]], sum += w[i];
        while (tp && w[i] <= w[st[tp]]) --tp; st[++tp] = i;
        int num = 0;
        for (it = cnt.upper_bound (w[i]); it != cnt.end ();) {
            sum -= (it -> first - w[i]) * it -> second;
            num += it -> second;
            ii = next (it), cnt.erase (it), it = ii;
        }
        int qwq = w[st[1]] + sum;
        resr += qwq, cnt[w[i]] += num;
        if (resr >= mod) resr -= mod, ++resl; print ();
        ans_ = (ans_ + qwq) % 26, ans = (ans + qwq) & MX;
    }
    return 0;
}

F

把每次操作 \(2\) 看成一条边,如果某个连通块中存在环(非树),那么这个块中全都改成操作 \(1\) 不会更劣。所以想要答案更优就要让操作 \(2\) 连成森林,且每棵树可让答案 \(-1\)

如何判断一棵大小为 \(n\) 的树是否可以通过操作 \(2\) 清空?先随便定一个根,从下往上消除,观察这个过程,容易想到把节点分为两部分:奇数层和偶数层节点(即二分图黑白染色)。设奇数层的权值和为 \(sa\),偶数层为 \(sb\)。如果每次操作减掉的是一样的,则必有 \(sa=sb\)。但现在每次可以让奇数层或偶数层多 \(+1(-1)\),共 \(n-1\) 次调整的机会,那么条件变为 \(|sa-sb|<n\)\(|sa-sb|\)\(n-1\) 奇偶性相同,而 \(|sa-sb|\) 的奇偶性与 \(sa+sb\) 相同,即为所有节点权值之和,因此第二个条件可以快速判断。

对于一个点集 \(S\),可以枚举奇数层的(子)点集来判断是否能够用树形消除,复杂度 \(O(3^ {|S|})\)。考虑采用折半优化。上述过程就相当于给每一个数添上 \(+-\) 号。把这些点均分成两部分,分别处理出所有情况的权值,存储数组 \(sl,sr\) 中,将 \(sl,sr\) 排序。可用双指针在线性时间内完成判断。

这里的排序可以在枚举的过程中完成,去掉 \(log\):依次加入每一个数 \(a_i\),假设前 \(i-1\) 个数的 \(2^{i-1}\) 种情况已经排好序,将 \(a_i\) 为正号和负号的情况分别依次放入 \(A,B\) 中,\(A,B\) 依旧单调,然后通过双指针归并合并成长度 \(2^i\) 的有序数组。总复杂度为 \(O(2^{\frac{|S|}{2}})\),判断所有集合的复杂度为 \(O(\sum\limits_{i=1}^{n}2^{\frac{i}{2}}C_n^i=(1+\sqrt{2})^n)\)

接下来可以用一些我不会的东西做到 \(O(2^nn^2logn)\)。而直接暴力\(dp\)复杂度为 \(O(3^n)\),加一些小小的剪枝就过了

const int N = 22, M = (1 << 20) + 5;
int n, a[N], f[M], b[N], sl[M], sr[M], A[M], B[M];
void get (int l, int r, int *o) {
    int len = 1; o[1] = 0;
    for (int i = l; i <= r; ++i, len <<= 1) {
        for (int j = 1; j <= len; ++j)
            A[j] = o[j] + b[i], B[j] = o[j] - b[i];
        A[len + 1] = B[len + 1] = 1e18;
        for (int p = 1, q = 1, j = 1; j <= len << 1; ++j)
            o[j] = A[p] > B[q] ? B[q++] : A[p++];
    }
}
int check (int s) {
    int c = 0, sum = 0;
    for (int i = 1; i <= n; ++i)
        if (s >> i - 1 & 1) sum += a[i], b[++c] = a[i];
    if ((sum + c - 1) & 1) return 0;
    get (1, c / 2, sl), get (c / 2 + 1, c, sr);
    int nl = 1 << (c / 2), nr = 1 << (c - c / 2);
    int cnt = (abs (sum) < c) ? 3 : 1; // 不能所有数同号.如果所有数同号可行,则有两种无效
    for (int i = nr, j = 1; i >= 1; --i) {
        while (j <= nl && sl[j] + sr[i] <= -c) ++j;
        // 拆开绝对值,先让下边界合法,然后判上界
        for (int p = j; p <= nl && cnt && sl[p] + sr[i] < c; ++p) --cnt;
    }
    return (cnt == 0);
}
signed main() {
    scanf ("%lld", &n); int m = 0;
    for (int i = 1; i <= n; ++i)
        { scanf ("%lld", a + i); if (a[i]) a[++m] = a[i]; }
    n = m; int mx = 1 << n;
    for (int i = 1; i < mx; ++i)
        if (!f[i] && check (i)) {
            int p = (mx - 1) ^ i; f[i] = 1;
            for (int j = p; j; j = (j - 1) & p)
                f[i | j] = max (f[i | j], f[j] + 1);
        }
    return printf ("%lld\n", n - f[mx - 1]), 0;
}
posted @ 2021-05-18 21:01  -敲键盘的猫-  阅读(43)  评论(0编辑  收藏  举报