题目链接:https://codeforces.com/contest/1490
贪心,数学
题目大意为给出一数列,求出最少往其中加入多少个数能使得任意相邻的两个数相差不超过较小数min(a[i],a[i+1])的1倍。
逐一判断,若存在相差超过较小数min(a[i],a[i+1])1倍的相邻两个数,则不断往两个数之间按序插入2*min(a[i],a[i+1])并判断直到满足,最后输出即可。
#include
#define ll long long
#define next next_
#define y1 yy
using namespace std;
ll _,n,a[55],ans;
int main(){
scanf("%lld",&_);
while(_--){
ans=0;
scanf("%lld",&n);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
for(ll i=1;i<n;i++){
if(max(a[i],a[i+1])<=2*min(a[i],a[i+1])) continue;
else{
ll minn=min(a[i],a[i+1]),maxn=max(a[i],a[i+1]);
while(maxn>2*minn){
minn<<=1;
ans++;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
暴力,数学
题目大意为给出一长度为3的倍数的数列,每次可以将其中一个数+1,求最小的次数使得数列中%3余0,余1,余2的数的个数相同。
记录%3余0,余1,余2的数的个数,给定操作可以执行余0->余1,余1->余2,余2->余0的转变,每次找到数量大于n/3的数,将多余的数向后推,重复3次即可。
#include
#define ll long long
#define next next_
#define y1 yy
using namespace std;
ll _,n,ans,sum0,sum1,sum2;
int main(){
scanf("%lld",&_);
while(_--){
ans=sum0=sum1=sum2=0;
scanf("%lld",&n);
for(ll i=1,x;i<=n;i++){
scanf("%lld",&x);
if(x%3==0) sum0++;
if(x%3==1) sum1++;
if(x%3==2) sum2++;
}
if(sum0==n/3&&sum1==n/3&&sum2==n/3) printf("0\n");
else{
int k=3;
while(k--){
if(sum0>n/3){
ans+=sum0-n/3;
sum1+=sum0-n/3;
sum0=n/3;
if(sum1>n/3){
ans+=sum1-n/3;
sum2+=sum1-n/3;
sum1=n/3;
}
}
else if(sum1>n/3){
ans+=sum1-n/3;
sum2+=sum1-n/3;
sum1=n/3;
if(sum2>n/3){
ans+=sum2-n/3;
sum0+=sum2-n/3;
sum2=n/3;
}
}
else if(sum2>n/3){
ans+=sum2-n/3;
sum0+=sum2-n/3;
sum2=n/3;
if(sum0>n/3){
ans+=sum0-n/3;
sum1+=sum0-n/3;
sum0=n/3;
}
}
}
printf("%lld\n",ans);
}
}
return 0;
}
暴力,二分
题意为给出数x,判断x能否表示为2数的立方和。
x范围为[1,1012],考虑到小于等于x的立方数只有104个,可以先打表出104内的所有立方数,再枚举一遍其中一个数,二分判断相减得到的另一个数是否为立方数即可。
#include
#define ll long long
#define next next_
#define y1 yy
using namespace std;
ll _,n,a[100010],cnt;
void init(){
for(ll i=1;i<=10000;i++) a[++cnt]=i*i*i;
}
int main(){
init();
scanf("%lld",&_);
while(_--){
bool ok=false;
scanf("%lld",&n);
for(ll i=1;i<=cnt;i++){
ll x=a[i];
ll y=n-x;
ll pos=lower_bound(a+1,a+1+cnt,y)-a;
if(a[pos]==y){
ok=true;
break;
}
}
if(ok) printf("YES\n");
else printf("NO\n");
}
return 0;
}
分治,RMQ
题意为给出一数列,要求将区间内的最大值作为数的根节点,最大值左右部分分别作为左右子树建树,求每个数所对应的节点所在的深度。
从区间[1,n]开始,区间查找最大值所在位置pos,再递归查找区间[1,pos-1]与区间[pos+1,n]。这里数据范围不大,直接O(n)查找好像也可以过,但我当时打了个线段树查询最大值。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n,a[100010],ans[100010],pos[100010];
struct tree{
ll l;
ll r;
ll maxn;
}t[400010];
void build(ll i,ll l,ll r){
t[i].l=l;
t[i].r=r;
if(l==r){
t[i].maxn=a[l];
return;
}
ll mid=l+r>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
t[i].maxn=max(t[i<<1].maxn,t[i<<1|1].maxn);
return;
}
ll search(ll i,ll l,ll r){
if(t[i].l>=l&&t[i].r<=r) return t[i].maxn;
ll sum1=0,sum2=0;
if(t[i<<1].r>=l) sum1=search(i<<1,l,r);
if(t[i<<1|1].l<=r) sum2=search(i<<1|1,l,r);
return max(sum1,sum2);
}
void work(int dep,int l,int r){
ll num=search(1,l,r);
ans[pos[num]]=dep;
if(l<=pos[num]-1) work(dep+1,l,pos[num]-1);
if(pos[num]+1<=r) work(dep+1,pos[num]+1,r);
}
int main(){
scanf("%lld",&_);
while(_--){
scanf("%lld",&n);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i]);
pos[a[i]]=i;
}
build(1,1,n);
work(0,1,n);
for(ll i=1;i<=n;i++) printf("%lld ",ans[i]);
printf("\n");
}
return 0;
}
前缀和,贪心
题意为给出若干个人的初始代币数,随机挑选2个代币数不为0的人对决,代币数多的一方获胜并拿走败方的所有代币,最后剩下代币数不为0的一人赢得比赛,求有机会赢得比赛的人的编号。
先对所有人按代币数升序排序,排序后第i个位置的人能够稳吃前i-1个人,对排序后的代币数求一遍前缀和,a[i]为第i个位置的人能够稳拿的代币数,若第i个位置的人能够稳拿的代币数比下一个人原有的多,那么他就有机会赢得下一个人的代币,如此循环直到赢得最后一个人。因此从代币数最多的人开始向前判断,遇到第一个不能向后拓展的人结束,从该位置到末位置的所有人都符合条件。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n;
struct p{
ll w;
ll id;
}a[200010];
bool cmp(p x,p y){
return x.w<y.w;
}
int main(){
scanf("%lld",&_);
while(_--){
set<ll> app;
scanf("%lld",&n);
for(ll i=1;i<=n;i++){
scanf("%lld",&a[i].w);
a[i].id=i;
}
sort(a+1,a+1+n,cmp);
for(ll i=1;i<=n;i++) a[i].w+=a[i-1].w;
ll t=0;
for(ll i=n-1;i>=0;i--){
if(a[i].w<a[i+1].w-a[i].w){
t=i+1;
break;
}
}
for(ll i=t;i<=n;i++) app.insert(a[i].id);
printf("%lld\n",(ll)app.size());
for(auto i:app) printf("%lld ",i);
printf("\n");
}
return 0;
}
排序,暴力
题意为给出一个数列,要求从中去掉若干数,使得剩下的所有数中每个数出现的次数均相等,求最少要去掉的数的个数。
因为结果只和数的出现次数有关,因此对输入的数排序后记录每个数出现的次数,观察可以得到最后剩下的每个数的出现次数一定是从原先记录的次数中获得,可以通过假设证明。因此枚举最后剩下的每个数的出现次数并将每种情况算出的答案取最小值即可,枚举时注意用set去重。
#include
#define ll long long
#define next next_
using namespace std;
ll _,n,a[200010],ans;
int main(){
scanf("%lld",&_);
while(_--){
ans=1e15;
scanf("%lld",&n);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
a[n+1]=0;
vector<ll> app;
set<ll> s;
sort(a+1,a+1+n);
ll t=1;
for(ll i=1;i<=n+1;i++){
if(a[i]!=a[t]){
app.push_back(i-t);
s.insert(i-t);
t=i;
}
}
for(auto i:s){
ll sum=0;
for(auto a:app){
if(a<i) sum+=a;
else if(a>i) sum+=a-i;
}
ans=min(ans,sum);
}
printf("%lld\n",ans);
}
return 0;
}
模拟,数学
题意为给出一列数,指针起始时位于下标1的位置,每隔一秒指针会向后移动一格并取走相应位置的数n往后走为1,要求求出对于给定的数x,指针至少走多久时间能够取得总和大于x的数或者永远取不到。
用数组a储存输入的数。
首先考虑取不到的情况,即每次循环完一遍n个数,取得数的总和sum不增,并且在一个周期的过程中所取得数总和sum的最大值小于x。对a数组求一遍前缀和,条件即为a[n]<=0&&max(a[i])
#include
#define ll long long
#define next next_
using namespace std;
ll _,n,m,a[200010],x,maxn,ans,k;
struct tree{
ll l;
ll r;
ll pos;
ll maxn;
}t[800010];
void build(ll i,ll l,ll r){
t[i].l=l;
t[i].r=r;
if(l==r){
t[i].maxn=a[l];
t[i].pos=l;
return;
}
int mid=l+r>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
t[i].maxn=max(t[i<<1].maxn,t[i<<1|1].maxn);
if(t[i].maxn==t[i<<1].maxn) t[i].pos=t[i<<1].pos;
else t[i].pos=t[i<<1|1].pos;
return;
}
ll search(ll i,ll k){
if(t[i].l==t[i].r){
if(t[i].maxn>=k) return t[i].pos;
else return 0;
}
if(t[i<<1].maxn>=k) return search(i<<1,k);
else if(t[i<<1|1].maxn>=k) return search(i<<1|1,k);
else return 0;
}
int main(){
scanf("%lld",&_);
while(_--){
maxn=-1e15;
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
for(ll i=1;i<=n;i++){
a[i]+=a[i-1];
maxn=max(maxn,a[i]);
}
build(1,1,n);
while(m--){
ans=0;
scanf("%lld",&x);
if(a[n]<=0&&maxn<x) printf("-1 ");
else{
if(x<=maxn){
ll t=search(1,x);
printf("%lld ",t-1);
}
else{
k=x-maxn;
ans+=n*(k/a[n]+(k%a[n]!=0));
x-=(k/a[n]+(k%a[n]!=0))*a[n];
ll t=search(1,x);
printf("%lld ",ans+t-1);
}
}
}
printf("\n");
}
return 0;
}