2.27 CW 模拟赛 T3. 芭蕾
前言
一类经典题, 但是并不打算做出正解
仅仅只是对
- 交换相邻元素性质
- 从 \(a\) 到 \(b\) , 交换 \(|a - b|\) 次
- 对于两个串的定位问题, 每个元素定位的花费就是关于其的逆序对个数
证明: 从大权值到小权值, 逐个固定位置 - 往往用固定之前的部分, 移动当前的部分来解决
- 如果交换有约束
- 可以认为一个数只能在固定区间内移动
- 可以看做对相对位置的约束
- 先从已经确定的部分开始考虑
- 拆分序列法
一般从可以严格分成两部分来考虑
- 拆分序列法
- 先排除无效元素
- 不动点 + 动点交换
- 在不动点之间的部分找构造方式
的一些补充罢了
白色显大是真的, 换成浅色主题之后感觉非常巨大
思路
题意
给定 表示第 个人的权值
给定 表示第 个位置上的人的编号
一次操作可以交换相邻两个位置 上的人仅当 , 其中 是一个给定的常数
求最终有多少种 可以达到, 输出一组字典序最小的
首先, 交换相邻元素的性质使我们可以对其进行一些转化
对于 \(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;
}
总结
拆分方法, 非常常见
约束条件建成图之后, 往往可以用图上的算法解决一些问题

浙公网安备 33010602011771号