今天做题赤了一下午史,最后发现是基础不牢的我自己给自己找史吃
Codeforces Educational Codeforces Round 167 (Rated for Div. 2) C
Codeforces Round 960 (Div. 2) C
先看 Educational Codeforces Round 167 (Rated for Div. 2) C
题目大意:
给两串数组a和b,两个数组都是由-1,0,1组成的,你可以选择a[i]或者b[i]进行累加(对于同一个i来说只能选择a或者b中的一个,a[i]加到SumA上,b[i]加到SumB上),最后得到一个值c是SumA和SumB中较小的那个值,让你求c的最大值
举个例子:
假设这是数组a
1 | 0 | -1 | 1 | 1 | 0 | -1 | -1 | 0 | 0 |
这是数组b
0 | 0 | -1 | -1 | 1 | 0 | -1 | 1 | 0 | -1 |
你要在a中选出x个数(x在0~10之间),将这x个数相加就是SumA的值(比如我选了第1,3,5,7,9格中的数字,那SumA就是1+(-1)+1+(-1)+0=0),然后再把b中其他位置上的数相加就是SumB的值(也就是2,4,6,8,10格中的数组相加,SumB=0+(-1)+0+1+(-1)=-1),最后c的值就是两者较小的那个,也就是-1,现在对于给定的a,b数组要求最大可能的c
算法其实并不难,不要用两个数组的方式来想,而是将上下对应位置的数合并成数值对(pair<int,int>),显然,如果两个数值不相等,那么肯定是选择更大的那个数更好,因为哪怕它不会增加较小的数,也能让较大的那个更大一点,至少不会让较小的那个数变得更小,选择较小的那个数只会更糟,所以我们可以先遍历一遍两个数组(两个指针同时向前),然后就可以确定下来那些不同的值该选那一个,剩下的就是两个一样的情况了,我们来看:
如果都是0,0那么不管怎么选都没有影响,所以不用管它,那么都是-1,-1和都是1,1的情况,该如何抉择呢
要注意,最后c的值是较小的那个数,所以1,1要加在较小的那个数上才有意义,同理,面对都是-1,-1的情况,我们要保护较小的那个数,这样这道题就完成了
下面看代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
int n;
cin >> n;
while (n--)
{
int sum1 = 0, sum2 = 0;
int m;
cin >> m;
int a = 0;
int b = 0;
vector<pair<int, int>> list(m);
vector<pair<int, int>> dif;
for (int i = 0; i < m; i++)
{
cin >> list[i].first;
}
for (int i = 0; i < m; i++)
{
cin >> list[i].second;
if (list[i].second == list[i].first)
{
pair<int, int>temp;
temp.first = list[i].first;
temp.second = temp.first;
dif.push_back(temp);
}
}
for (int i = 0; i < m; i++)
{
if (list[i].first > list[i].second)
{
sum1 += list[i].first;
}
else if (list[i].first < list[i].second)
{
sum2 += list[i].second;
}
}
for (int i = 0; i < dif.size(); i++)
{
if (dif[i].first >= 0)
{
if (sum1 < sum2)
{
sum1 += dif[i].first;
}
else
{
sum2 += dif[i].first;
}
}
else
{
if (sum1 < sum2)
{
sum2 += dif[i].first;
}
else
{
sum1 += dif[i].first;
}
}
}
cout << min(sum1,sum2) << endl;
}
}
接下来是Codeforces Round 960 (Div. 2)C. Mad MAD Sum
题目的大致意思是定义一个MAD运算,对于一个数组,MAD(i)的值就是该数组前i个数中出现过两次以上的数里最大的那个(如果没有元素出现过两次那么MAD的值就是0),然后对于给定的数组a,进行这样的操作:对a中所有元素求和加到sum上,然后把a中所有元素替换成MAD(i)(也就是a[i]变成MAD[i]),然后重复以上操作直到数组a中所有元素都为0,求sum的值
算法的思考过程还是很有意思的
乍一看完全不知道它想干什么,但是如果自己随便写一个数组去模拟一边这个过程的话就会发现其中的规律:当你对一个随机的数组进行一次这个操作之后,数组a就会变成一个非递减的数组,举个例子:
假设这是原来的数组a
3 | 1 | 1 | 2 | 2 | 3 | 3 | 1 |
那么经过一次求和之后数组就会变成
0 | 0 | 1 | 1 | 2 | 2 | 3 | 3 |
然后我们再来看对这个非递减的数组继续上述过程会得到
0 | 0 | 0 | 1 | 1 | 2 | 2 | 3 |
再来几次规律就非常明显了:
0 | 0 | 0 | 0 | 1 | 1 | 2 | 2 |
0 | 0 | 0 | 0 | 0 | 1 | 1 | 2 |
0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
可以发现其实就是一直在将第二次得到的数组右移(每次移动都要对每个元素求和)
所以第二次的数组只要对每个值*(数组长度-它的位置)就能得到最后累加的值(数组长度-它的位置就是它右移多少次之后才消失,也就是总共会被计算的次数)
但是先别急,还没有结束,我们来看一种特殊的情况
假如初始数组a是这样的:
4 | 3 | 2 | 1 | 1 | 2 | 3 | 4 |
那么经过一次求和之后会变成这样:
0 | 0 | 0 | 0 | 1 | 2 | 3 | 4 |
如果再求一次和就变成了
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
这种情况下就不是右移数组了,为什么会这样呢,我们可以再回想一下MAD是如何计算的
“MAD(i)的值就是该数组前i个数中出现过两次以上的数里最大的那个(如果没有元素出现过两次那么MAD的值就是0)”
我们可以这样来想,当前面没有出现过两次以上的数字时,所有元素下次都是0,这构成了底座:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
而如果从某一个位置上(比如第二个)开始出现了两次以上的数时,这个数的值就会覆盖给后面的所有数字(如果后续是例如2,3,4,5,6,那么下一次就会都是1,1,1,1,1,1)
1 | 1 | 1 | 1 | 1 | 1 | ||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
如果后面出现了更大的两次以上的数,那么从那个位置往后,继续覆盖上更大的值
2 | 2 | 2 | 2 | ||||
1 | 1 | 1 | 1 | 1 | 1 | ||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
以此类推。。。
最后我们得到的就是在最上层的数字,如上图中,我们得到的就是0,0,1,1,2,2,2,2
而要达成右移的条件,我们就要保证其中的每个元素都至少出现了两次,才能在过程中保留住每个元素(最后一个数可以只出现一次,因为会被右移吃掉,但是不重要)
我们会发现在上面提到的那个特殊的例子中,尴尬的地方就在于因为前一次是一个无序的数组,而后面得到的数组中没有一个元素是出现了两次的,但是我们再来看看这个经过一次求和之后得到的非递减数组,因为所有相同的元素都会扎堆,所以当有一个数出现了两次之后,该位置的值,和下一个位置的值一定是相同的,因为哪怕下一个位置上出现了更大的数,它也才刚刚出现一次,所以它的值跟前一个是相等的(这也是为什么会出现平移的现象),这样,每个非零元素都会至少出现两次(除了最后一个数)所以,要确保数组符合右移的条件其实很简单,对第一次得到的数组在进行一次求和,后面得到的数组一定是符合平移要求的
能够理解上面这段话,这道题目就很清晰了
下面看我第一次写的代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int m = 0;
cin >> m;
while (m--)
{
int n;
cin >> n;
vector<int> arr(n);
vector<int>arrB(arr.size());
int list[(unsigned long long)2e5]={0};
int sum = 0;
int index=0;
for (int i = 0; i < n; i++)
{
cin >> arr[i];
list[arr[i]]++;
sum += arr[i];
if (arr[i]> index&&list[arr[i]]>=2)
{
index = arr[i];
}
arrB[i] = index;
}
vector<int> arrB2(n);
int index2 = 0;
int count2 = 1;
for (int i = 1; i < n; i++)
{
sum += arrB[i];
if (arrB[i] != arrB[i-1])
{
count2 = 1;
}
else count2++;
if (count2 >= 2 && arrB[i] > index2)
{
index2 = arrB[i];
}
arrB2[i] = index2;
}
for (int i = 1; i < n; i++)
{
sum += arrB2[i] *(n-i);
}
cout << sum << endl;
}
return 0;
}
果不其然没过
超出int类型的范围了,行,我认
改成double
能想出来这种测试例的也是神人了,范围还能超两次
彳亍,那我再写个取模的算法,最后再用字符串的方式输出
for (int i = 1; i < n; i++)
{
sum += (double)arrB2[i] *(n-i);
if (sum >= 1e9)
{
while (sum >= 1e9)
{
sum -= 1e9;
num++;
}
}
}
if(num==0)
printf("%.0f\n", sum);
else if (num >= 1)
{
string temp;
temp = to_string(sum);
cout << num;
for (int i = 0; i < 16 - temp.size(); i++)
{
cout << '0';
}
printf("%.0f\n",sum);
}
这次过了
然后发现其实根本不需要写这么麻烦,被自己蠢到了,也怪自己基础知识不行,连double的范围和类型转换的知识都不知道。。。
double类型其实是可以表示比这大得多的数字的,但是我在计算sum += arrB2[i] *(n-i);的时候没有进行类型转换,等式右边还是int类型,那个取模的计算方式完全没有必要,加上(double)就可以了
最终的代码如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int m = 0;
cin >> m;
while (m--)
{
int n;
cin >> n;
vector<int> arr(n);
vector<int>arrB(arr.size());
int list[(unsigned long long)2e5 + 1] = { 0 };
double sum = 0;
int index = 0;
for (int i = 0; i < n; i++)
{
cin >> arr[i];
list[arr[i]]++;
sum += arr[i];
if (arr[i] > index && list[arr[i]] >= 2)
{
index = arr[i];
}
arrB[i] = index;
}
vector<double> arrB2(n);
int index2 = 0;
int count2 = 1;
for (int i = 1; i < n; i++)
{
sum += arrB[i];
if (arrB[i] != arrB[i - 1])
{
count2 = 1;
}
else count2++;
if (count2 >= 2 && arrB[i] > index2)
{
index2 = arrB[i];
}
arrB2[i] = index2;
}
for (int i = 1; i < n; i++)
{
sum += (double)arrB2[i] * (n - i);
}
printf("%.0f\n", sum);
}
return 0;
}