Atcoder abc214

Atcoder abc214

Packing Under Range Regulations

\(10^9\) 个盒子,有 \(n\) 个球,每个球只能放在 \([L_i,R_i]\) 中的某一个盒子中,问能否将 \(n\) 个球都放进盒子中

\(n\le 2\times 10^5\)

sol & code

按左端点排序,对于一些可以放的球,优先放右端点小的,用小根堆维护。

\(now\) 表示当前放球的位置,枚举排序后的区间 \([L_i,R_i]\)

\(L_i=now\) 说明可以考虑这个球,将 \(R_i\) 加入堆中

否则 \(now<L_i\) ,按照堆中顺序填满 \([now,L_i)\) 区间即可

若出现 \(R_i<now\) 则为非法

\(O(n\log n)\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 4e5 + 5;
int n, Ti;
struct L {
    int l, r;
    bool operator < (L o) const {
        if (l ^ o.l) return l < o.l;
        return r < o.r;
    }
} a[N];
priority_queue< int, vector<int>, greater<int> > Q;
int main() {
    scanf("%d", &Ti);
    while (Ti--) {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i].l, &a[i].r);
        }
        a[n + 1].l = a[n + 1].r = 2e9;
        sort(a + 1, a + n + 1);
        int flg = 1;
        int now = 0;
        while (!Q.empty()) Q.pop();
        for (int i = 1; i <= n + 1; i++) {
            if (now == a[i].l) Q.push(a[i].r);
            else {
                while (now < a[i].l && !Q.empty()) {
                    int u = Q.top(); Q.pop();
                    if (now <= u) ++now;
                    else { flg = 0; break; }
                }
                now = a[i].l, Q.push(a[i].r);
            }
            if (!flg) break;
        }
        if (flg) puts("Yes");
        else puts("No");
    }
}

Substrings

给一个字符串,求由不相邻的字符组成的子串个数 \(mod\;10^9+7\)\(|s|\le 2\times 10^5\)

sol & code

考虑弱化版:可以相邻。

\(f_{i}\) 为从 \(1\)\(i\) 选且必须选 \(s_i\) 的方案。

naive 的想法是 \(f_i=\sum_{j=0}^{i-1}f_j\) ,显然容易重复。

正确的:记 \(k\) 为最大的使得 \(s_k=s_i\) 相等的的位置,此时转移为 \(f_{i}=\sum_{j=k}^{i-1}f_j\)

即如果 \([1,k-1]\) 中的子串要选 \(s_i\) ,可以用 \(s_k\) 代替,保证不重复

转移像是 \(O(n)\) 的,实际上最多枚举 26 个就会找到 \(k\) 。所以转移总共 \(O(26n)\)

回到本题:不能相邻

多判断是否 \(j<i-1\) ,但需要多加 \(f_{k-1}\) ,因为 \(k\)\(k-1\) 相邻,\(f_{k-1}\) 没有被计算过

最后 \(ans=\sum f_i\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 2e5 + 5;
const LL P = 1e9 + 7;
int n;
LL f[N], ans;
char a[N];
int main() {
    scanf("%s", a + 1);
    n = strlen(a + 1);
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = i - 1; ~j; j--) {
            if (!j || j < i - 1)
                (f[i] += f[j]) %= P;
            if (a[j] == a[i]) {
                if (j > 1)
                    (f[i] += f[j - 1]);
                break;
            }
        }
        (ans += f[i]) %= P;
    }
    printf("%lld", ans);
}

Three Permutations

给两个 \((1,\cdots n)\) 的排列 \(a,b\) ,求排列 \(r\) 的个数, \(r\) 满足 \(\forall i,r_i\ne a_i\and r_i\ne b_i\)\(n\le 3000\)

sol

容斥,设 \(h(i)\) 表示有 \(i\)\(r_i=a_i\)\(r_i=b_i\) ,则

\[ans=\sum_{i=0}^n(-1)^i h(i)(n-i)! \]

需要求 \(h\) ,如果 \((a_i,b_i)\) 连边,则图由多个环构成,因为每个点的度都是 2

其实就是往边里面填数,每一个点可以选择与他连接的边匹配。

每一个环都是一个子问题,设 \(g(i,j)\) 表示长度为 \(i\) 的环里 \(j\) 对点、边匹配的方案

设共有 \(T\) 条边,第 \(i\) 个环长度为 \(c_i\) ,则枚举 \(i=1,\cdots,T\)

\[h(i,j)=\sum_{k=0}^n h(i-1,j-k)\times g(c_i,k) \]

可以滚掉一维,则最后 \(h(i)\) 等于 \(h(T,i)\)

求解 \(g_{i,j}\) ,设 \(f_{i,j,0/1,0/1}\) 表示长度为 \(i\) 的环,\(j\) 对点边匹配。

将环看成序列, \(i\) 个点 \(i-1\) 条边,末端到开始一条边 \((i,1)\)

第一个 \(0/1\) 表示点 \(i\) 是否和 \((i,i-1)\) 匹配,第二个 \(0/1\) 表示点 \(i\) 是否和 \((i,1)\) 匹配

每次向 \(i+1\) 扩展相当于新加入一个点、一条边,对于 \(f_{i,j,k,l}\) 可以转移到:

  • \(f_{i+1,j,0,l}\) ,第 \(i+1\) 个点不与新加入的边匹配
  • \(f_{i+1,j+1,1,l}\)\(i+1\) 个点和新加入的边匹配
  • \(f_{i+1,j+1,0,l}\) 仅当 \(k=0\) 时可行,让第 \(i\) 个点和新加的边匹配

显然 \(f_{i,j,k,l}\)\(k,l\) 不能同时为 \(1\) ,因为末端点不能同时匹配两条边,所以

\[g(i,j)=f(i,j,0,0)+f(i,j,0,1)+f(i,j,1,0) \]

解决问题,\(O(n^2)\)

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 3005;
const LL P = 1e9 + 7;
int n, a[N], b[N], to[N], vis[N];

LL f[N][N][2][2], g[N][N], fac[N], h[N], h2[N], ans;
inline void Ad(LL &x, LL y) { (x += y) %= P; }
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &b[i]), to[a[i]] = b[i];
    f[1][0][0][0] = 1;
    f[1][1][1][0] = 1;
    f[1][1][0][1] = 1;
    for (int i = 1; i < n; i++)
        for (int j = 0; j <= i; j++)
            for (int k = 0; k < 2; k++)
                for (int l = 0, v; l < 2; l++)
                    if (f[i][j][k][l]) {
                        LL v = f[i][j][k][l];
                        Ad(f[i + 1][j][0][l], v);
                        Ad(f[i + 1][j + 1][1][l], v);
                        if (!k) Ad(f[i + 1][j + 1][0][l], v);
                    }
    g[1][0] = g[1][1] = 1;
    for (int i = 2; i <= n; i++)
        for (int j = 0; j <= i; j++)
            for (int k = 0; k < 2; k++)
                for (int l = 0, v; l < 2; l++)
                    if (k + l < 2) Ad(g[i][j], f[i][j][k][l]);
    h[0] = 1;
    for (int i = 1, le; i <= n; i++) {
        if (vis[i]) continue;
        le = 0;
        for (int j = i; !vis[j]; j = to[j]) vis[j] = 1, ++le;
        memset(h2, 0, sizeof(h2));
        for (int j = 0; j <= n; j++)
            for (int k = 0; k <= le; k++)
                if (j + k <= n) Ad(h2[j + k], h[j] * g[le][k] % P);
        for (int j = 0; j <= n; j++) h[j] = h2[j];
    }
    fac[0] = 1;
    for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % P;
    for (int i = 0, op = 1; i <= n; i++, op *= -1)
        Ad(ans, op * fac[n - i] * h[i] % P);
    printf("%lld", (ans + P) % P);
}

Collecting

\(n\) 个点 \(m\) 条边的有向图,点 \(i\)\(a_i\) 个物品,\(K\) 个人依次从 1 开始遍历这个图。

所到之处收集所有物品,问 \(K\) 个人最多收集的物品。

\(n,m\le 2\times 10^5,K\le 10\)

sol

显然在一个强联通分量的点可以一次收集完。先缩点,得到一个 DAG

\(X_i\) 为编号为 \(i\) 的 scc 的点权和,\(id_i\) 为原图点 \(i\) 所在的 scc 编号, \(cnt\) 为 scc 总个数

关键(没想到): \(K\) 很小,可以用网络流。

一个点只能收集 1 次,拆点 \(u\)\(in_u,out_u\) ,注意 \(u\) 为缩点后的点

连边方式易得:

  • \(in_u\rightarrow out_u\) ,一条边为 \((1,-X_u)\) ,另一条 \((\infin,0)\)
  • 对于原来的边 \((u,v)\) ,连一条 \(out_u\rightarrow in_v\)\((\infin,0)\)
  • \(out_u\rightarrow T\)\((\infin,0)\)
  • \(S\rightarrow in_{id_1}\)\((K,0)\)

跑一个最小费用最大流,但是存在负边权,考虑转为正边权

(细节)按照拓扑序对这个 DAG 重新编号。

实现时由于 tarjan 缩点是 dfs ,其实可以直接反转编号

加边修改如下:

  • \(in_u\rightarrow out_u\) ,一条边为 \((1,0)\) ,另一条 \((\infin,X_u)\)
  • 对于原来的边 \((u,v)\) ,连一条 \(out_u\rightarrow in_v\)\((\infin,X_{u+1}+\cdots+X_{v-1})\)
  • \(out_u\rightarrow T\)\((\infin,X_{u+1}+\cdots+X_{cnt})\)
  • \(S\rightarrow in_{id_1}\)\((K,X_1+\cdots+X_{id_1})\)

最后 \(ans=K\sum_{u}X_u-mincost\)

理解:假设每一个人都可以获得全图的物品。

重复回到一个点就损失了 \(X_u\) ,由于按照拓扑序排好,从 \(u\)\(v\) 就跳过了 \(u+1,\cdots,v-1\) 这些点

最后求最小损失。

最多增广 \(K\) 次,用 dijkstra 实现可以做到 \(O(Kn\log n)\)

注意 long long

code

3K 代码,大嘘

#include <bits/stdc++.h>
#define MP make_pair
using namespace std;
typedef unsigned long long uLL;
typedef long double LD;
typedef long long LL;
typedef double db;
const int N = 4e5 + 5;
int n, m, K, a[N], lst[N], Ecnt, dfn[N], low[N], st[N], top, clk, cl[N], cnt, ins[N];
LL X[N], sm[N];
struct Edg { int to, nxt; } e[N];
inline void Ae(int fr, int go) {
    e[++Ecnt] = (Edg){ go, lst[fr] }, lst[fr] = Ecnt;
}
void tarjan(int u) {
    dfn[u] = low[u] = ++clk, st[++top] = u, ins[u] = 1;
    for (int i = lst[u], v; i; i = e[i].nxt) {
        if (!dfn[v = e[i].to])
            tarjan(v), low[u] = min(low[u], low[v]);
        else if (ins[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        register int o; ++cnt;
        do o = st[top--], ins[o] = 0, X[cnt] += a[o], cl[o] = cnt; while (o ^ u);
    }
}
struct net {
    int lst[N], Ecnt, St, Ed, eg[N], pre[N];
    LL dis[N], Cost;
    priority_queue< pair<LL, int> > Q;
    net() { Ecnt = 1, memset(lst, 0, sizeof(lst)); }
    struct Edge { int to, nxt, qz; LL cs; } e[N << 4];
    inline void Ae(int fr, int go, int vl, LL o) {
        e[++Ecnt] = (Edge){ go, lst[fr], vl, o }, lst[fr] = Ecnt;
    }
    inline void lk(int u, int v, int w, LL q) {
        Ae(u, v, w, q), Ae(v, u, 0, -q);
    }
    bool dijk() {
        for (int i = 1; i <= Ed; i++) dis[i] = 1e18;
        Q.push(MP(0, St)), dis[St] = 0;
        for (int u; !Q.empty(); ) {
            pair<LL, int> now = Q.top(); Q.pop();
            u = now.second;
            if (-now.first > dis[u]) continue;
            for (int i = lst[u], v; i; i = e[i].nxt)
                if (e[i].qz > 0 && dis[v = e[i].to] > dis[u] + e[i].cs) {
                    dis[v] = dis[u] + e[i].cs, pre[v] = u, eg[v] = i;
                    Q.push(MP(-dis[v], v));
                }
        }
        return dis[Ed] < 1e18;
    }
    LL sol() {
        LL res = 0;
        while (dijk()) {
            int fl = 10;
            for (int u = Ed; u ^ 1; u = pre[u]) fl = min(fl, e[eg[u]].qz);
            res += dis[Ed] * fl;
            for (int u = Ed; u ^ 1; u = pre[u]) e[eg[u]].qz -= fl, e[eg[u] ^ 1].qz += fl;
        }
        return res;
    }
} T;
int main() {
    scanf("%d%d%d", &n, &m, &K);
    for (int i = 1, u, v; i <= m; i++) {
        scanf("%d%d", &u, &v);
        Ae(u, v);
    }
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
    T.St = 1, T.Ed = cnt * 2 + 2;
    reverse(X + 1, X + cnt + 1);
    for (int i = 1; i <= n; i++) cl[i] = cnt - cl[i] + 1;
    for (int i = 1; i <= cnt; i++) sm[i] = sm[i - 1] + X[i];
    for (int i = 1; i <= n; i++)
        for (int j = lst[i], v; j; j = e[j].nxt)
            if (cl[i] ^ cl[v = e[j].to]) {
                T.lk(cl[i] << 1 | 1, cl[v] << 1, K, sm[cl[v] - 1] - sm[cl[i]]);
            }
    for (int i = 1; i <= cnt; i++) {
        T.lk(i << 1, i << 1 | 1, 1, 0);
        T.lk(i << 1, i << 1 | 1, K, X[i]);
        T.lk(i << 1 | 1, T.Ed, K, sm[cnt] - sm[i]);
    }
    T.lk(T.St, cl[1] << 1, K, sm[cl[1] - 1]);
    printf("%lld", sm[cnt] * K - T.sol());
}
posted @ 2022-07-17 07:43  小蒟蒻laf  阅读(27)  评论(0编辑  收藏  举报