E. 双生双宿之错
题目简介:
给定一个数组,每次操作可以使得一个元素加1或者减1,问最小操作几次可以变成双生数组。
双生数组:元素种类数为2、且出现次数相同。
思路:
首先来了解一下中位数定理,即数轴上的一组数字到该组数字中位数的距离和最小
引申到本题,我们只需要将数组排序,将前半部分元素变成前半部分的中位数,后半部分元素变成后半部分的中位数。注意需要特判这两个中位数相等的情况,解决方案是要么将前半部分变成中位数减1,要么将后半部分变成中位数加1,枚举这两种方案分别统计取最优。
#include<bits/stdc++.h>
#define LL long long
#define N 100010
using namespace std;
int t,n,a[N];
LL check(int mid1,int mid2)
{
LL i=1,cnt=0;;
for(i;i*2<=n;i++)
cnt+=abs(a[i]-mid1);
for(i;i<=n;i++)
cnt+=abs(a[i]-mid2);
return cnt;
}
int main()
{
cin>>t;
while(t--)
{
LL ans=INT_MAX;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+n+1);
int mid1,mid2; //mid1为前半部分中位数,mid2为后半部分中位数
if(n%4==0) //对于中位数的求法分类
mid1=(a[n/4]+a[n/4+1])/2,mid2=(a[n/4+n/2]+a[n/4+n/2+1])/2;
else
mid1=a[(n/2+1)/2],mid2=a[((n/2+1)/2)+n/2];
if(mid1==mid2) //当中位数相同时,枚举两种方案求最优解
{
ans=check(mid1-1,mid2);
ans=min(ans,check(mid1,mid2+1));
}
else
ans=check(mid1,mid2);
cout<<ans<<endl;
}
return 0;
}
M. 数值膨胀之美
题目简介:
给定一个数组,可以选择一个区间将所有元素乘2,问操作后的最小极差。
思路:
显然,我们至少应当让最小值扩大一倍,其次考虑扩大区间,使最小值到次小值的区间扩大一倍……最小值到次次小值。此过程可以通过维护区间的两个端点 和
来实现,当我们需要翻倍的区间增大的时候(比如从最小值到次小值),只需要将
向左移动或者将
向右移动,直到包含当前需要选择的元素。
例如假设当前翻倍的区间为 [6,8] 即 ,
。下一个待翻倍的最小值位置在 11 ,这时我们需要将第 9,10,11 这三个数同时翻倍,即
,
。
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct node{ //num为数组的值,id为下表
int num,id;
}a[100010];
int b[100010];
int n;
bool cmp(node a,node b)
{
return a.num<b.num;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].num;
a[i].id=i;
b[i]=a[i].num;
}
sort(a+1,a+n+1,cmp);
a[n+1].num=INT_MAX; //设定哨兵,防止越界
int l=a[1].id,r=a[1].id; //初始的区间只包含最小值
int ma=max(a[1].num*2,a[n].num); //ma为数组当前状态的最大值
int res=ma-min(a[1].num*2,a[2].num); //rse为数组当前状态的极差
for(int i=2;i<=n;i++) //从最小值开始遍历,不断扩大区间
{
while(a[i].id<l) //如果下一个待翻倍的最小值在左边,则从左边扩大区间
{
l--;
ma=max(ma,b[l]*2);
}
while(a[i].id>r) //如果下一个待翻倍的最小值在右边,则从右边扩大区间
{
r++;
ma=max(ma,b[r]*2);
}
res=min(res,ma-min(a[1].num*2,a[i+1].num));//更新极差 min(最小值*2,未翻倍的最小值)
}
cout<<res;
}
J. 硝基甲苯之袭
题目简介:
给定一个数组,问有多少对元素满足
思路:
对于一个数而言,它和任意一个数的 一定是它的某个因子。那么本题可以通过枚举每个元素
的每个因子
,作为它和其他元素的“假想gcd”,然后我们去检测
是不是等于真正的
即可。如果确实相等,那么证明
就是真正是
,我们直接用map计算
出现的次数。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,a[200005];
LL ans;
map<int,int> p;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
p[a[i]]++;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=a[i]/j;j++)
{
if(a[i]%j==0) //找出a[i]的因数j
{
if(j==__gcd(a[i],a[i]^j)) ans+=p[a[i]^j];
if(a[i]/j!=j&&a[i]/j==__gcd(a[i],a[i]^(a[i]/j))) ans+=p[a[i]^(a[i]/j)];
}
}
}
cout<<ans/2;
return 0;
}
H. 井然有序之窗
题目简介:
给定每个元素允许出现的区间,构造一个排列,使得每个元素都在一个指定的区间内。
思路:
我们先将所有区间排序,用一个优先队列来维护“可选的区间右端点”。当我们指定某个区间为 的时候,我们首先将所有以
为左端点的区间加入优先队列,然后尽量选择目前最小的那个右端点即可。
#include<bits/stdc++.h>
#define N 100010
using namespace std;
typedef pair<int,int> pll;
priority_queue< pll,vector<pll>,greater<pll> > k;
int ans[N],n;
struct node{ //数字b的可选区间为[l,r]
int l,r,b;
}a[N];
bool cmp(node a,node b)
{
if(a.l==b.l) return a.r<b.r;
else return a.l<b.l;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i].l>>a[i].r;
a[i].b=i;
}
sort(a+1,a+n+1,cmp);
int cnt=1;
bool flag=false;
for(int i=1;i<=n;i++) //遍历下标,查找第i个数字能填多少
{
while(a[cnt].l==i) //所有左端点取到l的数入队
{
k.push({a[cnt].r,a[cnt].b});
cnt++;
}
if(k.size() && k.top().first>=i) //若数字存在,取出右端点最小的数
{
pll res=k.top();
k.pop();
ans[res.second]=i;
}
else //数字不存在,无解
{
cout<<-1;
flag=true;
break;
}
}
if(!flag)
for(int i=1;i<=n;i++)
cout<<ans[i]<<" ";
return 0;
}