【题解】Educational Codeforces Round 4
比较菜所以只到 \(E\) 题
A.The Text Splitting
题目描述:
题目分析:
就是要把长度为 \(n\) 的字符串分成长度为 \(q\) 和 \(p\) 的字符串若干,没有任何特殊限制,而且 \(n\) 的范围很小,那么我们就枚举有多少个被分成的 \(q\) 长度的字符串,然后判断是否剩下的可以被分成几个长度为 \(p\) 的字符串就好了,知道了这两个值剩下就暴力模拟就出来了。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,p,q,l = -1,r = -1;
string s;
cin>>n>>p>>q;
cin>>s;
for(int i=0; i<=n; i++){
if(n >= i * p && (n - i * p) % q == 0){
l = i;
r = (n - l * p) / q;
break;
}
}
if(l == -1 || r == -1){
cout<<-1<<endl;
return 0;
}
printf("%d\n",l + r);
bool flag = false;
for(int i=0; i<l * p; i++){
if(i % p == 0 && i != 0)
printf("\n");
cout<<s[i];
flag = true;
}
if(flag)
cout<<endl;
for(int i=0; i<r * q; i++){
if(i % q == 0 && i != 0)
cout<<endl;
cout<<s[i + l * p];
}
return 0;
}
B.HDD is Outdated Technology
题目描述:
题目分析:
这一题我们就记录一下值为 \(i\) 的点是哪个,一个数组就能统计,然后统计 \(i\) 到 \(i+1\) 所有的路径长度求和就好了。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const long long MAXN = 2e5+5;
long long mp[MAXN];
long long a[MAXN];
int main(){
long long n;
cin>>n;
for(long long i=1; i<=n; i++){
cin>>a[i];
mp[a[i]] = i;
}
long long ans = 0;
for(long long i=1; i<=n-1; i++){
ans = ans + abs(mp[i] - mp[i+1]);
}
cout<<ans;
return 0;
}
C.Replace To Make Regular Bracket Sequence
题目描述:
题目分析:
最小的操作次数当然就是将不合法的匹配变为合法的匹配所需要花费的次数,也就是不合法的匹配的数量。那么我们就做一遍括号匹配然后出现不合法的就答案加一并让这对合法就好了。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
bool zuo(char a){
return a == '<' || a == '{' || a == '(' || a == '[';
}
bool you(char a){
return a == '>' || a == '}' || a == ')' || a == ']';
}
bool check(char a,char b){
return (a == '<' && b == '>') || (a == '{' && b == '}') || (a == '(' && b == ')') || (a == '[' && b == ']');
}
int main(){
int ans = 0;
string s;
cin>>s;
stack<char> st;
for(int i=0; i<s.size(); i++){
if(!st.empty() && zuo(st.top()) && you(s[i])){
if(!check(st.top(),s[i])){
ans++;
}
st.pop();
}
else{
st.push(s[i]);
}
}
if(!st.empty()){
printf("Impossible\n");
}
else{
printf("%d\n",ans);
}
return 0;
}
D.The Union of k-Segments
题目描述:
题目分析:
我们发现:只要我们知道了每个位置被覆盖了多少次那么从前到后扫一遍就能得到最后的答案。
这其实就相当于多次修改一次查询,那么使用差分与前缀和就能 \(O(n)\) 的时间内维护这些地方的信息,要注意的是因为区间范围很大所以需要进行离散化后操作。
但是这样就会出现一个问题,假设 \(a_i\) 被覆盖了 \(k\) 次,\(a_{i+1}\) 也被覆盖了 \(k\) 次,但是不代表 \([i,i+1]\) 被覆盖了 \(k\) 次。这样也很好办那就在两个点之间插入一个新点,可以将位置设为 \(\frac{l+r}{2}\),这样只有这个点被覆盖了 \(k\) 次扫的时候才会将下一个点统计入当前的区间,所以就很好地实现了这个问题。因为浮点数不好处理,所以可以考虑将位置全部乘二,这样最后再除回来,因为被临时加入的点不可能成为我们答案区间的左端点或右端点,所以不用考虑其他情况。
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const long long MAXN = 2e6+5;
const long long INF = 1e18+5;
struct node{
long long l,r;
node(){}
node(long long _l,long long _r){
l = _l,r = _r;
}
}a[MAXN];
long long b[4 * MAXN],cnt,sum[4 * MAXN];
vector<node> ans;
int main(){
long long n,k;
scanf("%lld%lld",&n,&k);
for(long long i=1; i<=n; i++){
scanf("%lld%lld",&a[i].l,&a[i].r);
b[++cnt] = a[i].l * 2;
b[++cnt] = a[i].r * 2;
b[++cnt] = a[i].l * 2 - 1;
b[++cnt] = a[i].r * 2 + 1;
}
sort(b+1,b+cnt+1);
long long sz_b = unique(b+1,b+cnt+1) - b - 1;
for(int i=1; i<=n; i++){
a[i].l = lower_bound(b+1,b+sz_b+1,a[i].l * 2) - b;
a[i].r = lower_bound(b+1,b+sz_b+1,a[i].r * 2) - b;
}
for(long long i=1; i<=n; i++){
sum[a[i].l] ++;
sum[a[i].r + 1] --; //因为这个值肯定是有就是 a[i].r + 0.5 的位置,所以直接加没问题
}
for(long long i=1; i<=sz_b; i++){
sum[i] += sum[i-1];
}
long long l= -INF,r = -INF;
for(long long i=1; i<=sz_b; i++){
if(sum[i] < k){
if(l!=-INF && r!=-INF)
ans.push_back(node(l,r));
l = -INF;
r = -INF;
}
else if(sum[i] >= k){
if(l == -INF){
l = b[i]/2; //先乘 2 这里还原回去要除二
}
r = b[i]/2;
}
}
cout<<ans.size()<<endl;
for(long long i = 0; i<ans.size(); i++){
printf("%lld %lld\n",ans[i].l,ans[i].r);
}
return 0;
}
需要注意数组大小开四倍,因为一个区间对应拆出来了四个点
E.Square Root of Permutation
题目描述:
题目分析:
(这仿佛是我第一次彻底搞明白这种映射类型的题)
遇到这类的问题我们一般的思路就是把问题转化为图上问题。
假设我们已经有了序列 \(q\),很明显我们的建边思路就是 \(i - q_i\) 建边,那么考虑进行了 \(q_i = q_{q_i}\) 之后会有什么变化:就是由 \(i\) 点连向的点由 \(q_i\) 变成了由 \(q_i\) 连向的点。我们会发现对于 \(i - q_i\) 这些边所构成的图肯定是有许许多多的环组成的,考虑对于环进行分类讨论:
(1)奇环:我们自己模拟一组数据就能知道,奇环经过操作之后还是奇环只不过相当于每个点连向其连向点的下一个点,也就是中间隔开了一个点,那么我们就在最后的图上点与点也是隔一个,这样就能隔回来,也就是能还原
(2)偶环:对于偶环经过操作之后就会被分裂为两个偶环这两个偶环一个对应编号为奇数的点一个对应编号为偶数的点,也是一个点隔一个点连边,只不过奇偶分成了两个部分,那么两个环分别一个个地取这样也就是可以还原回去
代码详解:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+5;
int nxt[MAXN],tmp[MAXN],flag[MAXN],tmp2[MAXN],q[MAXN];
bool vis[MAXN];
int main(){
int n;
cin>>n;
for(int i=1; i<=n; i++){
cin>>nxt[i];
}
for(int i=1; i<=n; i++){
if(!vis[i]){
int cnt = 0; //记录环大小
int now = i;
do{
vis[now] = true; //记录被访问过
tmp[++cnt] = now; //记录环上的点+统计长度
now = nxt[now]; //跳边
}while(now != i); //神奇的 do-while
if(cnt % 2 == 1){ //奇环
for(int i=1,x = 0; i<=cnt; i++,x = (x + 2)%cnt){ //因为有取模操作所以只能从 0 开始
tmp2[x] = tmp[i]; //隔一个放一个
}
for(int i=0; i<cnt; i++){
q[tmp2[i]] = tmp2[i+1]; //我们的建边方式 i->q[i] 所以这样逆回去就好了
}
q[tmp2[cnt-1]] = tmp2[0]; //最后一个连向第一个
}
else{ //偶环
if(!flag[cnt]) flag[cnt] = i;
else{ //合并
int x = flag[cnt],cnt2 = 0;
do{ //获取另一个偶环的信息
tmp2[++cnt2] = x;
x = nxt[x];
}while(x != flag[cnt]);
flag[cnt] = 0;
int t1 = 1,t2 = 1,last = tmp[1];
for(int i=1; i<=cnt*2; i++){ //这样做:1.大小就是cnt*2 2.为了第一步符合条件
if(i % 2 == 0){ //相当于一个个取连边的操作
last = q[last] = tmp[++t1]; //这个是因为 t1 不能取
}
else{
last = q[last] = tmp2[t2++]; //因为 t2 可取
}
}
q[tmp2[cnt2]] = tmp[1]; //最后的连向最前面的
}
}
}
}
for(int i=2; i<=n; i++){
if(flag[i]){ //意味着有的偶环没有合并自然就不可行了
printf("-1");
return 0;
}
}
for(int i=1; i<=n; i++){
printf("%d ",q[i]);
}
return 0;
}
代码里的注释也非常全,有任何不懂的也可以在下方评论