1.普通排序
AcWing 103.电影
这个题目没什么说的,主要是复习一下离散化:本题可以考虑直接使用桶排序(利用map这一数据结构),或用vector或数组将数据离散化后用桶排序。map在此处不多做介绍,直接展示vector的写法:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=2e5+5;
vector<int> lsh;
int a[N],b[N],c[N],tub[N];
int get(int x){
return lower_bound(lsh.begin(),lsh.end(),x)-lsh.begin();
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
lsh.push_back(a[i]);
}
int m;
cin>>m;
for(int i=1;i<=m;i++){
cin>>b[i];
}
for(int i=1;i<=m;i++){
cin>>c[i];
}
sort(lsh.begin(),lsh.end());
lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
for(int i=1;i<=n;i++){
int x=get(a[i]);
tub[x]++;
}
int maxn=0,ans=1;
for(int i=1;i<=m;i++){
int x=get(b[i]);
if(lsh[x]==b[i] && tub[x]>maxn){
//特别注意判断b[i]是否在vector中出现过
maxn=tub[x];
ans=i;
}
}
int maxx=0;
for(int i=1;i<=m;i++){
int x=get(b[i]);
if((lsh[x]==b[i] && tub[x]==maxn) || maxn==0){
int y=get(c[i]);
if(lsh[y]==c[i] && tub[y]>maxx){
ans=i;
maxx=tub[y];
}
}
}
cout<<ans<<endl;
return 0;
}
2.归并排序
AcWing 108.奇数码问题
此问题可以转化为排序中的逆序对问题:将整个矩阵去掉空格后从上到下、从左到右得到一个长度为n*n-1的序列,那么考虑左右移动空格不会改变序列,而上下移动则相当于将某个元素x前移n-1位或后移n-1位,假设这n-1个跳过的元素中有a个元素大于x,有b个元素小于x(a+b=n-1)。则逆序对个数ans+=x-y,显然x-y是偶数,没有改变逆序对个数奇偶性。由此可以初步得到一个结论:若初状态与末状态的逆序对个数相同,则两个状态可以互相达成。这里证明了必要性,充分性过于复杂不做证明。此结论可以进一步推广到偶数码以及n * m矩阵的情况,具体见蓝书P39。总而言之,这类问题的解的存在性判断可以通过归并排序求逆序对实现。如果要求具体移动方法,则需要使用A * 算法求解。
#include<iostream>
#include<cstring>
using namespace std;
const int N=505;
int a[N*N],b[N*N],gb[N*N];
long long nxd;
void msort(int a[],int l,int r){
if(l>=r) return;
int mid=(l+r)>>1,i=l,j=mid+1,k=l;
msort(a,l,mid);
msort(a,mid+1,r);
while(i<=mid && j<=r){
if(a[i]<=a[j]) gb[k++]=a[i++];
else{
nxd+=mid-i+1;
gb[k++]=a[j++];
}
}
while(i<=mid) gb[k++]=a[i++];
while(j<=r) gb[k++]=a[j++];
for(int p=l;p<=r;p++) a[p]=gb[p];
}
int main(){
int n;
while(cin>>n){
memset(a,0,sizeof a);
memset(b,0,sizeof b);
int cnt=0;
for(int i=1;i<=n*n;i++){
int k;
cin>>k;
if(k!=0) a[++cnt]=k;
}
cnt=0;
for(int i=1;i<=n*n;i++){
int k;
cin>>k;
if(k!=0) b[++cnt]=k;
}
nxd=0;
msort(a,1,n*n-1);
long long x=nxd%2;
nxd=0;
msort(b,1,n*n-1);
long long y=nxd%2;
if(x!=y) cout<<"NIE"<<endl;
else cout<<"TAK"<<endl;
}
}
3.绝对值不等式(贪心)
基本模型:AcWing104.货仓选址
在一条数轴上有 n家商店,它们的坐标分别为 A1∼An。现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
假设货仓的位置为x,那么问题就转化为了求|a1-x|+|a2-x|+……+|an-x|的最小值。那么这个问题就很简单,在初一学过,只要将x取在a1~an的中位数上即可。
你以为事情就这么简单?不!这个问题还要在很多难题中得到应用。
类型1:二维货仓选址(AcWing 123.士兵)
本题很明显要先在y轴上使用货仓选址的公式,那么,x轴上怎么办呢?当然也要将其转化为货仓选址问题。但是并不是直接使用公式。所以我们要思考一下:假设第一个士兵移到了一个位置上,其横坐标为x。那么移动步数为|x[1]-x|。同理第二个士兵要移动|x[2]-x-1|步,第三个移动|x[3]-x-2|步,以此类推,第n个要移动|x[n]-x-(n-1)|=|x[n]-n+1-x|。那么很明显,我们只要将所有的a[i]=x[i]-i+1排序求中位数即可。
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=10005;
int a[N],b[N];
int main(){
int n;
long long ans=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i];
}
sort(a+1,a+n+1);
sort(b+1,b+n+1);
for(int i=1;i<=n;i++){
a[i]-=i;
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
ans+=abs(a[i]-a[n+1>>1]);
ans+=abs(b[i]-b[n+1>>1]);
}
cout<<ans<<endl;
}
类型2:推公式转化
例题1:AcWing 122.糖果传递
凡是碰到环形类问题,都要想到拆环为链的思想,那么我们想一下链状的糖果传递怎么做呢?很明显是从左向右不断地传递就可以了。所以对于该问题,设给定的糖果数为a1……an,设a为整个数组中的数字的平均数,那么ans=|a1-a|+|a1+a2-2a|+|a1+a2+a3-3a|+……+|a1+a2+a3+……+an-na|。但这个式子并不是我们想要的形式,所以要对它变形:根据拆环为链的思想,我们应当去枚举断开的位置,但是这样的复杂度达到了O(n^2),不能接受。所以我们可以假设拆开后的链的第一个数是原数组的第k个数ak。那么式子就变成了:|ak-a|+|ak+1 + ak+2 -2a|+……+|ak+ak+1 + ak+2 +……+an- (n-k)a|+|ak+ak+1 + ak+2 +……+an+a1- (n-k+1) a|+……+|ak+ak+1 + ak+2 +……+an+a1+……+ak-1 -na|,那么到这里,我们注意到每一项中ai的个数与a的系数相同,可以将它们配对。那么配对后可以发现这其中的每一项都是原数组中的一段区间和,考虑采用前缀和:令b[i]=a[i]-a,f[i]为b[i]的前缀和数组。那么式子可以化为:|f[k]-f[k-1]|+|f[k+1]-f[k-1]|+……+|f[n]-f[k-1]|+|f[n]-f[k-1]+f[1]|+|f[n]-f[k-1]+f[2]|+……+|f[n]-f[k-1]+f[k-1]|。我们惊奇地发现:所有的项中都有-f[k-1],好像可以用公式了,但是后面还有f[n]怎么办?显然f[n]=0。至此,这个问题被完美地解决了。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e6+5;
typedef long long ll;
ll a[N],f[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
a[0]+=a[i];
}
a[0]/=n;
for(int i=1;i<=n;i++){
a[i]-=a[0];
f[i]+=f[i-1]+a[i];
}
sort(f+1,f+n+1);
ll ans=0;
for(int i=1;i<=n;i++){
ans+=abs(f[i]-f[n+1>>1]);
}
cout<<ans<<endl;
}
总结:求解这一类问题,一定要对一些基本的转化思想与模板题了然于胸,并且对数学有一定的敏感程度。
例题2:AcWing 105.七夕祭
这题就是二维的糖果传递,没什么好说的。直接上代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int row[N],col[N];
ll get_min(int a[N],int n,int sum){
ll b[N];
int ave=sum/n;
for(int i=1;i<=n;i++) b[i]=b[i-1]+a[i]-ave;
sort(b+1,b+n+1);
ll mid=b[(n+1)/2],ans=0;
for(int i=1;i<=n;i++) ans+=abs(b[i]-mid);
return ans;
}
int main(){
int n,m,t;
cin>>n>>m>>t;
while(t--){
int x,y;
cin>>x>>y;
row[x]++;
col[y]++;
}
int R=0;
for(int i=1;i<=n;i++) R+=row[i];
int C=0;
for(int i=1;i<=m;i++) C+=col[i];
ll ans=0;
bool flagr=0,flagc=0;
if(R%n==0){
ans+=get_min(row,n,R);
flagr=1;
}
if(C%m==0){
ans+=get_min(col,m,C);
flagc=1;
}
if(flagr && flagc){
cout<<"both "<<ans;
}
else
if(flagr){
cout<<"row "<<ans;
}
else
if(flagc){
cout<<"column "<<ans;
}
else puts("impossible");
return 0;
}
4.中位数
AcWing 106.动态中位数
本题作为模板题,核心就是“对顶堆”:即用大根堆存前半部分的数,小根堆存后半部分的数,每次读入一个数据,便将其与当前的中位数比较。若小于,压入大根堆;若大于,压入小根堆。并且每次都要调节两堆中数字的个数,使它们的个数之差始终为1或0。
代码
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
priority_queue<int> q1,nul1;
priority_queue<int,vector<int>,greater<int> > q2,nul2;
int main(){
int m;
cin>>m;
while(m--){
q1=nul1;
q2=nul2;
int n,bh;
cin>>bh>>n;
cout<<bh<<' '<<(n+1)/2<<endl;
int a,cnt=0;
for(int i=1;i<=n;i++){
cin>>a;
if(!q1.size() || a<q1.top()) q1.push(a);//注意这些小细节,要保证q1中的个数始终大于等于q2中的个数
else q2.push(a);
while(q1.size()<q2.size()){//调剂个数
int t=q2.top();
q1.push(t);
q2.pop();
}
while(q1.size()>q2.size()+1){
int t=q1.top();
q2.push(t);
q1.pop();
}
if(i&1){
cnt++;
cout<<q1.top()<<' ';
if(cnt%10==0) cout<<endl;
}
}
if(cnt%10!=0) cout<<endl;
}
return 0;
}