「Fortuna OJ」Jul 5th – Group A 解题报告 / 集训队互测 2013

A – 家族

这道题真的是送分题(快要想出来直接暴力枚举+并查集的时候去看了题解,最后发现就是这么 sb)。

考虑枚举频段区间\([L, R]\)(将边进行排序,确定下界之后再枚举上界),这个地方是\(O(m^2)\)的。每次枚举下界的时候都要初始化并查集,然后合并两个集合的时候按照大小来修改答案就行了。

// A.cpp
#include <bits/stdc++.h>
#define ll long long

using namespace std;

const ll INF = 0x3f3f3f3f3f3f3f3f, MAX_N = 1010, MAX_E = 5050;

ll n, m, k, ans = INF, fa[MAX_N], current_ans, siz[MAX_N], dat[MAX_N];

struct edge
{
    ll from, to, frequency;
    bool operator<(const edge& e) const { return frequency < e.frequency; }
} edges[MAX_E];

void ufs_initialize()
{
    for (ll i = 1; i <= n; i++)
        fa[i] = i, siz[i] = 1;
}

ll find(ll x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

void unite(ll x, ll y) 
{
    x = find(x), y = find(y);
    if (x == y)
        return;
    if (siz[x] < siz[y])
        swap(x, y);
    current_ans -= dat[siz[x]] + dat[siz[y]];
    fa[y] = fa[x], siz[x] += siz[y];
    current_ans += dat[siz[x]];
}

signed main()
{
    scanf("%lld%lld%lld", &n, &m, &k);
    for (ll i = 1; i <= n; i++)
        scanf("%lld", &dat[i]);
    for (ll i = 1; i <= m; i++)
        scanf("%lld%lld%lld", &edges[i].from, &edges[i].to, &edges[i].frequency);
    sort(edges + 1, edges + 1 + m);
    for (ll lbound = 1, lower = edges[1].frequency; lbound <= m; lbound++, lower = edges[lbound].frequency)
    {
        ufs_initialize(), current_ans = dat[1] * n;
        for (ll rbound = lbound, upper = edges[rbound].frequency; rbound <= m; rbound++, upper = edges[rbound].frequency)
        {
            ll width = upper - lower;
            unite(edges[rbound].from, edges[rbound].to);
            if (current_ans >= k)
            {
                ans = min(ans, width);
                break;
            }
        }
    }
    if (ans == INF)
        puts("T_T");
    else
        printf("%lld", ans);
    return 0;
}

B – 供电网络

第二题就开始毒瘤了起来,把题意精简之后就是这样的:

给定一个有源汇点的上下界网络,其中每一条边的费用是关于流量的二次关系。求最小费用可行流即可。

首先忽略二次关系,这道题单建模是很简单的,标准的有源汇的上下界可行流模型,根据每个城市的结余连接超级源点和汇点,根据每个城市购入和输出电力的价格连接源点和汇点。

之后,我们可以把连续的二次关系离散化:也就是,恒定流过的流量为\(1\),然后每次求完最短路、统计答案之后,考虑继续加边:流量上界为\(1\)下界为\(0\),且费用为当前与上次流过的差值,也就是\(a_i (x + 1)^2 – b_i (x + 1) – a_i x^2 – b_i x_i = 2 a_i x _+ a_i + b_i\),根据这个式子可以加一条新边。判断是否可行即可。

// B.cpp
#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 330, MAX_E = 5050, INF = 0x3f3f3f3f;

int n, m, head[MAX_N], current, di[MAX_N], s, t, S, T, answer, flag[MAX_N][MAX_N];
int dist[MAX_N], upward[MAX_N], flow[MAX_N];

bool vis[MAX_N];

struct edge
{
    int to, nxt, weight, cost;
} edges[MAX_E << 1];

struct side
{
    int u, v, a, b, L, R;
} original_data[MAX_E << 1];

void addpath(int src, int dst, int weight, int cost)
{
    edges[current].to = dst, edges[current].nxt = head[src];
    edges[current].weight = weight, edges[current].cost = cost;
    head[src] = current++;
}

void addtube(int src, int dst, int lower, int upper, int cost, bool doDi = true)
{
    addpath(src, dst, upper - lower, cost), addpath(dst, src, 0, -cost);
    if (doDi)
        di[dst] += lower, di[src] -= lower;
}

bool spfa()
{
    for (int i = 1; i <= m; i++)
        if (flag[original_data[i].u][original_data[i].v] == original_data[i].L && original_data[i].L < original_data[i].R)
        {
            original_data[i].L++;
            addtube(original_data[i].u, original_data[i].v, 0, 1, original_data[i].a * (2 * original_data[i].L - 1) + original_data[i].b, false);
        }
    memset(dist, 0x3f, sizeof(dist));
    queue<int> q;
    q.push(S), dist[S] = 0, vis[S] = true;
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = false;
        for (int i = head[u]; i != -1; i = edges[i].nxt)
            if (dist[edges[i].to] > dist[u] + edges[i].cost && edges[i].weight > 0)
            {
                dist[edges[i].to] = dist[u] + edges[i].cost, upward[edges[i].to] = i;
                if (!vis[edges[i].to])
                    vis[edges[i].to] = true, q.push(edges[i].to);
            }
    }
    return dist[T] != INF;
}

void find()
{
    int u = T, i = upward[u];
    while (u != S)
    {
        edges[i].weight--, edges[i ^ 1].weight++;
        answer += edges[i].cost;
        flag[edges[i ^ 1].to][edges[i].to]++, flag[edges[i].to][edges[i ^ 1].to]--;
        u = edges[i ^ 1].to, i = upward[u];
    }
}

int main()
{
    memset(head, -1, sizeof(head));
    scanf("%d%d", &n, &m);
    s = n + 1, t = s + 1, S = t + 1, T = S + 1;
    for (int i = 1, lft, in, out; i <= n; i++)
    {
        scanf("%d%d%d", &lft, &in, &out);
        if (lft > 0)
            addtube(s, i, lft, lft, 0);
        else if (lft < 0)
            addtube(i, t, -lft, -lft, 0);
        addtube(s, i, 0, INF, in), addtube(i, t, 0, INF, out);
    }
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d%d%d%d", &original_data[i].u, &original_data[i].v,
              &original_data[i].a, &original_data[i].b, &original_data[i].L, &original_data[i].R);
        addtube(original_data[i].u, original_data[i].v, original_data[i].L, original_data[i].L, 0);
        answer += original_data[i].L * (original_data[i].a * original_data[i].L + original_data[i].b);
        flag[original_data[i].u][original_data[i].v] += original_data[i].L;
        flag[original_data[i].v][original_data[i].u] -= original_data[i].L;
    }
    // in searching the possible stream;
    addtube(t, s, 0, INF, 0);
    for (int i = 1; i <= t; i++)
        if (di[i] > 0)
            addtube(S, i, 0, di[i], 0);
        else if (di[i] < 0)
            addtube(i, T, 0, -di[i], 0);
    while (spfa())
        find();
    printf("%d", answer);
    return 0;
}

C – 城市规划

等我把多项式和卷积之类的东西全部搞懂再来写这道毒瘤题吧。

Available Here

Leave a Reply

Your email address will not be published. Required fields are marked *