Codeforces Round #998
第一场自己刷的 CF,貌似是贪心 + 数学场,除了 E 题都很水。
A Balloons(构造 + 贪心)
传送门:A
共有 \(n\) 袋气球,第 \(i\) 袋有 \(a_i\) 个气球。
现在要将这 \(n\) 袋气球分给两个人,每人至少分到一袋,且两人的气球总数不等,求出其中一个人分到的袋数和分到的袋子的编号(输出一种情况即可)
如果不存在答案,输出 \(-1\)。
Sol A
构造题先判断不存在的情况,显然只有两种可能,即只有一袋气球或两袋相等数量的气球,无论如何也不能满足题意。
然后对于其余情况,可以考虑这样构造:把最少的一袋气球分给第一个人,剩余的分给另一个人,这样一定可以保证两人所得气球数不相等。时间复杂度 \(O(n)\)。(原题数据范围好小)
#include<bits/stdc++.h>
using namespace std;
int n,minn = 1 << 29,pos;
int a[20];
signed main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
if(n == 1)
{
printf("-1\n");
return 0;
}
if(n == 2 and a[1] == a[2])
{
printf("-1\n");
return 0;
}
for(int i = 1;i <= n;i++)
if(minn > a[i]) pos = i,minn = a[i];
printf("1\n%d\n",pos);
return 0;
}
B Cuttings(贪心 + 堆)
传送门:B
有一个整数序列,其偶数和奇数的数量相同。给定有限的预算,您需要尽可能多地进行切割,使每个结果段具有相同数量的奇数和偶数。
“切割”操作将一个序列分成 \(2\) 个连续的段。您可以将每次“切割”看作是序列中两个相邻元素之间的中断。所以在切割之后每个元素恰好属于一个片段。
举个例子,\([4,1,2,3,4,5,4,4,5,5]\to\) 两次“切割”操作 \(\to[4,1|2,3,4,5|4,4,5,5]\)。在每个切割后的段上,偶数元素的个数应该等于奇数元素的个数。
在元素 \(x\) 和元素 \(y\) 之间进行一次“切割”操作的价值为 \(|x-y|\) 元。请问,在 \(B\) 元内,最多可以切割几次?
Sol B
题解区很多用 dp 做的大佬(貌似以后自己会再用 dp 写一遍吧),这里提供一种贪心的思路。由于我们每次切割之后不会影响其它位置的切割情况,所以我们本着尽可能多切的原则,先找代价小的地方切,直到花费不允许了为止。之后可以预处理可切割的地方以及花费,并用小根堆维护花费。时间复杂度 \(O(nlogn)\)。(原题数据范围好小)
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n,m,ans;
int a[N],f[N],g[N];
priority_queue <int,vector<int>,greater<int>> q;
signed main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++)
scanf("%d",&a[i]);
for(int i = 1;i <= n;i++)
{
f[i] = f[i-1],g[i] = g[i-1];
a[i] & 1 ? f[i]++ : g[i]++;
}
for(int i = 2;i <= n;i++) if(f[i-1] == g[i-1]) q.push(abs(a[i] - a[i-1]));
//cout << q.size() << endl;
while(not q.empty())
{
int t = q.top();
q.pop();
//cout << t << endl;
if(t <= m) ans++, m -= t;
else break;
}
printf("%d\n",ans);
return 0;
}
C Convert to Ones(贪心)
传送门:C
给你一个长度为 \(n\) 的 01 串(\(n\leq 3\times 10^5\)),你有两种操作:
1.将一个子串翻转,花费 \(X\)
2.将一个子串中的 0 变成 1,1 变成 0,花费 \(Y\)
求你将这个 01 串变成全是 1 的串的最少花费。
Sol C
相邻相同的显然不会对答案产生影响,所以我们考虑将相同相邻的合并成一段,用 0 或 1 来代表这一段中的数字是什么,这样我们得到了一个交错的 01 序列。
翻转操作的意义在于我们对于010…… 中的 10 进行翻转后可以将两个 0 合并成一段,然后可以一起取反操作。而取反操作在于一段 0 变成 1。
现在假设我们有 \(cnt\) 段 0,那么我们一定会至少进行一次取反操作。剩下的 \(cnt-1\) 段 0,我们要么直接取反把它变成 1,要么通过翻转把它和旁边的 0 合并成一段,其效果是一样的。
翻转:\(01010\to 01110\to 010\)
反转:\(01010\to 01100\to 010\)
所以使用花费小的操作就可以啦
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 300000 + 10;
int n,x,y,cnt;
char s[N];
signed main()
{
scanf("%lld%lld%lld",&n,&x,&y);
scanf("%s",s + 1);
for(int i = 1;i <= n;i++)
{
if(s[i] == s[i-1]) continue;
if(s[i] == '0') cnt++;
}
if(cnt == 0) printf("0\n");
else printf("%lld\n",min(x,y) * (cnt - 1) + y);
return 0;
}
D Roman Digits
传送门:D
我们在研究罗马数字。罗马数字只有 4 个字符,I,V,X,L 分别代表 1,5,10,100。一个罗马数字的值为该数字包含的字符代表数字的和,而与字符的顺序无关。例如 \(XXXV=35,IXI=12\)。
现在求问一个长度为 \(n\) 的罗马数字可以有多少种不同的值。(\(n\leq 10 ^9\))
Sol D
打表题,\(12\) 个后面是等差数列,公差为 \(49\)。前 \(11\) 个打表。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int ans[15] = {0,4,10,20,35,56,83,116,155,198,244,292,341};
signed main()
{
scanf("%lld",&n);
if(n <= 12) printf("%lld\n",ans[n]);
else printf("%lld\n",ans[12] + (n - 12) * 49);
return 0;
}
E Sky Full of Stars
传送门:E

浙公网安备 33010602011771号