Loading

2.27 CW 模拟赛 T3. 芭蕾

前言

一类经典题, 但是并不打算做出正解
仅仅只是对

  • 交换相邻元素性质
    • \(a\)\(b\) , 交换 \(|a - b|\)
    • 对于两个串的定位问题, 每个元素定位的花费就是关于其的逆序对个数
      证明: 从大权值到小权值, 逐个固定位置
    • 往往用固定之前的部分, 移动当前的部分来解决
    • 如果交换有约束
      • 可以认为一个数只能在固定区间内移动
      • 可以看做对相对位置的约束
  • 先从已经确定的部分开始考虑
    • 拆分序列法
      一般从可以严格分成两部分来考虑
  • 先排除无效元素
  • 不动点 + 动点交换
    • 在不动点之间的部分找构造方式

的一些补充罢了

白色显大是真的, 换成浅色主题之后感觉非常巨大

思路

题意

给定 wiw_i 表示第 ii 个人的权值
给定 pip_i 表示第 ii 个位置上的人的编号

一次操作可以交换相邻两个位置 i,i1i, i - 1 上的人仅当 wi+wi1Ww_i + w_{i - 1} \leq \textrm{W} , 其中 W\textrm{W} 是一个给定的常数

求最终有多少种 pip_i 可以达到, 输出一组字典序最小的

首先, 交换相邻元素的性质使我们可以对其进行一些转化
对于 \(a_i\) , 我们找到两侧第一个不能与其交换的数字位置, 记为 \([L, R]\) , 也就是说, 其只能在 \([L, R]\) 中移动
但是并没有什么用, 考虑利用相对位置的约束来解决

先找到没有位置限制的所有元素, 这些是没有用的, 全部去掉
现在我们可以把原串从中间最大的数分开, 这样左边的数一定在左边, 右边的数一定在右边, 方案数乘上这些数任意放置的系数, 就是个插空
同理的, 我们对左右两边递归处理即可

这个问题同样可以在图上理解
我们把所有有限制的数对连上有向边, 表示一个钦定的顺序
不难发现可以按照从大到小的顺序拆分这个图, 也就是断掉这个点之后图分成两份

算出方案数, 如何求出最小字典序
不难发现合法的情况相当于一个拓扑排序, 那些没有连边的点也顺手了

给一份大佬代码供参考

代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1010, mod = 1e9 + 7;
int n, w[MAXN], W, fac[MAXN], inv[MAXN], in[MAXN];
vector<int> p, g[MAXN];
int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}
int qkp(int x, int y)
{
    int res = 1;
    while (y)
    {
        if (y & 1)
            res = 1ll * res * x % mod;
        x = 1ll * x * x % mod;
        y /= 2;
    }
    return res;
}
int C(int x, int y)
{
    return 1ll * fac[y] * inv[x] % mod * inv[y - x] % mod;
}
int solve(vector<int> a, int m)
{
    if (m == 0)
        return 1;
    int mx = 0, ans, fl = -1, t = 1;
    for (int i = 0; i < m; i++)
    {
        mx = max(mx, w[a[i]]);
    }
    for (int i = 0; i < m; i++)
    {
        if (mx + w[a[i]] > W)
        {
            if (w[a[i]] == mx && fl == -1)
                fl = i;
        }
        else
            ++t;
    }
    ans = 1ll * C(t - 1, m) * fac[t - 1] % mod;
    vector<int> b;
    for (int i = 0; i < fl; i++)
    {
        if (mx + w[a[i]] > W)
        {
            b.push_back(a[i]);
        }
    }
    ans = 1ll * ans * solve(b, b.size()) % mod;
    b.clear();
    for (int i = fl + 1; i < m; i++)
    {
        if (mx + w[a[i]] > W)
        {
            b.push_back(a[i]);
        }
    }
    ans = 1ll * ans * solve(b, b.size()) % mod;
    return ans;
}
void get_ans()
{
    for (int i = 0; i < n; i++)
    {
        for (int j = i + 1; j < n; j++)
        {
            if (w[p[i]] + w[p[j]] > W)
            {
                g[p[i]].push_back(p[j]);
                in[p[j]]++;
                //				cout<<i<<" "<<p[i]<<":"<<j<<" "<<p[j]<<"--";
            }
        }
    }
    //	cout<<endl;
    priority_queue<int, vector<int>, greater<int>> q;
    for (int i = 1; i <= n; i++)
        if (!in[i])
            q.push(i);
    while (!q.empty())
    {
        int u = q.top();
        cout << u << " ";
        q.pop();
        for (int v : g[u])
        {
            --in[v];
            if (in[v] == 0)
                q.push(v);
        }
    }
}
signed main()
{
    //	freopen("ex_ballet4.in","r",stdin);
    fac[0] = 1;
    for (int i = 1; i <= MAXN - 10; i++)
        fac[i] = 1ll * fac[i - 1] * i % mod;
    inv[MAXN - 10] = qkp(fac[MAXN - 10], mod - 2);
    for (int i = MAXN - 11; i >= 0; i--)
        inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
    n = read(), W = read();
    for (int i = 1; i <= n; i++)
        p.push_back(read());
    for (int i = 1; i <= n; i++)
        w[i] = read();
    cout << solve(p, n) << endl;
    get_ans();
    return 0;
}

总结

拆分方法, 非常常见
约束条件建成图之后, 往往可以用图上的算法解决一些问题

posted @ 2025-02-27 21:44  Yorg  阅读(13)  评论(0)    收藏  举报