Educational Codeforces Round 102 (Rated for Div. 2) 题解
A.Replacing Elements
【题面】
给定一个有 \(n\) 个数字的数组 \(a\)。每次可以让 \(a_i\) 变成 \(a_j + a_k\) (\(i \neq j,j \neq k,k \neq i\))。
判断是否可以让所有 \(a_i \leq d\)。
【思路】
1.首先可以发现最优方案一定是让每一个 \(a_i\) 最小,那么除了 \(a_i\) 自己,最小的方案就是让 \(a_i\) 变成最小的 \(a_j+a_k\)。
保证 (\(i \neq j,j \neq k,k \neq i\))。
2.那么在考虑 No
的情况,如果说对于最小的两个元素 \(a_i,a_j\),如果 \(a_i+a_j > d\) 那么一定无解。
【实现】
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
inline 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 ^ 48); ch = getchar(); }
return x * f;
}
void solve(){
int n = read(), d = read();
vector<int>a(n+1);
bool flag = true;
for(int i=1; i<=n; ++i){
a[i] = read();
if (a[i] > d) flag = false;
}
if(flag){
cout << "Yes\n";
return ;
}
sort(a.begin()+1, a.end());
if(a[1] + a[2] <= d){
cout << "Yes\n";
}
else{
cout << "No\n";
}
return ;
}
int main(){
int T = read();
while(T--){
solve();
}
return 0;
}
B.String LCM
【题面】
定义一个字符串的乘法,然后求出对于两个字符串 \(s\) 和 \(t\) 求出其 \(lcm\)。
【思路】
1.首先如果任意两个字符串存在一个 \(lcm\),那么其长度必然也满足 \(lcm\)。
2.我们可以先求出两个字符串长度的 \(lcm\) 然后再用长的求出两个的 \(lcm\) 需要重复当前字符串几次。
3.把两个字符串分别重复这么多次,然后判断是否相等即可。
【实现】
#include <bits/stdc++.h>
using namespace std;
int gcd(int a, int b){
return (b==0 ? a : gcd(b, a % b));
}
void solve(){
string s, t;
cin >> s >> t;
int n = s.size(), m = t.size();
int g = gcd(n, m);
int lcm = g * (n/g) * (m/g);
int n1 = lcm/n, m1 = lcm/m;
string a="", b="";
for(int i=1; i<=n1; ++i){
a += s;
}
for(int i=1; i<=m1; ++i){
b += t;
}
if(a==b){
cout << a << '\n';
}
else{
cout << -1 << '\n';
}
return ;
}
int main(){
int T = read();
while(T--){
solve();
}
return 0;
}
C.No More Inversions
【题面】
先说一下大致题意,就是要求一个排列 \(p\),然后对于给定数列把每个数的值用 \(p\) 对应的值替换,满足某个要求(好像是逆序对数不大于原数列)的情况下字典序最大。
【思路】
1.要计算原序列的逆序对数量,其结果为:
由于排列 \(p\) 的长度为 \(k\),因此有 \(b[i] = p[a[i]] = p[i]\)。为了使排列 \(p\) 的字典序最大,根据康托展开的相关思想,需要让 \(p\) 的逆序对数最大化,即达到:
2.如果直接使用逆康托展开来构造该排列,时间复杂度为 \(O(Tn\log n)\),显然会超时。
3.进一步观察可以发现,原序列的逆序对数量呈现等差数列的规律,因此猜想排列 \(p\) 的构造也可以利用类似的结构。具体来说,可以构造排列 \(p\) 为:
此时 \(p\) 的逆序对数为:
令 \(k - t = n - k\),解得 \(t = 2k - n\)。由此,该问题得到解决。
【实现】
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
inline 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 ^ 48); ch = getchar(); }
return x * f;
}
void solve(){
ll n = read(), k = read();
for(int i=1;i<2*k-n;++i){
cout << i << ' ';
}
int m=k;
for(int i=2*k-n;i<=k;++i) {
cout << m-- << ' ';
}
cout << '\n';
return ;
}
int main(){
int T = read();
while(T--){
solve();
}
return 0;
}
D. Program
【题面】
给定 \(n\) 个操作和一个变量 \(x\),每一个操作满足:
让 \(x\) 加 \(1\)
让 \(x\) 减 \(1\)。
问如果忽略操作 \(l\) 到 操作 \(r\) 之间的操作,那么 \(x\) 会有多少个不同的值
【思路】
1.在一段操作中 \(x\) 的所有值必然是连续的,所以只需求出最大值和最小值就知道了值域大小。无视一段操作后整个区间被分为至多两部分,只需求出每个值域的大小再减去公共部分即可。
【实现】
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
inline 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 ^ 48); ch = getchar(); }
return x * f;
}
const int maxn = 2e5 + 9;
const int inf = 0x3f3f3f3f;
int sum[maxn], maxxl[maxn], maxxr[maxn], minnl[maxn], minnr[maxn];
void solve() {
int n=read(), q=read();
string s;
cin >> s;
for (int i = 1; i <= n; ++i){
sum[i] = sum[i - 1] + (s[i - 1] == '-' ? -1 : 1);
maxxl[i] = max(maxxl[i - 1], sum[i]);
minnl[i] = min(minnl[i - 1], sum[i]);
}
maxxr[n + 1] = -inf;
minnr[n + 1] = inf;
for (int i = n; i >= 1; --i){
maxxr[i] = max(maxxr[i + 1], sum[i]);
minnr[i] = min(minnr[i + 1], sum[i]);
}
for (int cas = 1, l, r; cas <= q; ++cas){
cin >> l >> r;
int t = sum[r] - sum[l - 1];
int ans = max(maxxl[l - 1], maxxr[r + 1] - t) - min(minnl[l - 1], minnr[r + 1] - t) + 1;
cout << ans << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T=read();
while(T--) solve();
return 0;
}
E.Minimum Path
【题面】
给定一张 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边边权为 \(w_i\)。
对于一条从点 \(1\) 到点 \(n\) 的路径,设其经过的边集为 \(E\),定义这条路径的长度为:
求点 \(1\) 到其他所有点的最短路径。
【思路】
1.这种路径长度涉及最大值和最小值,无法直接进行转移,考虑将题目进行变形。题目要求计算:
2.为了使得路径长度最小,需要减去的一定是边权最大值,加上的一定是边权最小值,因此题目实际上可以转化为:路径长度之和加上任意某边的最小值,再减去任意某边的最大值。
3.接下来可以通过建立分层图来实现加减操作。每层包含正常的连边,从第一层到第二层连接权值为 \(0\) 的边,从第二层到第三层连接权值为 \(2w\) 的边,答案即为从起点 \(1\) 到第三层各点的最短路径。
4.但上述方法忽略了先进行加法后进行减法的情况,因此可以再增加一层:从第一层到该新层连接权值为 \(2w\) 的边,再从该新层向终点层连接权值为 \(0\) 的边。
【实现】
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
inline 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 ^ 48); ch = getchar(); }
return x * f;
}
const int N = 1e6 + 10;
int n, m, T;
struct edge {
int to, nxt; ll val;
}e[N * 3 << 1];
int head[N], tot;
void add (int u, int v, ll w) {
e[++tot] = (edge) { v, head[u], w }, head[u] = tot;
}
struct node {
int val; ll key;
bool operator < (const node &a) const {
return key > a.key;
}
};
priority_queue <node> q;
ll dis[N];
bool vis[N];
void dij () {
memset (dis, 127 / 3, sizeof dis);
dis[1] = 0;
q.push((node){ 1, 0 });
while (!q.empty()){
int u = q.top().val; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (dis[v] > dis[u] + e[i].val) {
dis[v] = dis[u] + e[i].val;
q.push((node) { v, dis[v] });
}
}
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= m; i++) {
int u = read(), v = read(); ll w = read();
add (u, v, w), add(v, u, w);
add (u + n, v + n, w), add(v + n, u + n, w);
add (u + 2 * n, v + 2 * n, w), add (v + 2 * n, u + 2 * n, w);
add (u + 3 * n, v + 3 * n, w), add (v + 3 * n, u + 3 * n, w);
add (u, v + n, 2 * w), add (v, u + n, 2 * w);
add (u + n, v + 3 * n, 0), add(v + n, u + 3 * n, 0);
add (u, v + 2 * n, 0), add (v, u + 2 * n, 0);
add (u + 2 * n, v + 3 * n, 2 * w), add (v + 2 * n, u + 3 * n, 2 * w);
}
dij();
for (int i = 2; i <= n; i++)
printf ("%lld ", min(dis[i], dis[i + 3 * n]));
return 0;
}