Educational Codeforces Round 62 (Rated for Div. 2) Solution

时间:2022-08-17 06:04:09

最近省队前联考被杭二成七南外什么的吊锤得布星,拿一场Div. 2恢复信心

然后Div.2 Rk3、Div. 1+Div. 2 Rk9,rating大涨200引起舒适

现在的Div. 2都怎么了,最难题难度都快接近3K了……

A. Detective Book

记\(a_i\)的前缀最大值为\(Max_i\),那么要求的就是\(Max_i = i\)的\(i\)的数量

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std; inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
} int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int N = read() , maxN = 0 , cnt = 0;
for(int i = 1 ; i <= N ; ++i){
maxN = max(maxN , read());
if(maxN == i) ++cnt;
}
cout << cnt;
return 0;
}

B. Good String

注意到如果字符串最右侧是一个'<'或者最左侧是一个'>'的话,我们只需要一直对最右边或者最左边的字符进行操作就可以满足条件

所以我们只需要在字符串首尾删去尽可能少的字符使得其最右侧是一个‘<’或者最左侧是一个'>'。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std; inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
} int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int T;
for(cin >> T ; T ; --T){
string s;
int len;
cin >> len >> s;
if(s[0] == '<' && s[s.size() - 1] == '>'){
int cnt1 = 0 , cnt2 = 0;
for(int i = 0 ; i < s.size() && s[i] == '<' ; ++i)
++cnt1;
for(int i = s.size() - 1 ; i >= 0 && s[i] == '>' ; --i)
++cnt2;
cout << min(cnt1 , cnt2) << endl;
}
else cout << 0 << endl; }
return 0;
}

C. Playlist

从大到小枚举选择的乐曲中最小的愉悦值,用一个堆维护愉悦值大于等于当前枚举值的所有乐曲的播放时间前\(k\)大

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std; #define int long long
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
#define PII pair < int , int >
#define st first
#define nd second bool p(PII a , PII b){return a.st > b.st;} signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int N , K;
cin >> N >> K;
vector < PII > song;
priority_queue < int , vector < int > , greater < int > > q;
int sum = 0 , ans = 0;
for(int i = 1 ; i <= N ; ++i){
int k = read() , b = read();
song.push_back(PII(b , k));
}
sort(song.begin() , song.end() , p);
for(auto t : song){
q.push(t.nd);
sum += t.nd;
if(q.size() > K){
sum -= q.top();
q.pop();
}
ans = max(ans , t.st * sum);
}
cout << ans;
return 0;
}

D. Minimum Triangulation

可以证明最优的划分方案中,\(1\)与所有点都有一条边。所以直接计算\(\sum\limits_{i=2}^{n-1} i \times (i+1)\)即可

我觉得可以直接从样例看出这个结论

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<vector>
#include<cmath>
#include<random>
//This code is written by Itst
using namespace std; #define int long long
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
}
#define PII pair < int , int >
#define st first
#define nd second bool p(PII a , PII b){return a.st > b.st;} signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int N , sum = 0;
cin >> N;
for(int i = 3 ; i <= N ; ++i)
sum = sum + i * (i - 1);
cout << sum;
return 0;
}

E. Palindrome-less Arrays

既然不能存在长度\(>1\)的奇回文串,那么一定不能存在长度为\(3\)的回文串,也就意味着\(\forall i \in [1,n-2] , a_i \neq a_{i+2}\)

把下标为奇数和下标为偶数的\(a_i\)拿出来作为两个序列\(b_i,c_i\),那么我们只需要\(b_i \neq b_{i+1} , c_i \neq c_{i+1}\)就是合法的方案

对于两个序列分别dp,设\(f_{i,0/1}\)表示当前已经填完前\(i\)个数,且第\(i\)个数的值与\(i\)之后的第一个有限制的位置的值是否相等的方案数,转移分有限制的点和无限制的点。

边界情况有点多需要注意。

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
//This code is written by Itst
using namespace std; #define int long long
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
} const int MAXN = 2e5 + 7 , MOD = 998244353;
int dp[MAXN][2] , arr[MAXN] , N , K; inline int poww(int a , int b){
int times = 1;
while(b){
if(b & 1) times = times * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return times;
} signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read(); K = read();
for(int i = 1 ; i <= N ; ++i)
arr[i] = read();
vector < int > ind;
ind.clear();
for(int i = 1 ; i <= N ; i += 2)
if(arr[i] != -1)
ind.push_back((i + 1) >> 1);
int times1 , times2;
if(!ind.empty()){
dp[0][0] = 1;
int pos = 0;
for(int i = 1 ; pos < ind.size() ; ++i)
if(ind[pos] == i){
dp[i][0] = dp[i - 1][0];
if(++pos < ind.size() && arr[ind[pos] * 2 - 1] == arr[ind[pos - 1] * 2 - 1])
swap(dp[i][0] , dp[i][1]);
}
else{
if(i == 1){
dp[i][0] = K - 1; dp[i][1] = 1;
}
else{
dp[i][0] = (dp[i - 1][0] * (K - 2) + dp[i - 1][1] * (K - 1)) % MOD;
dp[i][1] = dp[i - 1][0];
}
}
--pos;
times1 = poww(K - 1 , (N + 1) / 2 - ind[pos]) * (dp[ind[pos]][0] + dp[ind[pos]][1]) % MOD;
}
else
times1 = K * poww(K - 1 , (N + 1) / 2 - 1) % MOD;
ind.clear();
for(int i = 2 ; i <= N ; i += 2)
if(arr[i] != -1)
ind.push_back(i >> 1);
if(!ind.empty()){
memset(dp , 0 , sizeof(dp));
dp[0][0] = 1;
int pos = 0;
for(int i = 1 ; pos < ind.size() ; ++i)
if(ind[pos] == i){
dp[i][0] = dp[i - 1][0];
if(++pos < ind.size() && arr[ind[pos] * 2] == arr[ind[pos - 1] * 2])
swap(dp[i][0] , dp[i][1]);
}
else{
if(i == 1){
dp[i][0] = K - 1; dp[i][1] = 1;
}
else{
dp[i][0] = (dp[i - 1][0] * (K - 2) + dp[i - 1][1] * (K - 1)) % MOD;
dp[i][1] = dp[i - 1][0];
}
}
--pos;
times2 = poww(K - 1 , N / 2 - ind[pos]) * (dp[ind[pos]][0] + dp[ind[pos]][1]) % MOD;
}
else
times2 = K * poww(K - 1 , N / 2 - 1) % MOD;
cout << times1 * times2 % MOD;
return 0;
}

F. Extending Set of Points

如果你做过eJOI2018 元素周期表,这道题应该不难想到怎么做

对于题目中"\((x_1 , y_1) \in R , (x_1 , y_2) \in R , (x_2 , y_1) \in R , (x_2 , y_2) \neq R\)时将\((x_2,y_2)\)加入点集中"的操作,可以使用并查集描述:

并查集中存\(6 \times 10^5\)个点分别表示行和列,对于一个点\((x,y)\),将第\(x\)行和第\(y\)列在并查集中合并。注意到加入\((x_1 , y_1)(x_1 , y_2)(x_2 , y_1)\)之后,\(x_2\)行和\(y_2\)列就在同一个并查集里了,这就表示\((x_2,y_2)\)成为了点集\(R\)中的一个点。不难得到加完所有点后,答案是所有并查集包含的行数和列数的乘积之和。

注意到要删点,所以使用线段树分治+带撤销并查集做一下就可以了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<stack>
#include<vector>
//This code is written by Itst
using namespace std; #define int long long
inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
} const int MAXN = 6e5 + 7;
#define PII pair < int , int >
#define st first
#define nd second
map < PII , int > mp;
vector < PII > Edge[MAXN << 2];
int fa[MAXN] , szX[MAXN] , szY[MAXN] , Q , ans; int find(int x){
return fa[x] == x ? x : find(fa[x]);
} #define mid ((l + r) >> 1)
#define lch (x << 1)
#define rch (x << 1 | 1)
void addEdge(int x , int l , int r , int L , int R , PII pos){
if(l >= L && r <= R){
Edge[x].push_back(pos);
return;
}
if(mid >= L) addEdge(lch , l , mid , L , R , pos);
if(mid < R) addEdge(rch , mid + 1 , r , L , R , pos);
} void merge(int x , int y , stack < int > &stk){
x = find(x); y = find(y);
if(x == y) return;
ans -= szX[x] * szY[x] + szX[y] * szY[y];
if(szX[x] + szY[x] < szX[y] + szY[y])
swap(x , y);
fa[y] = x; szY[x] += szY[y]; szX[x] += szX[y];
ans += szX[x] * szY[x];
stk.push(y);
} void work(int x , int l , int r){
stack < int > stk;
int lastans = ans;
for(auto t : Edge[x])
merge(t.st , t.nd + 300000 , stk);
if(l == r)
printf("%lld " , ans);
else{
work(lch , l , mid);
work(rch , mid + 1 , r);
}
while(!stk.empty()){
int t = stk.top(); stk.pop();
int p = find(t);
szX[p] -= szX[t]; szY[p] -= szY[t];
fa[t] = t;
}
ans = lastans;
} signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
Q = read();
for(int i = 1 ; i <= Q ; ++i){
int a = read() , b = read();
PII t = PII(a , b);
if(mp.find(t) == mp.end()) mp[t] = i;
else{
addEdge(1 , 1 , Q , mp[t] , i - 1 , t);
mp.erase(mp.find(t));
}
}
for(auto t : mp)
addEdge(1 , 1 , Q , t.second , Q , t.first);
for(int i = 1 ; i <= 3e5 ; ++i)
szX[fa[i] = i] = 1;
for(int i = 3e5 + 1 ; i <= 6e5 ; ++i)
szY[fa[i] = i] = 1;
work(1 , 1 , Q);
return 0;
}

G. Double Tree

考虑转移问题的优先级:我们要求\((u,v)(2 | u , 2|v)\)和\((u,v)(2 \not\mid u , 2 \not\mid v)\)的总经过次数最小,其次总边权最小。

既然两棵树同构,可以把这两棵树拍扁到一起变成一棵树,那么我们需要在这一棵树上经过边数最小,那么经过的显然是两点之间的路径。

上面的问题可以在拍扁的树上树形DP+倍增处理:设\(f_{i,j,k=0/1,l=0/1}\)表示:设从\(i\)开始跳\(2^j\)次方步到达的点为\(x\),那么原图中\((2 \times i + k , 2 \times x + l)\)的最短路是多少。注意上面的\(i,x\)指的是在拍扁的树上的编号。转移类似于矩阵乘法。

注意到转移优先级之后有可能答案不优,因为可能存在某些情况会绕一段路到另一棵树上,边权总和比直接走的边权要小。如果能将所有\((2i - 1 , 2i)\)的边权变为\(2i-1\)到\(2i\)之间的最短路长度,那么上面的情况就会覆盖\((2i - 1 , 2i)\)的边权,在DP过程中就不需要考虑了。

所以最后的问题是如何求出所有\(2i-1\)到\(2i\)的最短路。有一个很精妙的SSSP做法:建\(N+1\)个点编号为\(0\)到\(N\),连边\((0,i,w_{2i-1,2i})\),对于树上存在的边\((i,j,w_1,w_2)\)在图上连\((i,j,w_1+w_2)\),跑Dijkstra,得到\(0\)到\(i\)的最短路长度就是\(2i-1\)到\(2i\)的最短路长度。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
//This code is written by Itst
using namespace std; #define int long long
inline int read(){
int a = 0;
char c = getchar();
while(!isdigit(c))
c = getchar();
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return a;
} const int MAXN = 3e5 + 7;
int N , Q;
struct Edge{int end , upEd , w;}; inline void addEd(Edge *Ed , int *head , int &cntEd , int a , int b , int c , bool f = 0){
Ed[++cntEd] = (Edge){b , head[a] , c};
head[a] = cntEd;
if(f){
Ed[++cntEd] = (Edge){a , head[b] , c};
head[b] = cntEd;
}
} namespace SSSP{
#define PII pair < int , int >
#define st first
#define nd second
Edge Ed[MAXN << 2];
int head[MAXN] , dis[MAXN];
int cntEd;
priority_queue < PII > q; void work(){
memset(dis , 0x7f , sizeof(dis));
dis[0] = 0;
q.push(PII(0 , 0));
while(!q.empty()){
PII t = q.top(); q.pop();
if(dis[t.nd] != -t.st) continue;
for(int i = head[t.nd] ; i ; i = Ed[i].upEd)
if(dis[t.nd] + Ed[i].w < dis[Ed[i].end])
q.push(PII(-(dis[Ed[i].end] = dis[t.nd] + Ed[i].w) , Ed[i].end));
}
}
} namespace Tree{
Edge Ed[MAXN << 1];
struct matrix{
int a[2][2];
matrix(){memset(a , 0x3f , sizeof(a));}
int* operator [](int x){return a[x];}
matrix operator *(matrix b){
matrix c;
for(int i = 0 ; i < 2 ; ++i)
for(int j = 0 ; j < 2 ; ++j)
for(int k = 0 ; k < 2 ; ++k)
c[j][k] = min(c[j][k] , a[j][i] + b[i][k]);
return c;
}
}dis[MAXN][20] , tmp , initial , toX , toY;
int head[MAXN] , dep[MAXN] , fa[MAXN][20] , val2[MAXN << 1] , cntEd; void dfs(int x , int p , matrix val){
dis[x][0] = val;
fa[x][0] = p;
for(int i = 1 ; fa[x][i - 1] ; ++i){
fa[x][i] = fa[fa[x][i - 1]][i - 1];
dis[x][i] = dis[x][i - 1] * dis[fa[x][i - 1]][i - 1];
}
dep[x] = dep[p] + 1;
for(int i = head[x] ; i ; i = Ed[i].upEd)
if(Ed[i].end != p){
tmp[1][0] = min(Ed[i].w + SSSP::dis[Ed[i].end] , val2[i] + SSSP::dis[x]);
tmp[0][1] = min(val2[i] + SSSP::dis[Ed[i].end] , Ed[i].w + SSSP::dis[x]);
tmp[0][0] = min(Ed[i].w , tmp[0][1] + SSSP::dis[x]);
tmp[1][1] = min(val2[i] , tmp[1][0] + SSSP::dis[x]);
dfs(Ed[i].end , x , tmp);
}
} void init(){
memset(initial.a , 0 , sizeof(initial));
dfs(1 , 0 , initial);
} int jump(int x , int y , bool f1 , bool f2){
if(dep[x] < dep[y]){
x ^= y ^= x ^= y;
swap(f1 , f2);
}
toX = initial; toY = initial;
toX[0][1] = toX[1][0] = SSSP::dis[x];
toY[0][1] = toY[1][0] = SSSP::dis[y];
for(int i = 18 ; i >= 0 ; --i)
if(dep[x] - (1 << i) >= dep[y]){
toX = toX * dis[x][i];
x = fa[x][i];
}
if(x == y) return toX[f1][f2];
for(int i = 18 ; i >= 0 ; --i)
if(fa[x][i] != fa[y][i]){
toX = toX * dis[x][i];
toY = toY * dis[y][i];
x = fa[x][i]; y = fa[y][i];
}
toX = toX * dis[x][0]; toY = toY * dis[y][0];
return min(toX[f1][0] + toY[f2][0] , toX[f1][1] + toY[f2][1]);
} void work(int x , int y){
bool f1 = !(x & 1) , f2 = !(y & 1);
x = (x + 1) >> 1; y = (y + 1) >> 1;
printf("%lld\n" , jump(x , y , f1 , f2));
}
} signed main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
N = read();
for(int i = 1 ; i <= N ; ++i)
addEd(SSSP::Ed , SSSP::head , SSSP::cntEd , 0 , i , read());
for(int i = 1 ; i < N ; ++i){
int a = read() , b = read() , w1 = read() , w2 = read();
addEd(SSSP::Ed , SSSP::head , SSSP::cntEd , a , b , w1 + w2 , 1);
addEd(Tree::Ed , Tree::head , Tree::cntEd , a , b , w1 , 1);
Tree::val2[Tree::cntEd] = Tree::val2[Tree::cntEd - 1] = w2;
}
SSSP::work(); Tree::init();
for(int Q = read() ; Q ; --Q)
Tree::work(read() , read());
return 0;
}