欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

牛客 CSP-J入门组 简单排序

程序员文章站 2022-06-24 11:58:42
我以为的排序->sort自定义实际的排序->与大量算法综合考察,甚至还考推导公式问题一:求和走这问题分析:一开始是用间距写的,思路是间距最多到n/3,但实际是错误的,当1~9的时候间距能取到4,1到13同理(但我现在还是不知最大能到几)(我可真是菜…)修正了后只能在洛谷拿到40分,其他全部TLE,看了下数据范围,如果O(n^2)到了10000000000看了题解才发现有公式可推…以下结合AC代码说明#include using...

我以为的排序->sort自定义
实际的排序->与大量算法综合考察,甚至还考推导公式

问题一:
求和
走这

问题分析:

  1. 一开始是用间距写的,思路是间距最多到n/3,但实际是错误的,当1~9的时候间距能取到4,1到13同理(但我现在还是不知最大能到几)(我可真是菜…)
  2. 修正了后只能在洛谷拿到40分,其他全部TLE,看了下数据范围,如果O(n^2)到了10000000000
  3. 看了题解才发现有公式可推…

以下结合AC代码说明

#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct lat{
	int color;
	int nums;
	bool ji;//1表奇 2表偶
	int id;	
};
struct cp{
	bool operator()(lat t1,lat t2)
	{
		if(t1.color==t2.color)
		{
			if(t1.ji==t2.ji)
			{
				return t1.id<t2.id;
			}
			else{
				return t1.ji<t2.ji;
			}
		}else{
			return t1.color<t2.color;
		} 
	}
};
lat lats[100010];
ll sum; 
ll cnt[100010][3]; 
ll cnts[100010][2]; 
int main()
{
	/*
	题目要求:y-x=z-y,而x与z的color必须相同,题目除了第一个条件与y毫无关系
	x+z=2*y(说明x与z同奇偶) ,不同颜色、奇偶的x、z不会产生分数 
	根据奇偶颜色相同将格子 分为1组 
	*/ 
	int n,m;
	cin>>n>>m; 
	ll sum = 0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&lats[i].nums);
		//sum+=(lats[i].nums);公式的sum是奇偶相同颜色相同的sum 
		lats[i].id = i;
		if(i%2==0)
		{
			lats[i].ji = 0;
		}else{
			lats[i].ji = 1;
		}
	}
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&lats[i].color);
		if(lats[i].ji==1)
		{
			cnt[lats[i].color][1]++;//计数 
			cnts[lats[i].color][1]+=lats[i].nums;
		}else{
			cnt[lats[i].color][0]++;
			cnts[lats[i].color][0]+=lats[i].nums; 
		}
	}
	sort(lats+1,lats+n+1,cp());
	/*先计数有多少个颜色奇偶相同在一组*/
	ll sumqi = 0;
	ll sumou = 0;
	ll res = 0;
	for(int i=1;i<=n;i++)
	{
		if(lats[i].ji&&cnt[lats[i].color][1]>=2)//如果为奇数 
		{
			sumqi = ((cnt[lats[i].color][1]-2)*lats[i].nums*lats[i].id+lats[i].id*cnts[lats[i].color][1])%10007;
			res+=sumqi;
			res%=10007;
		}
		if(!lats[i].ji&&cnt[lats[i].color][0]>=2)
		{
			sumou = ((cnt[lats[i].color][0]-2)*lats[i].nums*lats[i].id+lats[i].id*cnts[lats[i].color][0])%10007;
			res+=sumou;
			res%=10007;
		}
	}
	printf("%lld\n",res);
}   
  1. 假设存在这样一组,那么就可以推导公式了,将(1,2)(1,3)…(1,n)的分数求和,用分配律即可发现公式即
    (cnt-2)×id×nums[id]+id×(同奇偶同颜色的格子和)
  2. 根据分别统计即可

问题二:
不简单的排序
走这

问题分析:

  1. 虽然提示了可能已经排好序了但我还是用了快排+直接插入排序,结果显示TLE,最后看了答案才发现用优化的冒泡排序即可…
  2. 如果已经排好序了,那么,冒泡排序的时间复杂度就是O(n),比快速排序快

AC代码:

#include <bits/stdc++.h>
using namespace std;
int nums[1500010];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		scanf("%d",&nums[i]);
	}
	for(int i=0;i<n-1;i++)
	{
		bool flag = false;
		for(int j=0;j<n-i-1;j++)
		{
			if(nums[j]>nums[j+1])
			{
				swap(nums[j],nums[j+1]);
				flag = true;
			}
		}
		if(!flag)
		{
			break;
		}
	}
	for(int j=0;j<n;j++)
       printf("%d ",nums[j]);
    return 0;
}

问题三:
国王的游戏

走这

问题分析:

  1. 完全莫得思路,体感就是最后一位大臣获得最多的钱(实际不是),结果看了题解发现又双有规律可循
  2. 由公式推导可得,当按照左右手积排序时,后面的大臣就可以获得最优解,(但也不一定,测试点有所有大臣的左手积小于右手数字的情况)
  3. 因此要在每次除以大臣的右手时对大臣判断是否为最大值
  4. 最重要的是:这里要用到高精除和高精乘,因为每次都要累乘所以乘积是不能被除法改变的!!!,而商每次都要清零

AC代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
struct people{
	ll left;
	ll right;
	ll mul;
};
people p[1010];
ll nums[30000] ={0,1};//1位初始化为1 ,nums[0]取位数 
ll res[30000];
ll ans[30000];
int r = 1;
int k = 1;//存储位数 
int m;//真答案的位数 
struct cp{
	bool operator()(people p1,people p2)
	{
		return p1.mul<p2.mul;
	}
};
void multiple(ll x)
{
	//进位 
	for(int i=1;i<=k;i++)
	{
		nums[i]*=x;
	}
	for(int i=1;i<=k;i++)
	{
		nums[i+1]+=nums[i]/10;
		nums[i]%=10;
	}
	while(nums[k+1]!=0)
	{
		k++;
		nums[k+1] = nums[k]/10;
		nums[k]%=10;
	}
}
void divide(ll x)//高精除
{
	memset(res,0,sizeof(res));//每次都要获得新的商 
	ll tmp = 0;
	r = k;//如果不用r存位数,那么积的位数就会改变,乘积就会错误 
	for(int i=r;i>0;i--)
	{
//		res[i] = nums[i]/x;//每次除把之前的nums改变了 
//		
//		nums[i-1] += ((nums[i]-res[i]*x)*10);//0位存余数
         tmp*=10;
         tmp+=nums[i];//用变量就不会改变nums的值 
         res[i] = tmp/x;
         tmp%=x;//不是取余10,如果x>10,那么商就是错误的 
	}
	while(res[r]==0&&r>1)
	{
		r--;
	}
} 
bool compares()
{
	if(m<r) return 1;
	if(m>r) return 0;
	if(m==r)
	{
		for(int i=m;i>0;i--)
		{
			if(res[i]>ans[i])
			{
				return 1;
			}
			else if(res[i]<ans[i])
			{
				return 0;
			}
			else {
				continue;
			}
		}
	}
}
void maxs()
{
	memset(ans,0,sizeof(ans)) ;
	for(int i=1;i<=r;i++)
	{
		ans[i] = res[i];
	}
	m = r;
}
int main()
{
	int n;
	cin>>n;
	cin>>p[0].left>>p[0].right;
	for(int i=1;i<=n;i++)
	{
		cin>>p[i].left>>p[i].right;
		p[i].mul = p[i].left*p[i].right;
	}
	/*根据规律,要最后一位大臣获取金币最少,那么left*right大的排在后面
	更能获得最优解 ,但要注意,不一定是最后一位就能获得最大值 
	*/
	sort(p+1,p+n+1,cp());
	/*n<=1000,且a、b<10000,10000的1000次方必然用高精*/
	for(int i=0;i<n;i++)//每获得一次结果就要与上一次比较 
	{
		multiple(p[i].left);
		divide(p[i+1].right);
		if(compares())//每除法会改变k的值,因此需要保护k,以便
		//乘积正常 
		{
			maxs();
		}
	}
	bool flag = true;
	
	for(int i=m;i>0;i--)//这个如果是0就输出不了 
	{
		if(ans[i]!=0)
		{
			flag = false;
		}
		if(!flag)
		{
			cout<<ans[i];
		}
	}
	cout<<endl;
} 

找bug找了几个小时…

问题四:
瑞士轮
走这

问题分析:

  1. 绝了,自己写的代码只能在牛客过,而洛谷四个TLE,看来排序还要优化
  2. 要注意通过函数修改结构体变量的值时,要加上引用符号否则不会改变
  3. 快排比归并排序更适合数据量大的情况,而每次比较相邻区间用归并排序更好,但他们都比sort快

牛客的AC代码:

#include <bits/stdc++.h>
using namespace std;
struct player{
	int id;
	int power;
	int score;
};
player p[200010];
struct cp{
	bool operator()(player p1,player p2)
	{
		if(p1.score==p2.score)
		{
			return p1.id<p2.id;
		}
		else{
			return p1.score>p2.score;
		}
	}
};
void play(player& p1,player& p2)
{
	if(p1.power>p2.power)
	{
		p1.score++;
		//p2.score--;
	}else{
		//p1.score--;
		p2.score++;
	}
}
int main()
{
	int n,r,q;
	cin>>n>>r>>q;
	for(int i=1;i<=2*n;i++)
	{
		cin>>p[i].score;
		p[i].id = i;
	}
	for(int i=1;i<=2*n;i++)
	{
		cin>>p[i].power;
	}
	sort(p+1,p+2*n+1,cp());
	for(int i=1;i<=r;i++)
	{
		for(int j=1;j<=2*n;j+=2)
		{
			play(p[j],p[j+1]);
		}
		sort(p+1,p+n*2+1,cp());
	}
	cout<<p[q].id<<endl;
} 

再分析:

  1. 每次比赛结束后,胜者和负者的顺序已经确定了,而sort更适合无序序列,并且每次排序都是打乱重来,而归并排序只要将胜者和负者这些有序序列归并即可(即归并更适合有序序列)

洛谷AC代码:

#include <bits/stdc++.h>
using namespace std;
struct player{
	int id;
	int power;
	int score;
};
player p[200010];
player wins[100010];
player loser[100010];
struct cp{
	bool operator()(player p1,player p2)
	{
		if(p1.score==p2.score)
		{
			return p1.id<p2.id;
		}
		else{
			return p1.score>p2.score;//错误3:score写成了power 
		}
	}
};
int main()
{
	ios::sync_with_stdio(0);
	int n,r,q;
	cin>>n>>r>>q;
	for(int i=1;i<=2*n;i++)
	{
		cin>>p[i].score;
		p[i].id = i;
	}
	for(int i=1;i<=2*n;i++)//错误1:没有*2 
	{
		cin>>p[i].power;
	}
	sort(p+1,p+2*n+1,cp());
	for(int i=1;i<=r;i++)
	{
		for(int j=1;j<=2*n;j+=2)
		{
			if(p[j].power>p[j+1].power)
	      {
		     p[j].score++;
	      }else{
		     p[j+1].score++;
	      }
		}
		stable_sort(p+1,p+n*2+1,cp());//基于归并排序实现 
	}
	cout<<p[q].id<<endl;
}

问题五
P1158 导弹拦截
走这

问题分析:

  1. 说实话,完全没有思路,但后来想到对所有导弹距离系统的距离进行排序,从最小的距离开始dfs,到最大的距离为止,但是我写的代码显示不出结果(菜是原罪)
  2. 看了题解发现是先对系统1的距离进行排序,然后再一个个枚举到系统二中,选出最小的平方和即可(我真的好菜555)
  3. 要注意的点是,既然求得是平方和,那么我们算距离的时候不用开方,这样还能省去精度问题
  4. 可能会用到非常规下标的情况,因为不能漏掉全交给1和全交给2的情况

AC代码:

#include <bits/stdc++.h>
using namespace std;
struct bomb{
	int x;
	int y;
	int dest1;
	int dest2;
};
struct cp1{
	bool operator()(bomb b1,bomb b2)
	{
		return b1.dest1<b2.dest1;
	}
};
bomb b[100010];
int n; 
int main()
{
	/*先将所有导弹到1号系统的距离进行排序,从最远方开始依次裁掉导弹 
	*/
	ios::sync_with_stdio(0);
	int x1,y1,x2,y2;
	cin>>x1>>y1>>x2>>y2;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>b[i].x>>b[i].y;
		b[i].dest1 = (pow(abs(b[i].x-x1),2)+pow(abs(b[i].y-y1),2));
		b[i].dest2 = (pow(abs(b[i].x-x2),2)+pow(abs(b[i].y-y2),2));
	}
	sort(b+1,b+1+n,cp1());
	int r2 = 0;
	int mins = 0xfffffff;//此时的平方和最小值 
	b[0].dest1 = 0;//避免漏掉全交给第二个系统的情况 
	b[n+1].dest2 = 0; //不从n+1开始会漏掉全交给第一个系统的情况 
	for(int i=n+1;i>0;i--)//将第一个系统能拦截的导弹线性排列 
	{
	     r2 = max(r2,b[i].dest2);//b[i].dest2定义为
		//拦截i~n颗导弹的最远距离
		mins = min(r2+b[i-1].dest1,mins); 
	}
	
	cout<<mins;
}

本文地址:https://blog.csdn.net/sky666tzz/article/details/109982634