8.29~8.30习题总结

目录

一、尺取法

(1)POJ-3061

(2)POJ-2566

(3)洛谷-P1102

(4)UVA-11572

(5)洛谷-P1886

二、二分法

(1)洛谷-P1824

(2)POJ-3122

(3)洛谷-P1083

(4)洛谷-P2678

一、尺取法

(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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值