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\)
证明:
任选\(s_i,i<=k-1\),那么\(s_i...s_k...s_i\)三个之间一定会形成一个逆序对,那么这样的组合一共有\(k-1\)个,所以一定会产生\(k-1\)个逆序对
任选\(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\)到其他每个节点路径的最小权值
题解:分层图最短路
因为要求的是路径最小权值,我们贪心的想删去最大权值边和加上最小权值边,我们将题目转化为删去任意一条边然后一条边加上两次,那么我们实现加减操作可以建立分层图:
- 建立四层图(将每个点拆成4个),每层正常建边
- 连接第一层到第二层的边权为\(0\),代表删去一条边
- 连接第二层到第四层(答案层)的边权为\(2*w\),代表一条边加上两次
- 但是第三层有什么用呢?实际上我们前面分层情况代表了先出现最大边后出现最小边,但是另一种情况就是先出现最小边再出现最大边,所以我们连接第一层到第三层的边权为\(2*w\),连接第三层到第四层(答案层)的边权为\(0\)
- 最后答案就在第四层,但是需要注意有些点和\(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;
}