Educational Codeforces Round 102 (Rated for Div

Educational Codeforces Round 102 (Rated for Div. 2)

No More Inversions

给定\(k\),序列\(a\)长度为\(n\)\(1,2,3...k,k-1,k-2...k-(n-k)\),设序列\(a\)中逆序数为\(m\),给定排列\(p:1,2,3...k\),利用该排列形成新的序列\(b:b[i]= p[a[i]]\), 请你构造排列\(p\)使得序列\(b\)的逆序数不超过\(m\),且序列\(b\)字典序最大

题解:构造 + 思维 + 回文串的逆序对 : 记一下结论

引理:长度相同且元素不重复的回文串(\([s_1,s_2...s_k...s_2,s_1]\))逆序对恒定相等,其逆序数为\((k-1)^2\)

证明:

  1. 任选\(s_i,i<=k-1\),那么\(s_i...s_k...s_i\)三个之间一定会形成一个逆序对,那么这样的组合一共有\(k-1\)个,所以一定会产生\(k-1\)个逆序对

  2. 任选\(s_i,s_j且i,j<=k-1\),那么\(s_i...s_j...s_j...s_i\)四个之间一定会形成两个逆序对,那么这样的组合一共有\(C_{k-1}^{2}\)个,所以一定会产生\(2*C_{k-1}^{2}\)个逆序对

那么最终合并两种情况产生的逆序对,总共产生\((k-1)^2\)个逆序对

我们发现对于任意排列\(p\),假设\(p:[1,2,3,4]\),\(n=6\),那么序列\(b\)一定是\([1,2,3,4,3,2]\),也就是说\([k-(n-k),n]\)之间一定形成回文串,我们发现对于序列\(a\)而言,恰好也是在\([k-(n-k),n]\)之间形成回文串,根据引理得知两者之间逆序数相同,因为序列\(b\)的逆序数不能超过序列\(a\),所以对于序列\(b\)而言\([1,k-(n-k)-1]\)一定不能产生逆序对,也就是说前面一定为\(1,2,3...k-(n-k)-1\),那么如果想要使得字典序最大我们只要使得排列\(p\)\([k-(n-k),k]\)中翻转即可,例如上述样例\(p:[1,4,3,2],b:[1,4,3,2,3,4]\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, k;

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= (k - (n - k) - 1); ++i)
        cout << i << " ";
    for (int i = k; i >= (k - (n - k)); --i)
        cout << i << " ";
    cout << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Program

一共有\(n\)次操作,\(x\)初始值为\(0\),每次操作可以使\(x\)加1或减1,现在给出\(m\)次询问,每次询问给出\(l,r\),要求不执行\([l,r]\)之间的操作,让你给出如果只执行其他操作的话一共\(x\)会出现多少个不同的值

题解:前缀/后缀维护最值

我们思考如何得出答案:我们只要知道这些操作之中\(x\)的最大值和最小值就能知道出现多少个不同的值

我们只需要将每次操作看成-1或1,得到前缀和\(pre\),然后维护\(pre\)的前缀以及后缀最大值、最小值

对于询问\([l,r]\)来说,首先\([1,l-1]\)之间的最大值和最小值不会受到影响,但是\([r+1,n]\)之间的最大值和最小值会受到影响,实际上模拟后发现只需要在后缀最大值和最小值中减去\(pre[r]-pre[l-1]\)就能消除影响

本题也可以通过线段树维护区间最值求解,消除影响也是通过减去\(pre[r]-pre[l-1]\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, q;
int pre[N];
int pre_min[N], pre_max[N];
int suf_min[N], suf_max[N];

void solve()
{
    cin >> n >> q;
    string s;
    cin >> s;
    s = " " + s;
    for (int i = 1; i <= n; ++i)
    {
        if (s[i] == '-')
            pre[i] = pre[i - 1] - 1;
        else
            pre[i] = pre[i - 1] + 1;
        pre_min[i] = min(pre_min[i - 1], pre[i]);
        pre_max[i] = max(pre_max[i - 1], pre[i]);
    }
    suf_max[n + 1] = suf_min[n + 1] = pre[n];
    for (int i = n; i >= 1; i--)
    {
        suf_min[i] = min(suf_min[i + 1], pre[i]);
        suf_max[i] = max(suf_max[i + 1], pre[i]);
    }
    while (q--)
    {
        int l, r;
        cin >> l >> r;
        int sum = pre[r] - pre[l - 1];
        int maxx = max(pre_max[l - 1], suf_max[r + 1] - sum);
        int minn = min(pre_min[l - 1], suf_min[r + 1] - sum);
        cout << maxx - minn + 1 << endl;
    }
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Minimum Path

现给定有权无向图,\(n\)个点,\(m\)条边,定义两点之间路径的权值为:路径上所有边的权值之和\(-\)路径上的最大边权\(+\)路径上的最小边权;先让你求出原点\(1\)到其他每个节点路径的最小权值

题解:分层图最短路

因为要求的是路径最小权值,我们贪心的想删去最大权值边和加上最小权值边,我们将题目转化为删去任意一条边然后一条边加上两次,那么我们实现加减操作可以建立分层图:

  1. 建立四层图(将每个点拆成4个),每层正常建边
  2. 连接第一层到第二层的边权为\(0\),代表删去一条边
  3. 连接第二层到第四层(答案层)的边权为\(2*w\),代表一条边加上两次
  4. 但是第三层有什么用呢?实际上我们前面分层情况代表了先出现最大边后出现最小边,但是另一种情况就是先出现最小边再出现最大边,所以我们连接第一层到第三层的边权为\(2*w\),连接第三层到第四层(答案层)的边权为\(0\)
  5. 最后答案就在第四层,但是需要注意有些点和\(1\)直接相连,那么它既作为最大值也作为最小值,最后路径权值就等于路径上边权之和,所以我们需要特判一下\(min(dis[i],dis[i+3*n])\)

建完图后跑一遍\(dij\)即可

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, m;
vector<pii> g[N << 2];
int dis[N << 2];
int vis[N << 2];

void dij(int st)
{
    for (int i = 1; i <= 4 * n; ++i)
        dis[i] = INF;
    dis[st] = 0;
    priority_queue<pii, vector<pii>, greater<pii>> q;
    q.push({dis[st], st});
    while (q.size())
    {
        int u = q.top().second;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = 1;
        for (auto [v, w] : g[u])
        {
            if (dis[v] > dis[u] + w)
            {
                dis[v] = dis[u] + w;
                q.push({dis[v], v});
            }
        }
    }
}

void solve()
{
    cin >> n >> m;
    for (int i = 1, u, v, w; i <= m; ++i)
    {
        cin >> u >> v >> w;
        g[u].push_back({v, w});
        g[v].push_back({u, w});
        g[u + n].push_back({v + n, w});
        g[v + n].push_back({u + n, w});
        g[u + 2 * n].push_back({v + 2 * n, w});
        g[v + 2 * n].push_back({u + 2 * n, w});
        g[u + 3 * n].push_back({v + 3 * n, w});
        g[v + 3 * n].push_back({u + 3 * n, w});
        g[u].push_back({v + n, 0});
        g[v].push_back({u + n, 0});
        g[u].push_back({v + 2 * n, 2 * w});
        g[v].push_back({u + 2 * n, 2 * w});
        g[u + n].push_back({v + 3 * n, 2 * w});
        g[v + n].push_back({u + 3 * n, 2 * w});
        g[u + 2 * n].push_back({v + 3 * n, 0});
        g[v + 2 * n].push_back({u + 3 * n, 0});
    }
    dij(1);
    for (int i = 2; i <= n; ++i)
        cout << min(dis[i], dis[i + 3 * n]) << " ";
    cout << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-03-16 23:15  Zeoy_kkk  阅读(14)  评论(0编辑  收藏  举报