zoukankan      html  css  js  c++  java
  • 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)

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

    那么我们只需先询问 ((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
    ", 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
    ", 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 o 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
    ", 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}) 扯上了关系:若 (xin P_{i-1}&&s_i==s_{x+1}),则 (x+1in 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
    ", resr);
        else printf ("%lld%018lld
    ", 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(sumlimits_{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 - f[mx - 1]), 0;
    }
    
  • 相关阅读:
    java -> final与static 关键字
    软件技术人员需要对数字的敏感性
    如何对抗放假综合症
    IT传统组织结构及新型扁平化组织
    别人的工作台系列三
    别人的工作台系列二
    外包公司做遗留项目有意思么?
    一些外国网站长时间不响应,点叉才能打开的问题
    别人的工作台系列
    2014年干了什么
  • 原文地址:https://www.cnblogs.com/whx666/p/612-div1.html
Copyright © 2011-2022 走看看