目录
一、尺取法
(1)POJ-3061
这道题考察双指针算法。双指针算法的核心:
1)区间具有单调性,也就是说,在处理完[i,j]区间后,我们应该很清楚的知道下一步移动的是i还是j。
2)其次它的时间复杂度为O(n),主要取决于i,j始终是前进的,不会回退。
解题思路:
我们可以考虑暴力解法。暴力解法需要考虑以i开头的元素,总和不小于s的最短字串长,时间复杂度为O(n^2)。
优化方案,当sum<s时,j++,当sum>=s时,更新答案,i++。优化方案成立的原因如下:首先,当sum>=s时,以i开头的解已经最优,也就是说a[i]已经没用了,故i++;其次,由于区间的sum是单调的(正整数数组),所以,当sum<s时,j++可以逼近解。
启发:连续字串、具有某种单调性的区间往往可以采用双指针算法。
#include<iostream> #include<vector> using namespace std; vector<int> a; int main(void) { int T;cin>>T; while(T--){ int n,s,Min=100001;cin>>n>>s; for(int i=0;i<n;i++){ int num;cin>>num; a.push_back(num); } int i=0,j=0,sum=a[0]; while(i<=j && j<n){ if(sum>=s){ Min=min(Min,j-i+1); sum-=a[i]; i++; }else{ j++; sum+=a[j]; } } cout<<Min<<endl; a.clear(); } return 0; }
(2)POJ-2566
解题思路:见博客POJ 2566 - Bound Found - 尺取法详解(尺取法好题)
个人见解:这道题是比较难的,启发有以下几点:
1)无论是前缀和还是差分,注意下标从1开始
2)当题目要求 区间和 与 区间端点 结合时,考虑前缀和
3)善于sort,改善单调性
#include<iostream>
#include<algorithm>
using namespace std;
int l,r,s;
struct Node{
int idx;
int sum;
}node[100001];
bool cmp(struct Node a,struct Node b)
{
return a.sum<b.sum;
}
void solve(int n,int t)
{
int i=0,j=1;
int Min=1e9+10;
while(i<=j && j<=n){
int temp=node[j].sum-node[i].sum;
if(abs(temp-t)<Min){
// cout<<"-- "<<node[i].idx<<" "<<node[j].idx<<" "<<temp<<endl;
Min=abs(temp-t);
s=temp;
l=node[i].idx;
r=node[j].idx;
}
if(temp>t)
i++;
else
j++;
}
}
int main(void)
{
int n,k;
while(cin>>n>>k && n && k){
node[0]={0,0};
for(int i=1;i<=n;i++){
cin>>node[i].sum;
node[i].sum+=node[i-1].sum;
node[i].idx=i;
}
sort(node,node+1+n,cmp);
for(int i=0;i<k;i++){
l=0,r=0,s=0;
int t;cin>>t;
solve(t,n);
if(l > r)
swap(l,r);
cout<<s<<" "<<l+1<<" "<<r<<endl;
}
}
}
(3)洛谷-P1102
经典的三指针题,比较简单,其他方法也可以做。详见教材。
#include<bits/stdc++.h>
using namespace std;
vector<int> a;
int main(void)
{
int n,c;cin>>n>>c;
for(int i=0;i<n;i++){
int num;cin>>num;
a.push_back(num);
}
sort(a.begin(),a.end());
long long ans=0;
for(int i=0,j=0,k=0;i<n;i++){
while(j<n && a[j]-a[i]<c) j++;
while(k<n && a[k]-a[i]<=c) k++;
if(a[j]-a[i]==c)
ans+=k-j;
}
cout<<ans;
return 0;
}
(4)UVA-11572
简单题目,关键在于考虑如何对重复数字的处理。
#include<bits/stdc++.h>
using namespace std;
vector<int> a;
map<int,bool> Map;
int main(void)
{
int T;
cin>>T;
while(T--){
int Min=0;
int n,ans=0;cin>>n;
for(int i=0;i<n;i++){
int num;cin>>num;
a.push_back(num);
}
int i=0,j=0;
while(i<=j && j<n){
if(!Map[a[j]]){
ans++;
Map[a[j]]=true;
j++;
}else{
Min=max(Min,ans);
Map[a[i]]=false;
i++;
ans--;
}
}
cout<<Min<<endl;
a.clear();
Map.clear();
}
return 0;
}
(5)洛谷-P1886
模版题,求固定窗口的最值问题
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int main(void)
{
int n,k;cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i];
deque<int> Q;
for(int i=0;i<n;i++){
while(Q.size() && Q.back()>a[i]) Q.pop_back();
Q.push_back(a[i]);
if(i-k>=0 && Q.front()==a[i-k]) Q.pop_front();//值得注意的是,队列里面可能不是k个元素,但是你要维护的是k个元素,当窗口大小超过k时,仍需要弹出
if(i-k+1>=0) cout<<Q.front()<<" ";
}
cout<<endl;
Q.clear();
for(int i=0;i<n;i++){
while(Q.size() && Q.back()<a[i]) Q.pop_back();
Q.push_back(a[i]);
if(i-k>=0 && Q.front()==a[i-k]) Q.pop_front();
if(i-k+1>=0) cout<<Q.front()<<" ";
}
}
二、二分法
(1)洛谷-P1824
二分法算法核心:
1)找到一个可二分的单调区间,往往就是求解答案的那个区间,可以先考虑一下暴力能否求解。
2)check函数需要根据mid进行过程分配,时间复杂度往往控制在O(n)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N];
int n,c;
bool check(int mid)
{
int last=0,cnt=1;
for(int i=1;i<n;i++){
if(a[i]-a[last]>=mid){
last=i;
cnt++;
}
}
if(cnt>=c)
return false;
}
int main(void)
{
cin>>n>>c;
int Max=0,Min=1e9+10,Maxs=0;
for(int i=0;i<n;i++){
cin>>a[i];
if(a[i]>Max){
Max=a[i];
Maxs=Max;
}
if(a[i]<Min) Min=a[i];
}
int l=Max-Maxs,r=Max-Min;
sort(a,a+n);
int ans=0;
while(l<r){
int mid = l+r >>1;
if(check(mid))
r=mid;
else{
ans=mid;
l=mid+1;
}
}
cout<<ans<<endl;
return 0;
}
(2)POJ-3122
#include<iostream>
#include<cmath>
using namespace std;
const double PI=acos(-1.0);
const int N=1e4+10;
double a[N];
bool check(double x,int n,int f)
{
int cnt=0;
for(int i=0;i<n;i++){
int temp=a[i]/x;
cnt+=a[i]/x;
}
if(cnt>=f) return true;
else return false;
}
int main(void)
{
int T;cin>>T;
while(T--){
int n,f;cin>>n>>f;
f++;
double sum=0;
for(int i=0;i<n;i++){
cin>>a[i];
a[i]=PI*a[i]*a[i];
sum+=a[i];
}
double l=0,r=sum/f;
double ans=0;
while(r-l>1e-5){
double mid=(l+r)/2;
if(check(mid,n,f)){
ans=mid;
l=mid;
}
else
r=mid;
}
printf("%.4lf\n",ans);
}
}
(3)洛谷-P1083
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int D[N],a[N];
int d[N],s[N],t[N];
int DD[N];
int n,m;
bool check(int mid)
{
for(int i=0;i<=n;i++) DD[i]=D[i];
for(int i=0;i<=mid;i++){
DD[s[i]]-=d[i];
DD[t[i]+1]+=d[i];
}
int last=0;
for(int i=1;i<=n;i++){
last+=DD[i];
if(last<0)
return true;
}
return false;
}
int main(void)
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
D[0]=0;
for(int i=1;i<=n;i++) D[i]=a[i]-a[i-1];
for(int i=0;i<m;i++)
cin>>d[i]>>s[i]>>t[i];
int l=0,r=m-1;
while(l<r){
int mid=l+r>>1;
if(check(mid))
r=mid;
else
l=mid+1;
}
if(l>=m-1)
cout<<0<<endl;
else
cout<<-1<<endl<<l+1<<endl;
}
(4)洛谷-P2678
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int len,n,m;
int ans=0;
bool check(int mid)
{
int cur=0,i=0,cnt=0;
while(i<n+1){
i++;
if(a[i]-a[cur] < mid)
cnt++;
else
cur=i;
}
if(cnt > m)
return false;
else
return true;
}
int main(void)
{
cin>>len>>n>>m;
a[0]=0;
for(int i=1;i<=n;i++) cin>>a[i];
a[n+1]=len;
int l=1,r=len;
while(l<=r){
int mid=l+r>>1;
if(check(mid)){
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
cout<<ans;
}