编程五:灵能传输
题目描述
题目背景(不用看)
问题描述
你控制着 n 名高阶圣堂武士,方便起见标为 1,2,⋅⋅⋅,n。每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 ai 表示其拥有的灵能的多少,ai非负表示这名高阶圣堂武士比在最佳状态下多余了 ai 点灵能,ai 为负则表示这名高阶圣堂武士还需要 −ai 点灵能才能到达最佳战斗状态)。
现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i∈[2,n−1],若 ai ≥ 0 则其两旁的高阶圣堂武士,也就是 i−1、i+1 这两名高阶圣堂武士会从 i 这名高阶圣堂武士这里各抽取 ai 点灵能;若 ai < 0 则其两旁的高阶圣堂武士,也就是 i−1,i+1 这两名高阶圣堂武士会给 i 这名高阶圣堂武士 −ai 点灵能。形 式化来讲就是 ai−1+=ai,ai+1+=ai,ai−=2ai。
灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 ,请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。
输入描述
本题包含多组询问。
输入的第一行包含一个正整数 T 表示询问组数。 接下来依次输入每一组询问。
每组询问的第一行包含一个正整数 n,表示高阶圣堂武士的数量。
接下来一行包含 n 个数 a1,a2,⋅⋅⋅,an。
其中,T≤3,3≤n≤300000,∣ai∣≤10^9.
输出描述
输出 T 行。每行一个整数依次表示每组询问的答案。(即最小的不稳定度)
3
3
5 -2 3
4
0 0 0 0
3
1 2 3
3
0
3
思路:若用前缀和来表示序列,所求答案即求s[i]−s[i−1]的最大值可取得的最小值。
若有a[i−1],a[i],a[i+1],进行一次传递灵能后,变为a[i−1]+a[i],−a[i],a[i+1]+a[i]。
前缀和变化如下:
s[i−1] 变为 s[i−2]+a[i−1]+a[i]=s[i]。
s[i] 变为 s[i−2]+a[i−1]+a[i]−a[i]=s[i−1]。
s[i+1] 保持不变。
这意味着将s[i−1]和s[i]交换,故除了s[0]和s[n]以外 1~n 的任何s[i]可以进行相互交换从而得到一个有序的序列。(交换一次相当于传递一次灵能)
【反证法】将s序列画在x-y坐标系中:
当s0>sn时,我们取s0=sn,sn=s0,最优的路线为s0->最小值->最大值->sn。(序列尽可能单调,有上面的特殊情况可得普遍情况)
在s0到达最小值的过程中,如果一步到达,那么差值一定会很大,但是如果分几步到达,再单调上升,再一步一步下降直到snsn会使答案更优,要做到每一步都尽量的小可以发现每隔一个向前跳是最好的。
在s0向前跳、sn向后跳的过程中,拿到的数标记为已拿,最后将没有用过的数串起来就是中间单调的数。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
typedef long long ll;
using namespace std;
int main()
{
int t,i;//整数个数,测试数据个数
ll x,s0,sn;//每个整数n
cin>>t;
while(t--){
int n;
cin>>n;
vector<ll>s(n+1);//初始值为0
for(i=0;i<n;i++){
cin>>x;
s[i+1]=s[i]+x;//前缀和序列
}
s0=s[0], sn=s[n];//固定位置不动
if(s0>sn) swap(s0,sn);
sort(s.begin(),s.end());
for(i=0;i<n+1;i++){//找s0原来的位置
if(s[i]==s0){
s0=i;
break;
}
}
for(i=n;i>=0;i--){//找sn原来的位置
if(s[i]==sn){
sn=i;
break;
}
}
vector<ll>a(n+1);//最优前缀和序列
vector<bool>v(n+1);//排序后每个数取出与否的标志
int l=0,r=n;
for(i=s0;i>=0;i-=2){//从s0跳着走到最小值s[0]
a[l++]=s[i];
v[i]=true;
}
for(i=sn;i<=n;i+=2){//从sn反着跳到最大值s[n]
a[r--]=s[i];
v[i]=true;
}
for(i=0;i<=n;i++)//剩下的按顺序取
if(!v[i]) a[l++]=s[i];
ll ans=0;
for(i=1;i<=n;i++)
ans=max(ans,abs(a[i]-a[i-1]));
cout<<ans<<endl;
}
return 0;
}
编程:回文日期
题目描述
2020 年春节期间,有一个特殊的日期引起了大家的注意:2020 年 2 月 2 日。因为如果将这个日期按 “yyyymmdd” 的格式写成一个 8 位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期。
有人表示 20200202 是 “千年一遇” 的特殊日子。对此小明很不认同,因为不到 2 年之后就是下一个回文日期:20211202 即 2021 年 12 月 2 日。
也有人表示 20200202 并不仅仅是一个回文日期,还是一个 ABABBABA 型的回文日期。对此小明也不认同,因为大约 100 年后就能遇到下一个 ABABBABA 型的回文日期:21211212 即 2121 年 12 月 12 日。算不上 “千年一遇”,顶多算 “千年两遇”。
给定一个 8 位数的日期,请你计算该日期之后下一个回文日期和下一个 ABABBABA 型的回文日期各是哪一天。
输入描述
输入包含一个八位整数 N,表示日期。
对于所有评测用例,10000101≤N≤89991231,保证 N 是一个合法日期的 8 位数表示。
输出描述
输出两行,每行 1 个八位数。第一行表示下一个回文日期,第二行表示下一个 ABABBABA 型的回文日期。
20200202
20211202
21211212
代码:
#include <iostream>
using namespace std;
bool isLeap(int y){
return (y%4==0&&y%100!=0)||(y%400==0);
}
bool check(int year,int month,int day){//判断是否为合法日期
if(month>12||month==0) return false;
if(day>31) return false;
if(month==2){
if(isLeap(year)&&day>29)
return false;
if(!isLeap(year)&&day>28)
return false;
}
if(month==4||month==6||month==9||month==11){
if(day>30) return false;
}
return true;
}
int main()
{
int n,i;
cin>>n;
int a,b,c,d,e,f,g,h;//8位数字
int year,month,day;
bool flag=false;
for(i=n+1;i<=99999999;i++){
year=i/10000;
month=(i%10000)/100;
day=i%100;
a=i%10;
b=(i/10)%10;
c=(i/100)%10;
d=(i/1000)%10;
e=(i/10000)%10;
f=(i/100000)%10;
g=(i/1000000)%10;
h=(i/10000000)%10;
if(a==h&&b==g&&c==f&&d==e&&flag==false){
if(check(year,month,day)){
cout<<i<<endl;
flag=true;//只输出一个回文
}
}
if(a==h&&b==g&&c==f&&d==e&&a==c&&b==d){
if(check(year,month,day)){
cout<<i<<endl;
break;
}
}
}
return 0;
}