一.B
题目链接:https://codeforces.com/contest/2013/problem/B
我先考虑一个简单的问题:对于a1 a2 a3,他的答案是什么呢?考虑a2最后被合并,
先合并a1,那么a2=a2-a1,再合并a3,a3=a3-a2+a1;
考虑a1最后被合并
先合并a3,a3=a3-a2,再合并a1,a3=a3-a2-a1;
很显然第一种方式更大。现在扩展问题,先考虑加一个数a4;
a3最后被合并,那么式子为a4=a4-([1,3]的合并最小),a4=a4-a3+a2+a1
a2最后被合并,那么a4=a4-a3-a2+a1
a1最后被合并,a4=a4-a3+a2-a1
考虑a1,a2,a3...an,假设我们选取ai作为分割点,ai最后合并,对于[1,i]而言,由于ai最后合并所以希望[1,i]合并后ai最小,可以知道ai最小为ai=ai-sum(a[1,i-1]),那么[i+1,n]需要最大,那么an必然需要减去,因为n-1的下标作为减号必然是被更高的标号支配,由于n-1后面只有一个n的下标,那么[i+1,n]的最大值上界为an-+sum(a[i+1,n-2]),于是最后的ai的答案为an=an--ai+(sum--ai);
于是贪心的思考,分割点选择n-1最为合适
代码如下:
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
void solve() {
int n;
cin>>n;
i64 res=0,sum=0;
i64 mm=0;
for(int i=0; i<n; i++){
int t;
cin>>t;
if(i<n-2) sum+=t;
if(i==n-2)mm=t;
if(i==n-1)res=t-(mm-sum);
}
cout<<res<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
二.C
题目链接:https://codeforces.com/contest/2013/problem/C
思路:考虑先往一边加0,1判断,如果两者都不可,同时长度还未达到要求,考虑向另外一边加0,1;注意一个优化,当到另外一边后,查询次数应该是线性的,非0即1;
否则被下面一种情况hack,假设a+b=n,(换边的话b至少为1)
首先一边的01判断为2*(a+1),那么另外一遍的判断应该小于等于2*(b-1);
但是不优化的化判断为2*b,优化后为b
2*a+2+b,似乎还是会被hack,但是需要注意到一个问题,如果串包含01,第一次查询百分百正确,那么最坏情况为2*a+1+b<=2*n;如果串为0串或者1串,那么必然没有换边的情况,2*a<=2*n;
代码如下:
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
void solve() {
int n;
cin >> n;
auto ask = [&](string s) -> int {
cout << "? " << s<< endl;
cout.flush();
int res;
cin >> res;
return res;
};
string s="";
int cnt=n;
char p1[4]={'0','1','1','0'};
bool flag=false;
while(cnt>0 ){
if(!flag){
int num=0;
bool flag1=false;
for(int i=0; i<=1; i++){
string temp=s;
temp=p1[i]+temp;
if(ask(temp)==1){
cnt--;
s=temp;
num--;
flag1=true;
}
if(flag1)break;
num++;
}
if(num==2)flag=true;
}else{
bool flag1=false;
string temp=s;
temp=temp+p1[0];
if(ask(temp)==1){
cnt--;
s=temp;
}
else{
cnt--;
s=s+p1[1];
}
}
}
cout<<"! ";
cout<<s<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
三.D
题目链接:https://codeforces.com/contest/2013/problem/D
有两种做法:
1.二分最大值和最小值
2.前缀平均求最小,后缀平均求最大
第一种做法:
考虑二分最小值的话,可以知道,mid比答案大时,比mid大的也false;比答案小时,还可以提高;check的标准是,前面的值传递到后面是否可以使得最小值提高
考虑二分最大值的话,可以知道,mid比答案大时,比mid大的也一定合法;
比答案小时,一定不合法。check的标准是,向后面传递时,把高于mid的高度,降低的值向后面传递使用,注意不要过量使用,同时这一定不会破坏最小值的条件,因为最小值是需要被补偿的那个。
代码如下:
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
void solve() {
int n;
cin >> n;
vector<i64> a(n);
i64 maxnum = 0;
i64 minnum = 1e13 + 10;
for (int i = 0; i < n; i++) {
cin >> a[i];
maxnum = max(maxnum, a[i]);
minnum = min(minnum, a[i]);
}
auto check = [&](i64 mid) -> bool {
i64 ss=0;
for (int i = 0; i < n ; i++) {
if(a[i]<mid){
if(a[i]+ss<mid)return false;
else {
ss-=(mid-a[i]);
}
}else ss+=a[i]-mid;
}
return true;
};
i64 l = minnum, r = maxnum;
while (l < r) {
i64 mid = (l + r+1 ) >> 1;
if (check(mid)) {
l = mid ;
} else {
r = mid-1;
}
}
auto check1 = [&](i64 mid) -> bool {
i64 ss=0;
for (int i = 0; i < n - 1; i++) {
if(a[i]>mid){
ss+=a[i]-mid;
}
if(a[i]<mid)ss-=min(mid-a[i],ss);
}
if(ss+a[n-1]>mid)return false;
return true;
};
i64 ll=l,rr=maxnum;
while(ll<rr){
i64 mid = (ll + rr) >> 1;
if (check1(mid)) {
rr = mid ;
} else {
ll = mid+1;
}
}
// cout<<l<<' '<<ll<<endl;
cout<<ll-l<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
第二种做法:
前缀大概的思想是,考虑第i个数x,如果前面的数都小于x,那么最大的最小值在前面产生。如果存在一个数大于x,就可以通过运算,将x增加,这个增加应该是一种平均值。后缀的大致思想是,将运算考虑为i为+1,i-1为-1.对于第i个数x,后面的数如果都大于x,那么最大的最小值一定在后面产生,如果存在数y小于x,那么可以通过运算将y减少,同样也是平均值(但是求最大的最小值需要考虑平均值,差值为1的情况。)
代码如下:
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
void solve() {
int n;
cin >> n;
vector<i64> a(n);
i64 maxnum=0,minnum=(i64)1e14+10;
for(int i=0; i<n; i++){
cin>>a[i];
}
i64 sum=0;
for(int i=0; i<n; i++){
sum+=a[i];
minnum=min(minnum,sum/(i+1));
}
sum=0;
for(int i=n-1; i>=0; i--){
sum+=a[i];
maxnum=max(maxnum,(sum+n-i-1)/(n-i));
}
cout<<maxnum-minnum<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
四.E
题目链接:https://codeforces.com/contest/2013/problem/E
思路:实际上我们如果知道gcd(a)最多降低log(amax)次就可以很好的解决问题;
由于最多降低log(amax)次,我们考虑每次先寻找最小的gcd,直到不再降低;
为什么贪心是对的呢,考虑a,b,c,d,e这个序列(e<a),那么
序列1:a+gcd(a,b)+gcd(a,b,c)+gcd(a,b,c,d)+gcd(a,b,c,d,e)
序列2:e+gcd(e,a)+gcd(e,a,b)+gcd(e,a,b,c)+gcd(e,a,b,c,d)
显然序列2优于序列1:
e+gcd(e,a)<=a
gcd(e,a,b)<=gcd(a,b)
gcd(e,a,b,c)<=gcd(a,b,c)
那么可以知道,不论序列1如何变化,e始终放在第一位,那么我们于是可以更新e后面的数为gcd(x,min),同样的,我们只需要对gcd(x, min)求一下最小值,放在次位即可。这样就解决了排序问题。不会存在贪心的正确性问题,我们的正确性实际上是最优解的考虑,而非局部最优解的考虑。
代码如下:
#include <bits/stdc++.h>
using u32 = unsigned;
using i64 = long long;
using u64 = unsigned long long;
using namespace std;
void solve() {
int n;
cin >> n;
vector<int> a(n);
for(int i=0; i<n; i++)cin>>a[i];
int g=0;
i64 ans=0;
int cnt=n;
while(true){
int ming=1e8+10;
for(int i=0; i<n; i++){
int x=gcd(g,a[i]);
ming=min(ming,x);
}
if(ming==g)break;
g=ming;
ans+=ming;
cnt--;
}
cout<<ans+(i64)g*cnt<<endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}