A. LRC and VIP
翻译:
您有一个大小为 n 的数组 a ——
。
您需要将 n 元素分成 2 个 序列 B 和 C满足以下条件:
- 每个元素只属于一个序列。
- 两个序列 B 和 C 至少包含一个元素。
思路:
gcd的值必然会越来越小,那么可以将a的最大值单独一类,其余一类。
实现:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MX = 2e5+10;
void solve(){
int n;cin>>n;
vector<int>a(n),b(n);
for (auto& i:a)cin>>i;
for (int i=0;i<n;i++) b[i] = i;
sort(b.begin(),b.end(),[&](int x,int y){
return a[x]<a[y];
});
if (a[b[0]]!=a[b[n-1]]){
cout<<"Yes"<<endl;
vector<int> ans(n,1);
ans[b[n-1]] = 2;
for (int i=0;i<n;i++) cout<<ans[i]<<" ";cout<<endl;
}else{
cout<<"No"<<endl;
}
}
int main(){
// 关闭输入输出流同步
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 不使用科学计数法
// cout<<fixed;
// 四舍五入中间填保留几位小数,不填默认
// cout.precision();
int t;
cin>>t;
while (t--){
solve();
}
return 0;
}
B. Apples in Boxes
翻译:
汤姆和杰瑞在地下室找到了一些苹果。他们决定玩一个游戏来获得一些苹果。
一共有 n 个盒子,第 i 个盒子里有
个苹果。汤姆和杰瑞轮流捡苹果。汤姆先捡。轮到他们时,他们必须做以下事情:
- 选择第 i 个(1≤i≤n)装有正数苹果的盒子,即
,然后从这个盒子里摘 1 个苹果。注意,这样会将
减少 1。
- 如果没有有效的盒子,当前棋手就输了。
- 如果移动后
成立,那么当前棋手(下最后一步棋的棋手)也会输。
如果双方都下得很好,请预测对局的胜负。
思路:
最佳策略必然是取有最大值的盒子,因为能最大可能避免第三种情况。那么除了一拿就输的情况,只有拿完的情况。分类讨论即可。
实现:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MX = 2e5+10;
void solve(){
ll n,k;
cin>>n>>k;
ll maxx = 0,max_cnt = 0,minn = LLONG_MAX,summ = 0;
for (ll num,i=1;i<=n;i++){
cin>>num;summ+=num;
if (maxx<num){
maxx = num;
max_cnt = 1;
}else if (maxx==num) max_cnt++;
minn = min(num,minn);
}
ll now = (max_cnt>=2 ? maxx : maxx-1);
if (now-minn>k){
cout<<"Jerry"<<endl;
}else{
if (summ%2==0) cout<<"Jerry"<<endl;
else cout<<"Tom"<<endl;
}
}
int main(){
// 关闭输入输出流同步
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 不使用科学计数法
// cout<<fixed;
// 四舍五入中间填保留几位小数,不填默认
// cout.precision();
ll t;
cin>>t;
while (t--){
solve();
}
return 0;
}
C. Maximum Subarray Sum
翻译:
给你一个长度为 n 的数组
和一个正整数 k,但是数组 a 的某些部分缺失了。你的任务是补全缺失的部分,使 a 的最大子数组和∗ 恰好为 k,否则就报告不存在解法。
在形式上,我们给你一个二进制字符串 s 和一个部分填充的数组 a,其中:
- 如果你记得
的值,则
表示该值,然后给你
的实际值。
- 如果你不记得
的值,则
表示该值,并给出
。
你记住的所有值都满足
的条件。但是,您可以使用最大
的值 来填充你不记得的值。可以证明,如果存在一个解,那么也存在一个满足
的解。
.
思路:
在第一个不确定点出现的地方,令该点的值为k减其自左右点出发的最大和。其余不确定点都赋值为
,以避免可能导致的最大和变化。(构造)
实现:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MX = -2e12;
void solve(){
ll n,k;cin>>n>>k;
string s;cin>>s;
s = ' '+s;
vector<ll> a(n+1);
for (ll i=1;i<=n;i++) cin>>a[i];
for (ll f=0,i=1;i<=n;i++){
if (s[i]=='0'){
if (f) {a[i] = MX;continue;}
ll lm = 0,lmm = -1;
for (ll j=i-1;j>=1;j--){
if (s[j]=='0') break;
lm+=a[j];
lmm = max(lm,lmm);
}
ll rm = 0,rmm = -1;
for (ll j=i+1;j<=n;j++){
if (s[j]=='0') break;
rm+=a[j];
rmm = max(rm,rmm);
}
lmm = max(0ll,lmm);
rmm = max(0ll,rmm);
a[i] = k-lmm-rmm;
f = 1;
}
}
ll tmp=0,maxx =0,f=0;
for (int i=1;i<=n;i++){
tmp=max(0ll,a[i]+tmp);
maxx = max(maxx,tmp);
if (maxx>k) {
cout<<"No"<<endl;
return;
}else if (maxx==k) f = 1;
}
if (!f){
cout<<"No"<<endl;
return;
}
cout<<"Yes"<<endl;
for (int i=1;i<=n;i++){
cout<<a[i]<<" ";
}cout<<endl;
}
int main(){
// 关闭输入输出流同步
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 不使用科学计数法
// cout<<fixed;
// 四舍五入中间填保留几位小数,不填默认
// cout.precision();
ll t;
cin>>t;
while (t--){
solve();
}
return 0;
}
D. Apple Tree Traversing
翻译:
有一棵有 n 个节点的苹果树,最初每个节点都有一个苹果。你随身带着一张纸,最初上面什么也没写。
只要苹果树上至少还有一个苹果,你就在苹果树上进行以下操作:
- 选择一条苹果路径 (u,v)。当且仅当路径 (u,v) 上的每个节点上都有一个苹果时,路径 (u,v) 才称为苹果路径。
- 假设 d 是路径上苹果的个数,在纸上依次写下三个数字(d,u,v)。
- 然后把路径(u,v)上的苹果全部移走。
这里的路径 (u,v) 是指从 u 到 v 的唯一最短路径上的顶点序列。
让纸上的数字序列为 a。你的任务是找出可能的最大字典序的序列 a。
思路:
要最大字典序,要优先苹果的数量,再是端点的值。因此先求出树中的最长路径,同时对端点进行比较,将答案放入ans数组,在对剩下来的森林进行上述相同操作,直到所有点都被取完。最后对ans进行排序得到答案。(dfs,贪心)
如何求树的最长路径(也称为直径)。先在树上随便找个点x,通过x进行dfs得到距离最远的u。在对u进行dfs得到v,u->v就为直径。
为什么两个dfs可得到直径?考虑三种情况:
1. 如果 x 在 u->v的路径上,那么x到的最远点必为u/v,那么再次dfs得到的必为u到v的路径。
2. 如果 x 不在 u->v的路径上,在 x 找最远点时经过u到v路径上的点y,从y点再次出发如果得到的最远点不为u/v。说明有更远的距离比y->u或y->v那就与u->v最远不符合。那么y必定会到u/v。
3.如果 x 不在 u->v的路径上,在 x 找最远点时不经过u到v路径上的点,由于是一棵树这两线必定有相联系的点,说明从联系点出发有更远的路径,就与u->v最远不符合。那么y必定会到u/v。
最多进行
轮次的全面取值,每一轮都会对森林进行取直径的操作,那么最坏就是每轮只会取一棵树值从k到1,那么可得到轮数小于根号n。
时间复杂度为:
。
实现:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MX = -2e12;
// 得到树的直径和端点的最大情况
// 进行sqrt(n)轮,每次找直径为n
int n;
void solve(){
cin>>n;
vector<vector<int>> tree(n+1);
vector<array<int,3>> ans;
// 判断点有无被取
vector<int> vis(n+1,0);
// 记录路径
vector<int> pre_path(n+1,-1);
for (int u,v,i=1;i<n;i++){
cin>>u>>v;
tree[u].push_back(v);
tree[v].push_back(u);
}
// dis,to
auto dfs = [&](auto&& dfs,int u,int father=-1)->pair<int,int>{
pair<int,int> tmp = {1,u};
pre_path[u] = father;
for (int v:tree[u]){
if (v==father || vis[v]) continue;
auto nex = dfs(dfs,v,u);
nex.first+=1;
tmp = max(tmp,nex);
}
return tmp;
};
while (1){
if (count(vis.begin()+1,vis.end(),0)==0) break;
// 得到树的直径
for (int i=1;i<=n;i++){
if (vis[i]) continue;
auto [_,u] = dfs(dfs,i);
auto [d,v] = dfs(dfs,u);
ans.push_back({d,max(u,v),min(u,v)});
while (v!=-1){
vis[v] = 1;
v = pre_path[v];
}
}
}
sort(ans.begin(),ans.end(),greater<array<int,3>>());
for (auto [d,u,v]:ans){
cout<<d<<" "<<u<<" "<<v<<" ";
}cout<<endl;
}
int main(){
// 关闭输入输出流同步
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 不使用科学计数法
// cout<<fixed;
// 四舍五入中间填保留几位小数,不填默认
// cout.precision();
ll t;
cin>>t;
while (t--){
solve();
}
return 0;
}
F1. Cycling (Easy Version)
翻译:
Leo 在市中心做程序员,他的爱人在郊区的一所高中教书。每个周末,里奥都会骑着自行车去郊区,和爱人一起度过一个愉快的周末。
现在这条路上有 N 个骑自行车的人在 Leo 前面。他们的编号从前到后依次为 1、2、......、n。最初,李奥在第 n 个骑车人后面。第 i 位骑车人的敏捷值为 ai。
李奥想超过第 1 位骑车人。里欧可以多次采取以下行动:
- 假设李奥前面的第一个人是第 i 位骑车人,他可以以 ai 的代价走在第 i 位骑车人前面。这将使他位于骑自行车者 i-1 的后面。
- 利用他的超能力,将 ai 和 aj 互换(1≤i<j≤n),费用为 (j-i)。
利奥想知道在第 1 个骑车人前面的最小成本。这里只需打印整个数组的答案,即数组 [a1,a2,...,an]。
思路:
dp[i]:为a[i:n]的最小花费。
对于一个数组中最小的第一个数 x 之前的,通过交换最小速度在超车比直接超车速度快,在x之后的部分,考虑将x向后移动再移回来的操作加后面的区间的最小值。后面区间的左端点的值无论如何都不可能比 x 要小,因此这个区间不管怎么操作都不会影响向前的区间,满足了无后效性。(线性dp)
实现:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
// dp[i]:a[i:n] minna cost
void solve(){
ll n;
cin>>n;
vector<ll> a(n+1),dp(n+2,LLONG_MAX);
dp[n+1] = 0;
for (ll i=1;i<=n;i++) cin>>a[i];
for (ll i=n;i>=1;i--){
// first minna position
ll p = i;
for (ll j=i;j<=n;j++){
if (a[j]<a[p]) p = j;
}
for (ll j=p;j<=n;j++){
dp[i] = min(dp[i],a[p]*(j-i+1)+(j-p)+(j-i)+dp[j+1]);
}
}
cout<<dp[1]<<endl;
}
int main(){
// 关闭输入输出流同步
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 不使用科学计数法
// cout<<fixed;
// 四舍五入中间填保留几位小数,不填默认
// cout.precision();
ll t;
cin>>t;
while (t--){
solve();
}
return 0;
}
之后补题会在此增加题解。