Educational Codeforces Round 97 (Rated for Div. 2)
传送门(点击传送)
A. Marketing Scheme
题意:
顾客要买 x 件商品,现在商品每 a 件为一套包装,顾客的策略为先买
⌊
x
a
⌋
\left \lfloor \frac{x}{a} \right \rfloor
⌊ax⌋个整包装,再买剩余的 x mod a 个单品,如果 x mod a 的数量大于等于
a
2
\frac{a}{2}
2a(即
x
%
a
⩾
a
2
x\%a\geqslant\frac{a}{2}
x%a⩾2a),那么顾客则会选择再买一个整套。现在顾客有一个想要购买的个数范围 [ l , r ] ,问对于这个范围里所有的数是否能有一个固定的 a ,让顾客能够完成多买(顾客买的商品多于他一开始想要买的数量)。
思路:
通过 l 算出这个范围,我们可以选定最大的 a,这个 a 就是
2
×
n
2 \times n
2×n,所以只要判断是否有
r
<
2
×
n
r < 2 \times n
r<2×n即可。
代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t,l,r,limit;
cin>>t;
while(t--){
cin>>l>>r;
if(r<l*2) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
B. Reverse Binary Strings
题意:
现在有一串长度为 n ( n 为偶数 ) 的01串,且有
n
2
\frac{n}{2}
2n个1和
n
2
\frac{n}{2}
2n个0,现在你可以选择任意的子串进行反转操作,问最少的操作次数使得每一位与相邻的位字符不同。即改成(010101… 或101010…)
思路:
分别与标准串010101…和101010…进行对比操作。定义连续的不同区域为“位置连续且每个位置上都与标准串不同”,则答案就是连续的不同区域的个数。(对于连续的不同区域的长度如果是偶数,则这个区域自己进行反转则可得到该区域的标准串。对于长度为奇数的区域,一定还会存在另一个长度为奇数的区域,且两个区域再加上中间的正常区域的总字符个数一定为偶数(可推导),所以操作为先对中间的正常区域进行反转,再对两个连续不同区域以及中间的正常区域的总区域进行翻转,则可回复该区域的标准串)。
代码:
#include<bits/stdc++.h>
using namespace std;
int solve(int &n,string &s,string &ss){
int cont=0;
for(int i=0;i<n;i++){
if(s[i]!=ss[i]){
while(i+1<n && s[i+1]!=ss[i+1]){
i++;
}
cont++;
}
}
return cont;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t,n;
string s;
cin>>t;
while(t--){
int ans=1e9+7,ct,i;
bool flag=true;
cin>>n>>s;
string s1,s2;
for(int i=0;i<n;i++){
if(flag){
s1.push_back('0');s2.push_back('1');
}else{
s2.push_back('0');s1.push_back('1');
}
flag=!flag;
}
cout<<min(solve(n,s,s1),solve(n,s,s2))<<endl;
}
return 0;
}
C. Chef Monocarp
题意:
现在有一个厨师把菜都放入了烤箱,现在需要把菜拿出来,从第一分钟开始且他在每分钟最多拿出一道菜,规定每道菜有一个最优时间 ti ,如果没在第 ti 分钟拿出该道菜,设当前为第T分钟,则会有一个不高兴值为 | T - ti |,现在要尽可能的让不高兴值之和最小,问最小为多少。
思路:
动态规划,首先我们先将 ti 按照从小到大的顺序排好,我们设dp[i][j]为前 i 道菜在第 j 分钟拿完时所产生的不高兴值之和的最小值,那么这个状态可以由前 i-1 道菜在第 i-1~j-1 这个时间段拿完的所有的状态转移而来,所以状态转移方程为:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
i
−
1
]
,
d
p
[
i
−
1
]
[
i
]
,
d
p
[
i
−
1
]
[
i
+
1
]
,
.
.
.
,
d
p
[
i
−
1
]
[
j
−
1
]
)
+
∣
j
−
t
i
∣
dp[i][j]=min(dp[i-1][i-1],dp[i-1][i],dp[i-1][i+1],...,dp[i-1][j-1])+|j-t_{i}|
dp[i][j]=min(dp[i−1][i−1],dp[i−1][i],dp[i−1][i+1],...,dp[i−1][j−1])+∣j−ti∣,初始化第dp[0][0…2n]为0,其余均为0x3f3f3f3f,边界最高为 2n 分钟足够。
代码:
#include<bits/stdc++.h>
using namespace std;
int dp[205][400],t[205];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int q,n;
cin>>q;
while(q--){
cin>>n;
for(int i=1;i<=n;i++) cin>>t[i];
sort(t+1,t+n+1);
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<=2*n;i++) dp[0][i]=0;
for(int i=1;i<=n;i++){
for(int j=i;j<=2*n;j++){
for(int k=i-1;k<j;k++){
dp[i][j]=min(dp[i][j],dp[i-1][k]+abs(t[i]-j));
}
}
}
int ans=1e9+7;
for(int i=n;i<=2*n;i++) ans=min(ans,dp[n][i]);
cout<<ans<<endl;
}
return 0;
}
D. Minimal Height Tree
题意:
有一棵树,每个节点的子节点都是从小到大的顺序排列的。现在按照下图的方式进行 BFS 得到访问序列 a ,现在树已经丢失,让根据访问序列还原一种可能的原来的树,并使得这棵树的深度最小,输出这个最小深度。
思路:
BFS,把访问序列按照升序分割,每一段升序的序列都可以作为当前BFS所考虑的节点的子节点。(例如1,2,4,5,3,6, 先压入1对应的深度——0,队列为 [ 0 ] ,开始 BFS ,寻找升序序列,找到2,4,5,分别压入当前队首深度加一——1,队列为 [ 0 , 1 , 1 , 1 ] ,弹出队首元素,队列为 [ 1 , 1 , 1 ] 。再继续寻找升序序列,找到3,6,分别压入当前队首深度加一,队列为 [ 1 , 1 , 1 , 2 , 2 ],弹出队首元素,队列为 [ 1 , 1 , 2 , 2 ]。最后找出队列中剩余元素的最大值即为答案。)
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int tree[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t,n,ans;
cin>>t;
while(t--){
ans=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>tree[i];
}
queue<int>que;
que.push(0);
for(int i=2;i<=n;i++){
int node=que.front();
que.push(node+1);
while(i+1<=n && tree[i+1]>tree[i]){
i++;
que.push(node+1);
}
que.pop();
}
while(!que.empty()){
ans=max(ans,que.front());
que.pop();
}
cout<<ans<<endl;
}
return 0;
}
E. Make It Increasing
题意:
有一个序列 a ,以及一个 k 个元素的集合 b ,现在可以进行操作,当 i
∉
\notin
∈/b时,可以将
a
i
a_{i}
ai 改为任意值 x ,现在问能否通过上述操作让序列 a 满足
a
1
<
a
2
<
a
3
<
.
.
.
<
a
n
a_{1}< a_{2}<a_{3}<...<a_{n}
a1<a2<a3<...<an,如果不能,输出 -1 ,如果可以,输出最少的操作次数。
思路:
首先将所有的
a
i
(
i
∈
b
)
a_{i}(i\in b)
ai(i∈b) 固定,如果这些固定的数相邻的两个元素的数值差小于它们的位置差,则无论如何操作,都无法使其达到题目要求,所以无方案。当所有固定的相邻元素中间都有足够的空间时,我们对每两个元素中间的所有元素进行寻找最长上升子序列操作(寻找的为数值减位置即a[i]-i的最长上升子序列),那么这两个元素中间需要进行的操作次数为中间的数的个数减去最长上升子序列的个数,累计求和即为答案。注意最长上升子序列长度应采取O(nlogn)复杂度的算法。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10,inf=0x3f3f3f3f;
int a[maxn],b[maxn],ans=0;
int n,k;
int solve(int loc_l,int loc_r,int min_val,int max_val)
{
vector<int>vec;
for(int i=loc_l+1,j=0;i<loc_r;i++,j++){
if(a[i]-min_val<i-loc_l || max_val-a[i]<loc_r-i){
continue;
}else{
int val=a[i]-i;
int upp=upper_bound(vec.begin(),vec.end(),val)-vec.begin();
if(upp==vec.size()) vec.push_back(val);
else vec[upp]=val;
}
}
return loc_r-loc_l-1-(int)vec.size();
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
bool flag=true;
cin>>n>>k;
b[0]=0;b[k+1]=n+1;
a[0]=-inf;a[n+1]=inf;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=k;i++) cin>>b[i];
for(int i=1;i<=k+1;i++){
if(a[b[i]]-a[b[i-1]]<b[i]-b[i-1]){
flag=false;
break;
}else{
ans+=solve(b[i-1],b[i],a[b[i-1]],a[b[i]]);
}
}
if(flag){
cout<<ans<<endl;
}else{
cout<<-1<<endl;
}
return 0;
}