CF#892 div2

过了abc,卡在了d

A United We Stand

将数组a拆分乘数组b和c,使得满足任意c[i]不是b[j]的因子,b和c中至少存在一个数。

如果不能输出-1

法一:

巧妙构造:

因为一个大的数不可能是一个小的数的因子,所以我把最大的数(最大的数数量可能有很多个,需要全都放在c里面,因为两个相等的数之间也互为因子)放在c后,其他剩下的数放在b,即构造成功。

只有当所有的数都是最大的数(也就是a中所有数都相等),没有数放到b,输出-1

// 赛时代码
#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

void solve()
{
    int n;
    cin >> n;

    vector<int> a(n);
    int maxv = 0;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        maxv = max(maxv, a[i]);
    }

    sort(a.begin(), a.end());
    if (maxv == a[0])
    {
        cout << "-1" << endl;
        return;
    }

    int pos = 0;
    for (int i = 0; i < a.size(); i++)
        if (a[i] == maxv)
        {
            pos = i;
            break;
        }

    cout << pos << ' ' << n - pos << endl;
    for (int i = 0; i < pos; i++)
        cout << a[i] << ' ';
    cout << endl;
    for (int i = pos; i < a.size(); i++)
        cout << a[i] << ' ';
    cout << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }

    return 0;
}

法二: from jiangly

SCC

如果 x % y == 0,连接一条 x -> y 的有向边。

一个强连通分量里面的点互相之间都满足 x % y == 0,所以一个强连通分量里面的点需要全放在b或者c里面。

如果整个图只有一个强连通分量的话,故输出-1

如果大于一个的话,因为SCC后按照下标递减的顺序就是拓扑排序

所以 bel[i] == 0 => 表示的是只有入度没有出度的那个点

除了这个强连通分量里的点都不是这些点的因子,其他点都只可能是这些点的倍数

#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

struct SCC
{
    int n;
    vector<vector<int>> adj;
    vector<int> stk;
    vector<int> dfn, low, bel;
    int cur, cnt;

    SCC() {}
    SCC(int n)
    {
        init(n);
    }

    void init(int n)
    {
        this->n = n;
        adj.assign(n, {});
        dfn.assign(n, -1);
        low.resize(n);
        bel.assign(n, -1);
        stk.clear();
        cur = cnt = 0;
    }

    void addEdge(int u, int v)
    {
        adj[u].push_back(v);
    }

    void dfs(int x)
    {
        dfn[x] = low[x] = cur++;
        stk.push_back(x);

        for (auto y : adj[x])
        {
            if (dfn[y] == -1)
            {
                dfs(y);
                low[x] = min(low[x], low[y]);
            }
            else if (bel[y] == -1)
            {
                low[x] = min(low[x], dfn[y]);
            }
        }

        if (dfn[x] == low[x])
        {
            int y;
            do
            {
                y = stk.back();
                bel[y] = cnt;
                stk.pop_back();
            } while (y != x);
            cnt++;
        }
    }

    vector<int> work()
    {
        for (int i = 0; i < n; i++)
        {
            if (dfn[i] == -1)
            {
                dfs(i);
            }
        }
        return bel;
    }
};

void solve()
{
    int n;
    cin >> n;

    SCC g(n);
    vector<int> a(n);

    for (int i = 0; i < n; i++)
        cin >> a[i];

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (a[i] % a[j] == 0)
                g.addEdge(i, j);

    auto bel = g.work();
    if (bel == vector(n, 0))
    {
        cout << -1 << endl;
        return;
    }

    vector<int> b, c;
    for (int i = 0; i < n; i++)
    {
        if (bel[i] == 0)
            b.push_back(i);
        else
            c.push_back(i);
    }

    cout << b.size() << ' ' << c.size() << endl;

    for(auto i : b)
        cout << a[i] << " \n"[i == b.back()];
    for(auto i : c)
        cout << a[i] << " \n"[i == c.back()];
    
    return;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}

B Olya and Game with Arrays

最小的 + 选 n-1 组中的次小值

  • 如果最小的不动,那么就是最小的加上其他所有组的次小值
  • 如果最小的动,这组变成加次小值,最小的移动到的组变成加最小值

两种情况是等价的

比赛的时候我是暴力枚举把哪 n-1 行的最小值扔到 另外一行 在加上这一行(包括被扔过来的)的最小值

// 赛时代码
#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

void solve()
{
    int n, m;
    cin >> n;

    vector<int> vm1(n + 1), vm2(n + 1);
    vector<vector<int>> a(n + 1);

    for (int i = 1; i <= n; i++)
    {
        cin >> m;
        int minv1 = INF, minv2 = INF;

        a[i].resize(m + 1);
        for (int j = 1; j <= m; j++)
        {
            cin >> a[i][j];

            if (a[i][j] < minv1)
            {
                minv2 = minv1;
                minv1 = a[i][j];
            }
            else if (a[i][j] < minv2)
            {
                minv2 = a[i][j];
            }
        }

        vm1[i] = minv1;
        vm2[i] = minv2;
    }

    LL ans = 0;
    for (int i = 1; i <= n; i++)
    {
        LL res = 0;
        int minv = vm1[i];
        for (int j = 1; j <= n; j++)
            if (i != j)
            {
                res = res + (LL)vm2[j];
                minv = min(minv, vm1[j]);
            }
        res = res + (LL)minv;
        ans = max(ans, res);
    }

    cout << ans << endl;

    // for (int i = 1; i <= n; i++)
    //     cerr << i << ' ' << vm1[i] << ' ' << vm2[i] << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}

from jiangly

#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

void solve()
{
    int n;
    cin >> n;

    vector<int> a;

    int min1 = 1E9;
    int min2 = 1E9;
    LL ans = 0;

    for (int i = 0; i < n; i++)
    {
        int m;
        cin >> m;

        a.resize(m);
        for (int j = 0; j < m; j++)
        {
            cin >> a[j];
        }

        nth_element(a.begin(), a.begin() + 1, a.end());
        min1 = min(min1, a[0]);
        min2 = min(min2, a[1]);
        ans += a[1];
    }

    ans -= min2;
    ans += min1;

    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }
    return 0;
}

Trick

nth_element(a.begin(), a.begin() + x, a.end())

作用是将第(x + 1)小的数放在a[x]上

并且满足

a[0] ~ a[x - 1] <= a[x] <= a[x] ~ a[n - 1]

所以

nth_element(a.begin(), a.begin() + 1, a.end())

将第二小的数放到了a[1]

进而a[0]只能为最小的数

C Another Permutation Problem

求n的全排列中,以下式子的最大值

\((\sum_{i=1}^{n}p_{i}*i)-(max_{j=1}^{n}p_{j}*j)\)

法一:打表

发现最优解是将某个后缀翻转,形如{1, 2, k,...,n, n - 1,...,k + 1}

枚举翻转长度即可

#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

void solve()
{
    int n;
    cin >> n;

    LL ans = 0;

    for (int i = 0; i <= n; i++)
    {
        LL res = 0;
        LL maxv = i * i;
        for (int j = 1; j <= i; j++)
        {
            res += (LL)j * j;
        }

        maxv = i * i;
        for (int j = i + 1, k = n; j <= n; j++, k--)
        {
            res += (LL)j * k;
            maxv = max(maxv, (LL)j * k);
        }
        res -= maxv;
        ans = max(ans, res);
    }
    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }

    return 0;
}

法二:from jiangly

暴力 and 并查集

枚举\((max_{j=1}^{n}p_{j}*j)\)的值,进行限制。

之后从第n位开始填,尽可能填大的数。

并查集的用于维护的是,如果我现在这个位置上可以填的最大的数是x,但是x已经被填过了。

因为在x填的时候让p[x]=x-1,所以find(x)会从x向更小的数找x-1,x-2...直到第一个没有填的数

#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

struct DSU
{
    vector<int> f, siz;

    DSU() {}
    DSU(int n)
    {
        init(n);
    }

    void init(int n)
    {
        f.resize(n);
        iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }

    int find(int x)
    {
        while (x != f[x])
        {
            x = f[x] = f[f[x]];
        }
        return x;
    }

    bool same(int x, int y)
    {
        return find(x) == find(y);
    }

    bool merge(int x, int y)
    {
        x = find(x);
        y = find(y);
        if (x == y)
        {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }

    int size(int x)
    {
        return siz[find(x)];
    }
};

void solve()
{
    int n;
    cin >> n;

    int ans = 0;
    for (int l = n * n;; l--)
    {
        DSU dsu(n + 1);
        int sum = 0;
        bool ok = true;
        for (int i = n; i >= 1; i--)
        {
            int x = dsu.find(min(n, l / i));
            if (x == 0)
            {
                // 表示已经开始无法满足了
                ok = false;
                break;
            }
            sum += i * x;
            dsu.merge(x - 1, x); // p[x] = x - 1
        }

        if (!ok)
        {
            break;
        }
        ans = max(ans, sum - l);
    }

    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}

D Andrey and Escape from Capygrad

发现对于 l r a b

l到b的数都会跳到b,b到r的数不会动

故a和r是两个没有用的数,将所有线段的[l, b],区间合并即可

之后二分找第一个大于x的r即可

from jiangly

#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

void solve()
{
    int n;
    cin >> n;

    vector<array<int, 2>> seg(n);
    for (int i = 0; i < n; i++)
    {
        int l, r, a, b;
        cin >> l >> r >> a >> b;
        seg[i] = {l, b};
    }

    sort(seg.begin(), seg.end());

    vector<array<int, 2>> a;
    for (auto [l, r] : seg)
    {
        if (!a.empty() && l <= a.back()[1])
        {
            a.back()[1] = max(a.back()[1], r);
        }
        else
        {
            a.push_back({l, r});
        }
    }

    int q;
    cin >> q;

    for (int i = 0; i < q; i++)
    {
        int x;
        cin >> x;

        int j = lower_bound(a.begin(), a.end(), array{x + 1, 0}) - a.begin() - 1;
        if (j >= 0)
        {
            x = max(x, a[j][1]);
        }
        cout << x << " \n"[i == q - 1];
    }
    return;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}

E Maximum Monogonosity

DP

题目:

给两个长度均为 n 的数组 a, b

对选取 [l, r] 一段

长度为 \(r-l+1\)

价值为 \(\lvert a_l - b_r \rvert + \lvert a_r - b_l \rvert\)

选取总长度为 k 的段,每个段都互不相交,求最大总价值

法一:

暴力dp + 剪枝

\(dp[i][j]\) 表示的是前i个字符,还需要总长度为j的片段的最大价值

第一个循环:i

枚举到第i个点

第二个循环:len

枚举最后一段的长度,即枚举是从哪个点转移过来

最后一段即为 [i-len+1, i]

**第三个循环: **z

枚举除去这段前,仍需要的段的总长度

for (int i = 1; i <= n; i++)
{
    for (int len = 1; len <= i; len++)
    {
        int st = i - len + 1;
        for (int z = len; z <= k; z++)
        {
        dp[i][z - j] = max(dp[i][z - j], dp[st - 1][z] + + (LL)abs(a[st] - b[i]) + abs(a[i] - b[st])));
        }
    }
	
    // 因为dp[i][j] 表示的是前i个字符,还需要总长度为j的片段
    // 而每次上面的循环计算出来的dp[i][j]都是i这个点在目前的最后一段里的
    // 所以需要把i这个点不在目前的最后一段里的也max以下
    // 这个值就是dp[i - 1][j]
    for (int j = 0; j <= k; j++)
    {
        dp[i][j] = max(dp[i - 1][j], dp[i][j]);
    }
}
#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int INF = 0x3f3f3f3f;

void solve()
{
    int n, m;
    cin >> n >> m;

    vector<int> a(n + 1), b(n + 1);

    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];

    vector<vector<LL>> dp(n + 1, vector<LL>(m + 1));

    for (int i = 1; i <= n; i++)
    {
        for (int len = 1; len <= i; len++)
        {
            int st = i - len + 1;
            for (int z = max(len, m - (st - 1)); z <= min(m, n - (st - 1)); z++)
            {
                dp[i][z - len] = max(dp[i][z - len], dp[st - 1][z] + (LL)abs(a[st] - b[i]) + abs(a[i] - b[st]));
            }
        }

        for (int j = 0; j <= m; j++)
        {
            dp[i][j] = max(dp[i - 1][j], dp[i][j]);
        }
    }

    LL ans = 0;
    for(int i = 0; i <= n; i ++)
        ans = max(ans, dp[i][0]);

    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}

法二:

标准解法

参考

https://www.cnblogs.com/szdytom/p/how-did-we-get-here-2.html

https://zhuanlan.zhihu.com/p/649682355

首先拆开绝对值

将对于\(\lvert a_l - b_r \rvert + \lvert a_r - b_l \rvert\)的维护转化成

对于\(/+a_l + b_l/+a_l - b_l/-a_l + b_l/-a_l - b_l/\)这四个值的维护

同时维护两个数组dp mx

mx数组更新的是选择第一个点,即每一段的左端点

dp数组更新的是选择第二个点,即每一段的右端点

\(dp[i][j]\):表示前i个点,选的段的总长度为j

\(mx[u][id]\):u表示是拆分出来的四个式子中的哪一个,id表示i-j的值为多少

因为每次选一段 [l, r] 长度 len 后

i变成ii=i+len,j变成jj=j+len,会发现一件神奇的事情是i-j==ii-jj

所以每次转移的时候i-j的值是固定的

可以按照i-j的大小进行分类转移

#include <bits/stdc++.h>

#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"

using namespace std;

typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;

const int N = 3010;
const LL INF = 1e18;

LL mx[4][N];
LL dp[N][N];

int s1[4] = {1, 1, -1, -1};
int s2[4] = {1, -1, 1, -1};

void solve()
{
    int n, k;
    cin >> n >> k;

    vector<int> a(n + 2), b(n + 2);
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];

    auto get = [&](int x, int id, int u) -> LL
    {
        return dp[x][x - id] + a[x + 1] * s1[u] + b[x + 1] * s2[u];
    };

    dp[0][0] = 0;
    for (int i = 1; i <= k; i++)
        dp[0][i] = -INF;
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j <= n; j++)
        {
            mx[i][j] = -INF;
        }
    }

    for (int u = 0; u < 4; u++)
    {
        mx[u][0] = get(0, 0, u);
    }

    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= k; j++)
        {
            dp[i][j] = dp[i - 1][j];
        }
        for (int j = 0; j <= min(k, i); j++)
        {
            int id = i - j;
            for (int u = 0; u < 4; u++)
            {
                dp[i][j] = max(dp[i][j], mx[u][id] - s1[u] * b[i] - s2[u] * a[i]);
            }
            for (int u = 0; u < 4; u++)
            {
                mx[u][id] = max(mx[u][id], get(i, id, u));
            }
        }
    }
    cout << dp[n][k] << '\n';
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int t;
    cin >> t;

    while (t--)
    {
        solve();
    }

    return 0;
}
posted @ 2023-08-14 00:30  devilran  阅读(29)  评论(0)    收藏  举报