cf908(div2)题解(补题)
第一次akdiv2,赛后ak怎么不算是ak呢
比赛链接cf908div2
A
这题是个骗人题,整个比赛会停下来就是一个人赢够了回合数,那么在谁这停下来就是谁赢了整个比赛,不用管每回合赢得规则。
#include<iostream>
using namespace std;
#include<string>
int main()
{
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
string s;
cin >> s;
int A = 0, B = 0;
for (int i = 0; i < s.size(); i++)
{
if (s[i] == 'A')
{
A++;
}
else
{
B++;
}
}
if (s[s.size() - 1] == 'A')cout << "A" << endl;
else cout << "B" << endl;
}
return 0;
}
B
先从最简单的情况考虑,或者说分析一下给的三个条件必须正好满足两个是什么意思
如果序列为一个数a重复三次,那么要满足任意两个条件,都不可避免的同时满足了三个条件
那么自然想到,要将两个条件分配给两个数,让他们分别去实现其中一个条件,比如,有两个5,两个6,那么就可以给两个5分别赋值为1,2,给两个6分别赋值为1 3
那么,我们可以先明确不满足的情况是这样的:出现次数大于等于2的数字小于两个。
而如果出现次数大于等于2的数字大于两个,我们就可以任意找两个数字,找到他们的两次出现位置,分别赋值为1,2,和1,3,然后其他所有位置都是1,就一定满足。
#include<iostream>
#include<unordered_map>
#include<queue>
using namespace std;
const int maxn = 100020;
#include<vector>
int main()
{
int t;
cin >> t;
while (t--)
{
int a[maxn] = { 0 };
int b[maxn] = { 0 };
int n;
cin >> n;
unordered_map<int, int>mp; //记录每个数出现了多少次
int count_2 = 0; //记录有多少个数出现次数大于等于2
unordered_map<int, int>asdf;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
mp[a[i]]++;
}
for (int i = 1; i <= n; i++)
{
if (mp[a[i]] >= 2&&asdf.count(a[i])==0)
{
count_2++;
}
asdf[a[i]]++;
}
if (count_2 < 2)
{
cout << -1 << endl;
}
else
{
int firstnum = -1, secondnum = -1;
int index = -1;
for (int i = 1; i <= n; i++)
{
if (mp[a[i]] >= 2)
{
firstnum = a[i];
index = i;
break;
}
}
for (int i = index + 1; i <= n; i++)
{
if (mp[a[i]] >= 2&&a[i]!=firstnum)
{
secondnum = a[i];
break;
}
}
int countfirst = 0, countsecond = 0;
for (int i = 1; i <= n; i++)
{
if (a[i] == firstnum)
{
countfirst++;
b[i] = countfirst;
if (countfirst == 2)
{
break;
}
}
}
for (int i = 1; i <= n; i++)
{
if (a[i] == secondnum)
{
countsecond++;
b[i] = countsecond;
if (countsecond == 2)
{
b[i] = 3;
break;
}
}
}
for (int i = 1; i <= n; i++)
{
if (b[i] == 0)
{
b[i] = 1;
}
}
for (int i = 1; i <= n; i++)
{
cout << b[i] << " ";
}
cout << endl;
}
}
return 0;
}
C
我在赛后过题挑战中取得了比赛一结束立刻就通过的好成绩,你也来试试吧~
这题手动模拟几次可以发现,如果给的那个最终序列最后一个位置是x,那么它的上一个序列其实就是这个序列的后x位移动到序列前面,给定最终序列,那么如此反向操作k次,结果是唯一的。
如果中间发现这个最后一个数x>n,那么就说明不可能。
如果我们想模拟这个反向操作,可以把序列复制很多次,也可以用模运算,具体见代码:
#include<iostream>
using namespace std;
const int maxn = 200200;
#include<unordered_map>
#include<string>
#include<cstring>
long long a[maxn];
int main()
{
int t;
cin >> t;
int tt = t;
while (t--)
{
memset(a, 0, sizeof a);
long long n, k;
cin >> n >> k;
for (int i = 0; i <n; i++)
{
cin >> a[i];
}
int flag = 1;
long long count = 0;
unordered_map<long long, long long >num;
for (long long i = n - 1; count <k;i=((i-a[i])+n)%n) //count<k而不是<=k,因为要求是正好k次
{
if (a[i]>n)
{
flag = 0;
break;
}
count++;
if (num.count(i) != 0) //遇到了重复下标,说明进入一个循环的过程了,就一定可以
{
flag = 1;
break;
}
num[i]++;
}
if (flag == 1)cout << "Yes" << endl;
else if(flag==0)cout << "No" << endl;
}
return 0;
}
分割线,赛时就想出来了这三道题,C题还因为<=k这个愚蠢bug没调出来,后面是补题,待更新
D
首先,最长上升子序列的长度一定不会变小,因为无论怎么加,原本的序列相对位置不变,所以至少可以取和原本一样的最长上升子序列
写成式子就是:LIS(C)>=LIS(A)
然后是LIS(C)<=LIS(A)+1,因为将b序列从大到小插入到A序列中的某一个位置,b序列对于A的LIS的增长贡献不会超过1,因为新加入的这些元素两两不能位于同一个上升子序列,所以说这样加入的B序列里面最多有一个数会使得A的某个上升子序列加1
下面考虑是否可以让LIS(C)==LIS(A),是可以的,和刚才的考虑类似,即按照不能位于同一上升子序列来考虑,假设只插入一个数x,那么要么这个数小于所有的数,把它放到最后,就不会让LIS增加,要么这个数不是最小的,那么可以找到一个比他小的数y,把x插入到y的前一个位置,这样x和y不可能出现在同一个出现在同一个上升子序列中,那么插入之后所有的包含x的上升子序列都可以把x用y来代替,即x的插入不会改变上升子序列的长度。那么对于B序列的所有元素都这么做,为了方便并且防止b序列完整进入A序列自身产生上升子序列,我们让每个数插入到第一个比他小的数前面,那么最终的结果就是把B倒序排序,然后和A序列进行一种类似归并排序的过程(C++甚至有merge函数可以完成)
代码如下:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
vector<int> a(n), b(m);
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < m; i++)
cin >> b[i];
sort(b.begin(), b.end(), greater<int>());
int i = 0, j = 0;
vector<int> c(n + m);
int k = 0;
while (i < n && j < m)
{
if (a[i] > b[j])
{
c[k] = a[i++];
k++;
}
else
{
c[k] = b[j++];
k++;
}
}
while (i < n)
{
c[k++] = a[i++];
}
while (j < m)
{
c[k++] = b[j++];
}
for (int jk = 0; jk < n + m; jk++)
{
cout << c[jk] << " ";
}
cout << endl;
}
return 0;
}
E
应该算是一种贪心做法,假定最后的集合大小是len,从题意中可以想到肯定有len的集合要少取,没有len的集合要多取,对于每个len可以用这样的一个大致的策略求得一个值,然后取最小。那么len一共有多少个呢,定义sumn,suml,sumr为n,l,r的求和,那么lne的范围应该是suml~sumr,也就是一共有sumr-suml+1种len,而X集合里面可能出现的元素种类只有sumn,如果len的种类数多余集合X中的可能的元素种类数,那么根据抽屉原理,一定可以在len的若干个可能取值中找到至少一个不会出现在X中的值,那么这种情况答案就是0,也就是说,如果sumr-suml+1>sumn,那么答案就是0
那如果sumr-suml+1<=sumn,就得按照贪心策略来取,首先是,对于不含len的集合,必须取满上限r,然后对于含有len的集合,我们可以根据题给的c数组确定len在这个集合有多少个,然后能够得出在这个集合中有多少个‘非len’,如果‘非len’的个数很少,甚至不足以选够l,那么我们为了满足l这个限制就不得不取l-not_len个len,如果‘非len’的个数足够取l,那么我么尽可能多的取‘非len’.这样取完之后,看此时填充到X中的个数,如果不够len,就意味着必须再从那些‘非len’不够的集合继续取一些len
代码如下
#include <bits/stdc++.h>
using namespace std;
#define int long long // 数据很大,会爆int
signed main()
{
int t;
cin >> t;
while (t--)
{
int m;
cin >> m;
long long sumn = 0, suml = 0, sumr = 0;
vector<int> n(m), l(m), r(m);
vector<vector<int>> a(m);
vector<vector<int>> c(m);
vector<int> sumc(m); // 记录每个集合的元素总个数
for (int i = 0; i < m; i++)
{
cin >> n[i] >> l[i] >> r[i];
sumn += n[i];
suml += l[i];
sumr += r[i];
a[i].resize(n[i]); // 第i个集合的大小为n[i]
c[i].resize(n[i]);
for (int j = 0; j < n[i]; j++)
{
cin >> a[i][j];
}
for (int j = 0; j < n[i]; j++)
{
cin >> c[i][j];
sumc[i] += c[i][j];
}
}
if (sumr - suml > sumn)
{
cout << 0 << endl;
/*
X集合的元素个数的范围是suml~sumr,它有sumr-suml+1种取值,
而所有集合的元素种类数最多sumn种
如果sumr-suml+1>sumn
那么sumr-suml+1个数字里面一定至少有一个数是sumn种数里没有的
抽屉原理
*/
continue;
}
else
{
unordered_map<long long, long long> sum_have; // sum_have[asdf]记录含有asdf这个值的集合如果全都选满r个,共选了多少个
unordered_map<long long, vector<pair<int, int>>> indexs; // indexs记录的是某个值在所有集合中出现了多少次,位置在哪
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n[i]; j++)
{
sum_have[a[i][j]] += r[i];
indexs[a[i][j]].push_back({i, j});
}
}
// 选的策略是,含有len的,如果非len少于l就按照l选,如果非len多就尽可能多选非len,不含len的按照r来选,如果这样选之后不到len,那么只能再往里加若干个len
long long ans = (int)2e18;
for (long long len = suml; len <= sumr; len++)
{
long long xsize = 0; // 表示的是集合X的元素个数
long long have_to_choose_len = 0; // 不得不选的len的个数,也就是说某个集合中非len值太少,甚至不够l
xsize = sumr - sum_have[len]; // 集合X的个数初始化为:所有不含len的集合都按照r选
// 所有集合都选满r的值减去这个sumr_a[len]就是所有不含len的集合全部选满r的值
for (auto &[i, j] : indexs[len]) // 便利len出现的集合
{
int not_len = sumc[i] - c[i][j]; // 第i个结合中,所有元素的个数减去len在这里面的个数就是不是len的个数
if (not_len < l[i]) // 如果这个集合中非len元素甚至不够l,那么不得不选一些len
{
xsize += l[i];
have_to_choose_len += l[i] - not_len;
}
else
{
xsize += min(not_len, r[i]); // 如果非len值,尽可能多选非len值
}
}
ans = min(ans, have_to_choose_len + max(0LL, len - xsize));
// 对于每个len的答案就是必须选的值为len的答案(某个集合不等于len的值太少了,要选满至少l个不得不在这个集合选值为len的)
// 加上当前已经按照这个策略选的xsize和目标len之间的差值,如果当前的xsize还不够len,那么那些含有len的集合中不得不在选了l个之后继续选一些len
}
cout << ans << endl;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏