2025牛客寒假算法基础集训营1个人补题题解(C、E、H)
比赛链接:https://ac.nowcoder.com/acm/contest/95323
摸鱼一年,归来仍不会贪心。
H 井然有序之窗
知识点:贪心,优先队列
小红希望你构造一个长度为n的排列,需要满足第i个元素的范围在[l,r]范围内。你能帮帮她吗?
核心想法是从1到n顺序填入,首先将给出的区间按左端点的值从小到大排序,用一个容器维护右端点从小到大的值,确定接下来可以往这个区间内填数后将该区间的右端点加入该容器。
至于为什么是维护右区间大小,因为对于剩下的数而言应该优先去考虑那些即将没有机会的区间,还有更大可能性的区间可以后面再考虑。
优先队列中的右端点需要在填到比这些右端点大的数时及时弹出,这种情况就是不能全部填完的,填完数以后再判断目前答案数组是否是一个排列就可以了,方法应该很多,这里用的是set来删数做判断。
AC代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int ans[N];
priority_queue<pair<pair<int,int>,int>,vector<pair<pair<int,int>,int>>,greater<pair<pair<int,int>,int>>> q;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> temp;
set<int> st;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >>n;
for(int i=1;i<=n;i++)
{
int l,r;
cin >>l>>r;
q.push({{l,r},i});
}
for(int i=1;i<=n;i++)
{
while(!q.empty()&&q.top().first.first<=i)
{
temp.push({q.top().first.second,q.top().second});
q.pop();
}
while(!temp.empty()&&temp.top().first<i)
temp.pop();
if(!temp.empty())
{
ans[temp.top().second]=i;
temp.pop();
}
}
for(int i=1;i<=n;i++)
st.insert(i);
for(int i=1;i<=n;i++)
{
if(st.find(ans[i])!=st.end())
{
st.erase(ans[i]);
}
}
if(st.empty())
{
for(int i=1;i<=n;i++)
cout<<ans[i]<<" ";
}
else cout<<-1<<'\n';
return 0;
}
E 双生双宿之错
知识点:中位数
小红定义一个数组是“双生数组”,当且仅当该数组大小为偶数,数组的元素种类恰好为2种,且这两种元素的出现次数相同。例如 {1,1,4,4,1,4} 是双生数组。
现在小红拿到了一个长度为偶数的数组,她可以进行若干次操作,每次操作将选择一个元素,使其加1或者减1。小红希望你计算将该数组变成双生数组的最小操作次数。
可以抽象成一个问题:给你一个数轴和数轴上的一些数,现在让你取一个数,让数轴上已有的数到这个数的距离之和最小。
如果之前见到过类似的问题就很容易能想到是中位数,但这里不止需要一个中位数,需要两个。
那就可以试着扩展一下,我们要保证整个数组变成两个数且操作次数最少,那就可以按照数的大小排序后前后分成两段,再分别应用这种找中位数的思想。比如1 2 3 4 5 6 我们一定会考虑让前半段的3变成2,而不是让后半段的6变成2,不然不是给自己强行上操作次数的强度吗(
然后再考虑一下两段的中位数相等的情况,直接暴力枚举两边+1或者-1的时候取最小操作次数就行了。
(赛时完全没想到,鉴定为当天熬了夜直接昏头了)
AC代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
vector<int> b1;
vector<int> b2;
int n;
int check(int x,int y)
{
int ans=0;
for(int i=0;i<n/2;i++)
ans+=abs(b1[i]-x);
for(int i=0;i<n/2;i++)
ans+=abs(b2[i]-y);
return ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _;
cin >>_;
while(_--)
{
cin >>n;
for(int i=1;i<=n;i++)
cin >>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
{
if(i<=n/2)
b1.push_back(a[i]);
else b2.push_back(a[i]);
}
sort(b1.begin(),b1.end());
sort(b2.begin(),b2.end());
int idx=(n/2+1)/2-1;
int x,y;
x=b1[idx];
y=b2[idx];
int ans=1e18;
if(x!=y)
ans=min(ans,check(x,y));
if(x+1!=y)
ans=min(ans,check(x+1,y));
if(x!=y+1)
ans=min(ans,check(x,y+1));
if(x-1!=y)
ans=min(ans,check(x-1,y));
if(x!=y-1)
ans=min(ans,check(x,y-1));
cout<<ans<<'\n';
b1.clear();
b2.clear();
}
return 0;
}
C 兢兢业业之移
知识点:暴力
题目要求操作次数不超过\(n^3/2\)次,考虑到一个图中1的数量恒定为\(n^2/4\)个,那么很容易得到每个1的移动次数需要控制在2n次之内。
其实直接暴力就可以了,碰到的第一个1移动到(1,1)的位置,第二个1移动到(1,2)的位置,以此类推。至于怎么移动呢?
对于那些在目标位置右边的数而言,先行移动到目标行,再列移动到目标列。
对于那些在目标位置左边C的数而言,先列移动到目标列,再行移动到目标行。
画图可知这样移动可以保证不会影响到那些已经确定好位置的1。
对于已经移动到的目标位置打一个标记,下次遇到在这个位置上的1就不要动他们了。然后更新一下下一步的目标位置就ok。
就是个暴力题。
真的比我想象的要简单好多,我为什么打到一半就跑了啊亏大发了。
AC代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=550;
char s[N][N];
int nowj[N];
bool vis[N][N];
vector<pair<pair<int,int>,pair<int,int>>> ans;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _;
cin >>_;
while(_--)
{
int n;
cin >>n;
int nowi=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin >>s[i][j];
}
}
for(int i=1;i<=n;i++)
nowj[i]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i][j]=='1'&&vis[i][j]==0)
{
if(j>=nowj[nowi])
{
int t=i;
while(t<nowi)
{
ans.push_back({{t,j},{t+1,j}});
swap(s[t][j],s[t+1][j]);
t++;
}
while(t>nowi)
{
ans.push_back({{t,j},{t-1,j}});
swap(s[t][j],s[t-1][j]);
t--;
}
int k=j;
while(k>nowj[nowi])
{
ans.push_back({{t,k},{t,k-1}});
swap(s[t][k],s[t][k-1]);
k--;
}
}
else
{
int k=j;
while(k<nowj[nowi])
{
ans.push_back({{i,k},{i,k+1}});
swap(s[i][k],s[i][k+1]);
k++;
}
int t=i;
while(t>nowi)
{
ans.push_back({{t,k},{t-1,k}});
swap(s[t][k],s[t-1][k]);
t--;
}
}
vis[nowi][nowj[nowi]]=1;
if(nowj[nowi]+1==n/2+1)
nowi++;
else nowj[nowi]++;
j--;
}
}
}
cout<<ans.size()<<'\n';
for(int i=0;i<ans.size();i++)
{
cout<<ans[i].first.first<<" "<<ans[i].first.second<<" "<<ans[i].second.first<<" "<<ans[i].second.second<<'\n';
}
ans.clear();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
vis[i][j]=0;
}
}
}
return 0;
}

浙公网安备 33010602011771号