Bootstrap

洛谷 NOIP2018普及组 龙虎斗 + 洛谷 CSJ-2019 公交换乘 代码优化

这一道题目的题干比较长,然后我第一个方法是没过的,无脑的暴力枚举果然不具有普适性,不过也能达到80分,也还算是比较可观了,然后我优化了一下,把O(n^2)的时间复杂度优化成了O(n),接下来读者请看题目:

然后是输入输出:

最后是样例说明:

 然后是数据规模:

题干的长度真的很长,不过好在难度并不是很大,接下来话不多说,直接看第一种方法暴力枚举,上代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p1;
long long c[110000],s1,s2;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>c[i];
	cin>>m>>p1>>s1>>s2;
	long long ans=-1;
	int ansp2=-1;
	for(int p2=1;p2<=n;p2++){
		c[p1]+=s1;
		c[p2]+=s2;
		long long sum1=0,sum2=0;
		for(int i=1;i<=n;i++){
			if(i<m) sum1+=c[i]*(m-i);
			else if(i>m) sum2+=c[i]*(i-m);
		}
		long long tmp=abs(sum1-sum2);
		if((tmp<ans) || ansp2==-1){
			ans=tmp;
			ansp2=p2;
		}
		c[p1]-=s1;
		c[p2]-=s2;
	}
	cout<<ansp2<<endl;
	return 0;
}

 属于是一点脑子都不动这个方法,就是在输入完之后,我们引入一个ansp2,这个变量是来第一次更新ansp2的时候用到的,然后在每一次枚举之中,通过套两重循环,来不断的通过tmp和当时的ans进行比较,来判断是否需要更新ans还有ansp2,sum1,sum2分别是两个阵营的气势之和,最后输出我们要的ansp2即可。

接下来请看优化之后的代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,p1;
long long c[110000],s1,s2;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>c[i];
	cin>>m>>p1>>s1>>s2;
    c[p1]+=s1;
    long long sum1=0,sum2=0;
	for(int i=1;i<=n;i++){
		if(i<m) sum1+=(m-i)*c[i];
		else if(i>m) sum2+=(i-m)*c[i];
	}
	long long ans=-1;
	int ansp2=-1;
	for(int p2=1;p2<=n;p2++){
		long long t1=sum1,t2=sum2;
		if(p2<m) t1+=s2*(m-p2);
		else if(p2>m) t2+=s2*(p2-m);
		long long tmp=abs(t1-t2);
		if(ansp2==-1 || tmp<ans){
			ans=tmp;
			ansp2=p2;
		}
	}
	cout<<ansp2<<endl;
	return 0;
}

第二种方法其实思路差距也并不大只不过经过操作减少了一重循环,第二种方法就是在外先计算出sum1和sum2的答案,然后从1到n进行枚举每次判断在相应的地方如果加上s2的兵力,能不能让气势差更小,另外题目要求当有多种方式满足条件的时候,优先输入较小的方案,所以我们在循环之中的条件是tmp<ans,考虑到第一次更新ans和ansp2,我们对ans赋初始值-1。

这道题目虽然题量比较大不过也并不难理解题意只是数据多然后插图比较多所以导致读者阅读量较大,不过就算不考虑到时间复杂度的优化一样可以得到80的分数,而且对思维的要求不怎么高。

看第二道题目:公交换乘

这一道题目我感觉还是很难的,难在时间复杂度上面,实际上这道题目给我的第一印象就是模拟题,只不过确实感觉对思路还有时间复杂度都是有一定要求的,我第一个代码就是时间复杂度没过,接下来上题目:

 

首先给出我一开始超时的代码:

#include<iostream>
using namespace std;
int n;
int ans;
int k=1;
int money[100100],t[100010]; 
int main(){
     	cin>>n;
     	for(int i=1;i<=n;i++){
     		int way,price,time;
     		cin>>way>>price>>time;
     		if(way==0){
			money[k]=price;
			t[k]=time;
			++k;
			ans+=price;
     	    }
     	    else {
     	    	bool pd=false;
			for(int j=1;j<=k;j++){
				if(money[j]>=price && time-t[j]<=45)
				{
				pd=true;
				money[j]=0;
				break;
				}
			}
			if(pd==false)
			ans+=price;  
			}
		 }
		 cout<<ans<<endl;
		 return 0;
}

 主要还是循环的时候,当我用掉了优惠票之后,没有用一种方法把下一次循环的可能性减去一,s顺带一提,上述代码如果把cin改成scanf的话可以多得10分,然后我根据问题,想到了借助使用结构体来解决,接下来我给出正确的代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
struct ticket{
	int time,price;
	bool used;
}a[100010];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		int way,p,t;
		cin>>way>>p>>t;
		if(way==0){
			ans+=p;
			m++;
			a[m].time=t;
			a[m].price=p;
			a[m].used=false;
		} else {
			int id=-1;
			for(int j=m;j>=1;j--){
				if(a[j].time<t-45) break;
				if(a[j].price>=p && !a[j].used)
				id=j;
			}
			if(id!=-1)
			a[id].used=true;
			else ans+=p;
		}  
	}
	cout<<ans<<endl;
	return 0;
}

我们定义的结构体数组用来存储所有的优惠票信息,其中的三个变量包含了优惠票的价格上限,以及得到优惠票的时间,还有bool变量对应的优惠票的使用情况,当我们每次乘坐地铁的时候,就会增加一张优惠票,然后false代表优惠票未被使用,然后当way==1的时候就令一个中间变量id,如果找到了可以使用的优惠票就直接令id为对应的票的编号j,然后把他设置为已经使用过。

公交换乘在模拟题里面算是比较难的了,希望这篇博客对读者有所帮助。

;