Codeforces Round #700 (Div. 2)(D1,D2)(贪心)
D1 - Painting the Array I
题意:
题目给出一个数组 \(a\),其中 \(1\leqslant a[i]\leqslant n\),让你把 \(a\) 分成两个数组 \(a^{(1)},a^{(2)}\),这两个数组中如果两个相邻元素相同就会合并成一个元素。问两个数组元素之和最多可以有多少个。
想法:
- 可以对情况进行分类讨论。设 \(a^{(1)}\) 数组末尾元素为 \(list1\), \(a^{(2)}\) 数组末尾元素为 \(list2\)。
- 我们就可以对 \(a\) 数组进行遍历。
- 第一种情况: \(list1==list2\),若当前 \(a[i]\) 和 \(list1\) 不相等,那么直接放入 \(a^{(1)}\)即可。因为此时放在 \(a^{(1)}\) 和 \(a^{(2)}\) 造成的影响都一样。
- 第二种情况: \(list1!=a[i]\ and\ list2==a[i]\),那么此时 \(a[i]\) 放入 \(a^{(1)}\) ,才能让答案增加。
- 第三种情况: \(list1==a[i]\ and\ list2!=a[i]\),那么此时 \(a[i]\) 放入 \(a^{(2)}\) ,才能让答案增加。
- 第四种情况: \(list1!=a[i]\ and\ list2!=a[i]\),此时需要考虑选择那个对后面更优。那么就要将 \(list1\) 在 \(a\) 数组中下一次出现的下标和 \(list2\) 下一次出现的下标进行比较。谁会更早出现,我们就在哪个数组上放入 \(a[i]\),这样就可以更大可能性的去避免出现后面的 \(a[next]\) 会和 \(list1,list2\) 相同。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100005;
int a[maxn],id[maxn];
vector<int>v[maxn];
vector<int>v1,v2;
int main()
{
int n;
memset(id,0,sizeof id);
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
v[a[i]].push_back(i);
}
int last1=-1,last2=-1,ans=0;
for(int i=1;i<=n;i++){
id[a[i]]++;
if(last1==last2){
if(last1!=a[i])last1=a[i],ans++;
}else if(last1==a[i]&&last2!=a[i]){
last2=a[i],ans++;
}else if(last1!=a[i]&&last2==a[i]){
last1=a[i],ans++;
}else{
if(last1==-1){
last2=a[i];
ans++;
}else if(last2==-1){
last1=a[i];
ans++;
}else{
int num1=n+1,num2=n+1;
if(id[last1]<v[last1].size())num1=v[last1][id[last1]];
if(id[last2]<v[last2].size())num2=v[last2][id[last2]];
if(num1<num2){
last1=a[i];
ans++;
}else{
last2=a[i];
ans++;
}
}
}
}
cout<<ans;
}
D1 - Painting the Array II
题意:
题目给出一个数组 \(a\),其中 \(1\leqslant a[i]\leqslant n\),让你把 \(a\) 分成两个数组 \(a^{(1)},a^{(2)}\),这两个数组中如果两个相邻元素相同就会合并成一个元素。问两个数组元素之和最少可以有多少个。
想法:
- 和D1一样,我们同样需要结合贪心思想进行分类讨论。因为相同元素可以合并,那么让元素最少,我们要把 \(a\) 数组进行合并,把 \(a\) 合并成没有两个相邻元素相同的数组。
- 然后我们对合并完后的 \(a\) 数组进行遍历。
- 第一种情况: \(list1==list2\),若当前 \(a[i]\) 和 \(list1\) 不相等,那么直接放入 \(a^{(1)}\)即可。因为此时放在 \(a^{(1)}\) 和 \(a^{(2)}\) 造成的影响都一样。
- 第二种情况: \(list1!=a[i]\ and\ list2==a[i]\),那么此时 \(a[i]\) 放入 \(a^{(2)}\) ,才能让答案不增加。
- 第三种情况: \(list1==a[i]\ and\ list2!=a[i]\),那么此时 \(a[i]\) 放入 \(a^{(1)}\) ,才能让答案不增加。
- 第四种情况: \(list1!=a[i]\ and\ list2!=a[i]\),此时需要考虑选择那个对后面更优。那么就要将 \(list1\) 在 \(a\) 数组中下一次出现的下标和 \(list2\) 下一次出现的下标进行比较。谁会更晚出现,我们就在哪个数组上放入 \(a[i]\)。这样就使得后面的数更有可能被合并从而使答案最小化。
代码:
int a[maxn],b[maxn],id[maxn];
vector<int>v[maxn];
int main()
{
int n,nn=1;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
b[1]=a[1];
v[b[1]].push_back(1);
for(int i=2;i<=n;i++){
if(a[i]!=a[i-1]){
b[++nn]=a[i];
v[b[nn]].push_back(nn);
}
}
int last1=-1,last2=-1,ans=0;
for(int i=1;i<=nn;i++){
id[b[i]]++;
if(last1==last2){
if(last1!=b[i])last1=b[i],ans++;
}else if(last1==b[i]&&last2!=b[i]){
last1=b[i];
}else if(last1!=b[i]&&last2==b[i]){
last2=b[i];
}else{
if(last1==-1){
last1=b[i];
ans++;
}else if(last2==-1){
last2=b[i];
ans++;
}else{
int num1=nn+1,num2=nn+1;
if(id[last1]<v[last1].size())num1=v[last1][id[last1]];
if(id[last2]<v[last2].size())num2=v[last2][id[last2]];
if(num1<num2){
last2=b[i];
ans++;
}else{
last1=b[i];
ans++;
}
}
}
}
cout<<ans;
}
总结
两道题的贪心思想基本上是一致的。在使用贪心算法时,我们需要考虑的是当前对答案的贡献以及对后面答案的贡献即可,也要考虑这样才能让后面答案更优的可能性更大。
越自律,越自由