感觉自己好弱,写篇博客来督促自己补题。
补 D E G 这几题吧。
原比赛连接 https://codeforces.com/contest/1144
D Equalize Them All
题意 :
给你两种操作:
1、ai:=ai+|ai−aj|(其中 |i−j|=1)
2、ai:=ai−|ai−aj|(其中 |i−j|=1)
给你一组数列,问使得数字全相同的最小操作次数,输出哪种操作,操作的i,j。
题解 :
比赛的时候,我没什么思路,就跑去做F了。后来才发现,这两种操作会使得ai变为aj,这么理解就很容易了。最少操作次数就是n-数字出现最多的次数,使其他数字变为该数字就好了,复杂度 O(n)。
经验 : 要理解好所给公式达到的效果。
AC代码如下:
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <string>
#include <cmath>
#include <ctime>
#include <queue>
#include<stack>
#include <map>
#include <set>
#define lowbit(x) x&(-x)
using namespace std;
typedef long long ll;
const int INF =0x3f3f3f3f;
const int N = 2e5 + 5;
int a[N],num[N];
int main()
{
int n,ma,id;//ma为出现次数的最大值,id记录最大值的坐标
memset(num,0,sizeof(num));
ma = -1;
scanf("%d",&n);
for (int i =1; i <=n; i++) {
scanf("%d", &a[i]);
num[a[i]]++;//记录每个数出现次数。
if (ma < num[a[i]])
id = i,ma = num[a[i]];
}
printf("%d\n",n-ma);
for (int i = id - 1, j = id; i > 0; i--,j--) {//从id的左边操作
if (a[i] < a[j]) {
printf("1 %d %d\n",i,j);
}else if (a[i]>a[j]) {
printf("2 %d %d\n", i, j);
}
a[i] = a[j];
}
for (int i = id+1, j = id; i<=n; i++, j++) {//从id的右边操作
if (a[i] < a[j]) {
printf("1 %d %d\n", i, j);
}
else if (a[i] > a[j]) {
printf("2 %d %d\n", i, j);
}
a[i] = a[j];
}
return 0;
}
E : Median String
题意:
长度为k的字符串s,t。s字典序小于t,找出长度为k、字典序在s和t中间的字符串,保证s和t之间为奇数个长度为k的串。
题解 :
类似于哈希的思想,把字符串变为数值。找两个数的中间值不就是相加除以2。。把字符串看成26进制的大数,模拟数字相加,相除的过程就可以啦,超级简单。
AC代码如下:
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <string>
#include <cmath>
#include <ctime>
#include <queue>
#include<stack>
#include <map>
#include <set>
#include<fstream>
#define lowbit(x) x&(-x)
using namespace std;
typedef unsigned long long ull;
const int INF =0x3f3f3f3f;
const int N =2e5+5;
char s[N], t[N];
int sum[N];
int main()
{
int k;
cin >> k >> s+1 >> t+1;
memset(sum,0,sizeof(sum));
//sum[0]存可能多出来的一位,比如6+6=12,sum[0]=1;
for (int i = k; i >=1; i--) {//两字符串相加
sum[i]+= s[i]-'a'+ t[i]-'a';
if (sum[i] >=26) {//进制,注意是等于。
sum[i] -= 26;
sum[i - 1]++;
}
}
for (int i = 0; i <=k; i++) {//除2
sum[i + 1]+= (sum[i] % 2)*26;//上一位的余数留到下一位,想想两个数相除的过程。
sum[i] /= 2;
}
for(int i=1;i<=k;i++)
printf("%c",sum[i]+'a');
printf("\n");
return 0;
}
G - Two Merged Sequences
题意:
给定一个序列,问能否拆成两个序列,其中一个严格递增,一个严格递减,并且元素的相对位置保持不变。把分成的两个序列输出,否则输出NO。
题解:
错误想法:
一开始我是想求出LIS(最长上升子序列),然后判断剩余元素满不满足递减。然后wa了,因为LIS可能有多种情况,比如这个序列 1 1 0 2 3,LIS 如果找了 0 2 3,那么就是错的,1 2 3才可以找到正确答案。如果把所有可能答案找出来,时间复杂度就受不了,比如递减序列。(可能有更好的算法,我想不到nlogn把所有可能LIS找出来,还要判断剩余满不满足递减)。
正确做法:
其实可以维护一个递减序列和一个递增数列,对于a[i]分几种情况。
1、只满足其中一个序列,这时只能放其中一个序列(好像是废话)
2、都不满足,这时便无解了。
3、两种都满足。可以这样想,大的数放在下降数列,对以后的,两个序列可以容纳的范围更大。换句话说,就是对以后影响较小。小的数则放在上升序列。参照物取a[i+1].
AC代码如下:
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <string>
#include <cmath>
#include <ctime>
#include <queue>
#include<stack>
#include <map>
#include <set>
#include<fstream>
#define lowbit(x) x&(-x)
using namespace std;
typedef unsigned long long ull;
const int INF =0x3f3f3f3f;
const int N =2e5+5;
int a[N], up[N], down[N],vis[N];
int main()
{
int n,j,k,flag;
memset(vis,0,sizeof(vis));
up[0] = -INF, down[0] = INF;
j = k = 0; flag = 1;
cin >> n;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for (int i = 1; i <= n; i++) {
if (a[i] >= down[j] && a[i] <= up[k]) {//第2种情况
flag = 0;
break;
}
else if (a[i]<down[j] && a[i]>up[k]) {//第3种情况
if (a[i] > a[i + 1]) {
down[++j] = a[i];
vis[i] = 1;
}
else {
up[++k] = a[i];
}
}
else if (a[i] < down[j]) {//第1种情况
down[++j] = a[i];
vis[i] = 1;
}
else {
up[++k] = a[i];
}
}
if (!flag)
printf("NO\n");
else {
printf("YES\n");
for (int i = 1; i <= n; i++)
printf("%d ",vis[i]);
}
return 0;
}