2019 Multi-University Training Contest 1

目录

1001 Blank
1002 Operation
1004 Vacation
1005 Path


1001 Blank

不会做,看题解。

设dp[i][j][k][l]表示4种颜色出现的最后的位置分别是i,j,k,l的方法数,保证i>=j>=k>=l。其实不取=号,因为同一个位置不能放两个元素,除了开始的若干个比如dp[1][0][0][0]=4。

合法的转移叠加:
比如
刷新的颜色是i:dp[i+1][j][k][l]+=dp[i][j][k][l]
刷新的颜色是j:dp[i+1][i][k][l]+=dp[i][j][k][l]
刷新的颜色是k:dp[i+1][i][j][l]+=dp[i][j][k][l]
刷新的颜色是l:dp[i+1][i][j][k]+=dp[i][j][k][l]

假如从dp[i][j][k][l]转移到上述状态会导致新状态不满足约束则不进行这次转移,就太麻烦了。
由于是dp,其实只要遇到约束区间的右端点R的时候再考虑约束就可以了。

考虑四个数其实是[l,k,j,i],i肯定就是R,要是l>=L则有恰好4种颜色,否则要是k>=L则恰好3种颜色,否则要是j>=L则恰好2种颜色,否则只有1种颜色。

对每次转移后新状态的i维度必定是i+1,对i维度进行滚动节省空间。复杂度是精确的O(n4+mn3),据说很多队觉得过不了。

还卡memset???15组数据还卡memset?

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline int read() {
    int x = 0;
    char c = 0;
    while(c < '0' || c > '9')
        c = getchar();
    while(c >= '0' && c <= '9')
        x = (x << 3) + (x << 1) + c - '0', c = getchar();
    return x;
}

inline void _write(int x) {
    if(x > 9)
        _write(x / 10);
    putchar(x % 10 + '0');
}

inline void write(int x) {
    _write(x);
    putchar('
');
}

struct Condition {
    int l, r, x;
    bool operator<(const Condition &c)const {
        return r < c.r;
    }
} con[105];

const int mod = 998244353;
int n, m, dp[2][105][105][105], ctop;

inline void clear0(int n) {
    for(int j = 0; j <= n; ++j) {
        for(int k = 0; k <= j; ++k) {
            for(int l = 0; l <= k; ++l) {
                dp[n & 1][j][k][l] = 0;
            }
        }
    }
}

inline void clear1(int i, int L) {
    //清除==1种的
    for(int j = L - 1 ; j >= 0; --j) {
        for(int k = j ; k >= 0; --k) {
            for(int l = k ; l >= 0; --l) {
                dp[i & 1][j][k][l] = 0;
            }
        }
    }
}

inline void clear2(int i, int L) {
    //清除==2种的
    for(int j = i ; j >= L; --j) {
        for(int k = L - 1; k >= 0; --k) {
            for(int l = k ; l >= 0; --l) {
                dp[i & 1][j][k][l] = 0;
            }
        }
    }
}

inline void clear3(int i, int L) {
    //清除==3种的
    for(int j = i ; j >= L ; --j) {
        for(int k = j ; k >= L; --k) {
            for(int l = L - 1; l >= 0; --l) {
                dp[i & 1][j][k][l] = 0;
            }
        }
    }
}

inline void clear4(int i, int L) {
    //清除==4种的
    for(int j = i ; j >= L ; --j) {
        for(int k = j ; k >= L ; --k) {
            for(int l = k ; l >= L; --l) {
                dp[i & 1][j][k][l] = 0;
            }
        }
    }
}

inline void update1(int i) {
    if(ctop > m || i < con[ctop].r)
        return;
    while(ctop <= m && i == con[ctop].r) {
        //printf("cons %d
", ctop);
        int L = con[ctop].l, x = con[ctop].x;
        ++ctop;
        if(x == 1) {
            clear2(i, L);
            clear3(i, L);
            clear4(i, L);
        } else if(x == 2) {
            clear1(i, L);
            clear3(i, L);
            clear4(i, L);
        } else if(x == 3) {
            clear1(i, L);
            clear2(i, L);
            clear4(i, L);
        } else {
            clear1(i, L);
            clear2(i, L);
            clear3(i, L);
        }
    }
}

inline void update2(int i, int j, int k, int l) {
    dp[(i + 1) & 1][j][k][l] += dp[i & 1][j][k][l];
    if(dp[(i + 1) & 1][j][k][l] >= mod)
        dp[(i + 1) & 1][j][k][l] -= mod;
    dp[(i + 1) & 1][i][k][l] += dp[i & 1][j][k][l];
    if(dp[(i + 1) & 1][i][k][l] >= mod)
        dp[(i + 1) & 1][i][k][l] -= mod;
    dp[(i + 1) & 1][i][j][l] += dp[i & 1][j][k][l];
    if(dp[(i + 1) & 1][i][j][l] >= mod)
        dp[(i + 1) & 1][i][j][l] -= mod;
    dp[(i + 1) & 1][i][j][k] += dp[i & 1][j][k][l];
    if(dp[(i + 1) & 1][i][j][k] >= mod)
        dp[(i + 1) & 1][i][j][k] -= mod;
}

int solve() {
    ctop = 1;
    memset(dp[1], 0, sizeof(dp[1]));
    dp[1][0][0][0] = 4;
    update1(1);
    for(int i = 1; i <= n - 1; ++i) {
        //printf("i=%d
", i);
        clear0(i + 1);
        for(int j = i; j >= 0; --j) {
            for(int k = j ; k >= 0; --k) {
                if(k == j && k)
                    continue;
                for(int l = k ; l >= 0; --l) {
                    if(l == k && l)
                        continue;
                    update2(i, j, k, l);
                }
            }
        }
        update1(i + 1);
        for(int j = i; j >= 0; --j) {
            for(int k = j; k >= 0; --k) {
                if(k == j && k)
                    continue;
                for(int l = k ; l >= 0; --l) {
                    if(l == k && l)
                        continue;
                    //printf("dp[%d][%d][%d][%d]=%d
", i + 1, j, k, l, dp[(i + 1) & 1][j][k][l]);
                }
            }
        }
        //printf("
");
    }
    int ans = 0;
    for(int j = n - 1; j >= 0; --j) {
        for(int k = j ; k >= 0; --k) {
            for(int l = k ; l >= 0; --l) {
                ans += dp[n & 1][j][k][l];
                if(ans >= mod)
                    ans -= mod;
            }
        }
    }
    return ans;
}
int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
#endif // Yinku
    int T = read();
    while(T--) {
        n = read(), m = read();
        for(int i = 1; i <= m; ++i) {
            con[i].l = read(), con[i].r = read(), con[i].x = read();
        }
        sort(con + 1, con + 1 + m);
        write(solve());
    }
    return 0;
}

1002 Operation

这道题要用什么前缀线性基(类似的思想)。假如要求[1,r]的值能表示的最大值,那对每个位置保存一个前缀线性基,每次插入就复制前一个并插入就可以了。现在的问题是要求[l,r]里面的值能表示的最大值。题解有个思路,现在倒也觉得很显然,和以前尺取的做法一样,越靠右的元素一定越容易出现在[l,r]之中,插入一个新的元素的时候,由这个新元素从最高位开始贪心,遇到一个他能表示的位则和那个线性基进行交换,这样子就会有更多的贡献的可能,同时不会更差。求最大值的时候那也是从最高位开始向下问,在[l,r]范围内的就采用。


1004 Vacation

当时二分时间通过了这道题。实际上每辆车都会和前面的若干车组成一组通过终点线,所以只需要枚举每辆车通过终点线的时间,在时间中取最大值就可以了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000;
const double eps = 1e-9;
int l[maxn + 5], s[maxn + 5], v[maxn + 5], n;
double pos[maxn];

inline bool check(double t) {
    pos[n - 1] = t * v[n - 1] + s[n - 1] - l[n - 1];
    for(int i = n - 2; i >= 0; --i) {
        if(t * v[i] + s[i] >= pos[i + 1])
            pos[i] = pos[i + 1] - l[i];
        else
            pos[i] = t * v[i] + s[i] - l[i];
    }
    return pos[0] + l[0] >= -eps;
}

int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
#endif // Yinku
    while(~scanf("%d", &n)) {
        ++n;
        for(int i = 0; i < n; ++i)
            scanf("%d", l + i);
        for(int i = 0; i < n; ++i)
            scanf("%d", s + i), s[i] = -s[i];
        for(int i = 0; i < n; ++i)
            scanf("%d", v + i);
        double l = 0.0, r = 1e9;
        while(r - l >= eps) {
            double mid = (l + r) / 2.0;
            if(check(mid))
                r = mid;
            else
                l = mid;
        }
        printf("%.8f
", l);
    }
    return 0;
}

1005 Path

题意:给定一个有向图,删去其中的一些边使得1到n的最短路变长,删去边的代价与边的长度相等。

补题思路:跑一遍Dijkstra,找出两个点之间的最短路,然后遍历每一条边,假如边两端的最短距离就是边的长度,说明这条边是最短路的组成部分之一,注意到两点之间的最短路可能会有好几种走法,这样检查之后的确不会遗漏任何一种。比如1->2(4),2->3(6),1->3(10),这样三条都是1->3最短路的组成部分。然后假如割断最短路连通的两端,则最短路一定变长了,所以变成了一个最大流的问题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

namespace Dijkstra {
    const int MAXN = 1e4 + 5;
    const ll INF = 0x3f3f3f3f3f3f3f3f;

    ll dis[MAXN];
    bool vis[MAXN];

    struct Edge {
        int v, w;
        Edge() {};
        Edge(int v, int w): v(v), w(w) {}
    };

    vector<Edge> G[MAXN];

    priority_queue<pair<ll, int> >pq;
    void dijkstra(int s, int n) {
        memset(dis, INF, sizeof(dis[0]) * (n + 1));
        memset(vis, false, sizeof(vis[0]) * (n + 1));
        dis[s] = 0;
        pq.push({-dis[s], s});
        while(!pq.empty()) {
            int u = pq.top().second;
            pq.pop();
            if(vis[u])
                continue;
            vis[u] = true;
            for(auto e : G[u]) {
                int v = e.v, w = e.w;
                if(!vis[v] && dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    pq.push({-dis[v], v});
                }
            }
        }
    }
}

using namespace Dijkstra;

namespace Dinic {
    const int MAXN = 1e4 + 5;
    const int MAXM = 2e4 + 500;
    const int INF = 1e18;

    int top;
    int head[MAXN];

    struct Edge {
        int to, next;
        ll cap, flow;
        Edge() {}
        Edge(int to, int next, ll cap, ll flow): to(to), next(next), cap(cap), flow(flow) {}
    } e[MAXM];


    void Init() {
        top = 2;
        memset(head, -1, sizeof(head));
    }

    void AddEdge(int u, int v, ll w = 1, ll rw = 0) {
        e[top] = Edge(v, head[u], w, 0);
        head[u] = top++;
        e[top] = Edge(u, head[v], rw, 0);
        head[v] = top++;
    }

    int Q[MAXN];
    int dep[MAXN], cur[MAXN], sta[MAXN];

    bool bfs(int s, int t) {
        int fnt = 0, bak = 0;
        memset(dep, -1, sizeof(dep[0]) * (t + 1));
        dep[s] = 0;
        Q[bak++] = s;
        while(fnt < bak) {
            int u = Q[fnt++];
            for(int i = head[u]; i != -1; i = e[i].next) {
                int v = e[i].to;
                if(dep[v] == -1 && e[i].cap > e[i].flow) {
                    dep[v] = dep[u] + 1;
                    if(v == t)
                        return true;
                    Q[bak++] = v;
                }
            }
        }
        return false;
    }

    ll dinic(int s, int t) {
        ll maxflow = 0;
        while(bfs(s, t)) {
            for(int i = 0; i <= t; i++)
                cur[i] = head[i];
            int u = s, tail = 0;
            while(cur[s] != -1) {
                if(u == t) {
                    ll tp = INF;
                    for(int i = tail - 1; i >= 0; i--)
                        tp = min(tp, e[sta[i]].cap - e[sta[i]].flow);
                    maxflow += tp;
                    for(int i = tail - 1; i >= 0; i--) {
                        e[sta[i]].flow += tp;
                        e[sta[i] ^ 1].flow -= tp;
                        if(e[sta[i]].cap - e[sta[i]].flow == 0)
                            tail = i;
                    }
                    u = e[sta[tail] ^ 1].to;
                } else if(cur[u] != -1 && e[cur[u]].cap > e[cur[u]].flow
                          && dep[u] + 1 == dep[e[cur[u]].to]) {
                    sta[tail++] = cur[u];
                    u = e[cur[u]].to;
                } else {
                    while(u != s && cur[u] == -1)
                        u = e[sta[--tail] ^ 1].to;
                    cur[u] = e[cur[u]].next;
                }
            }
        }
        return maxflow;
    }
}

using namespace Dinic;


int main() {
#ifdef Yinku
    //freopen("Yinku.in", "r", stdin);
#endif // Yinku
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i)
            G[i].clear();

        for(int i = 1, u, v, w; i <= m; ++i) {
            scanf("%d%d%d", &u, &v, &w);
            G[u].push_back({v, w});
        }

        dijkstra(1, n);
        Init();
        for(int u = 1; u <= n; ++u)
            for(auto &e : G[u])
                if(dis[e.v] - dis[u] == e.w)
                    AddEdge(u, e.v, e.w);
        printf("%lld
", dinic(1, n));
    }
    return 0;
}

1013 Code


原文地址:https://www.cnblogs.com/Inko/p/11380055.html