2020.7.23 T2数列 (jz暑假训练day8)

题目大意

有个数列a[i],让其重新排列使得a[i]*a[i+1] (i=1~n-1)求和最大,之后输出重新排列后的编号,要求字典序最小。

正解

这个题是真的烦人,代码也很繁琐,就粗略看看吧。然后具体是贪心做法,假如没有重复的,那么显然是头放最小,尾放次小,头再放此次小,这样。(注意考虑字典序,也就是考虑是头放最小还是次小)之后假如有重复的,就自行讨论一下。

  1. 如果这个数出现的次数>=2,那么这个数必定会放入待解决数列头和待解决数列尾。
    而取决于放多少个进待解决数列头,取决于,他后面的前两个数的最前位置及出现
    次数。
  2. 如果这个数出现的次数=1
    a) 如果 i 位置元素的值小于 j 位置元素的值,显然会放到 i 的右方。
    b) 大于的话,显然会放到 i 的左方。
    c) 等于的话
    i. 如果后一个数的最小位置小于现在的数的位置,则放在 j 的左方。
    ii. 反之,放在 i 的右方。
    讨论的也许与读者的思路有所不同,希望读者自己仔细推一遍。

然后扯一下是先要排序啊,按权值再按编号。
(照抄题解,是真的烦人啊这道题)

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define N 100007
using namespace std;
int t,n,b[N],ans[N];
struct node{
	int x,w;
}a[N];
bool cmp(node a,node b){
	if(a.x==b.x) return a.w<b.w;
	return a.x<b.x;
}
int find(int x){
	while(x<n&&a[x+1].x==a[x].x) x++;
	return x;
}
int main(){
	freopen("permutation.in","r",stdin);
	freopen("permutation.out","w",stdout);
	scanf("%d",&t);
	while(t--){
		memset(a,0,sizeof(a));
		memset(ans,0,sizeof(ans));
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i].x),a[i].w=i,b[i]=a[i].x;
		sort(a+1,a+n+1,cmp);//排序
		int l=1,r=1,l1,r1,ll=1,rr=n;//l,r表示我们现在这个数的头和尾,l1,r1表示这个数下一个数的头和尾,包括下面的l2,r2指再下一个数的头和尾
		r=find(r);
		l1=r1=r+1;
		r1=find(r1);//find就是找目前这个数的尾
		while(ll<=rr){
			if(l1==n+1){
				for(int i=l;i<=r;i++)
					ans[ll++]=a[i].w;
				continue;
			}
			if(l==r){//若只有一个当前的数只有一个
				if(l1==r1){//若它后面的数也只有一个
					if(b[ans[ll-1]]<b[ans[rr+1]])
						ans[ll++]=a[l].w,ans[rr--]=a[l1].w;
					else if(b[ans[ll-1]]>b[ans[rr+1]])
						ans[ll++]=a[l1].w,ans[rr--]=a[l].w;
					else{
						if(a[l].w<a[l1].w)
							ans[ll++]=a[l].w,ans[rr--]=a[l1].w;
						else ans[ll++]=a[l1].w,ans[rr--]=a[l].w;
					}
					l=r=r1+1;
					r=find(r);
					l1=r1=r+1;
					r1=find(r1);//当这种情况我们一次可以吧两个数都做完,然后更新l,r,l1,r1
				}else{//后面的数不止一个
					if(b[ans[ll-1]]<b[ans[rr+1]])
						ans[ll++]=a[l].w;
					else if(b[ans[ll-1]]>b[ans[rr+1]])
						ans[rr--]=a[l].w;
					else{
						if(a[l].w<a[l1].w)
							ans[ll++]=a[l].w;
						else ans[rr--]=a[l].w;
					}
					l=l1,r=r1;
					l1=r1=r+1;
					r1=find(r1);//这种情况我们无法将两种情况做完,所以l=l1,r=r1
				}
			}else{//目前的数不止一个
				ans[ll++]=a[l++].w;
				ans[rr--]=a[r--].w;//前面的两个数是必放前面与后面的,接下来考虑中间的部分前面放几个,后面放几个(这就是这道题的恶心的东西,判断字典序是真的毒瘤)
				int l2=r1+1,r2=l2;//要考虑它后两个数
				r2=find(r2);
				if(l2!=n+1&&a[l1].w>a[l2].w&&(l1==r1)){//若第后两个数比第后一个数优。
					int w=r+1;
					for(int i=l;i<=r;i++)
						if(a[i].w<a[l2].w)
							ans[ll++]=a[i].w;
						else{
							w=i;
							break;
						}
					for(int i=r;i>=w;i--)
						ans[rr--]=a[i].w;
				}else{//否则
					int w=r+1;
					for(int i=l;i<=r;i++)
						if(a[i].w<a[l1].w)
							ans[ll++]=a[i].w;
						else{
							w=i;
							break;
						}
					for(int i=r;i>=w;i--)
						ans[rr--]=a[i].w;
				}
				l=l1;
				r=r1;
				l1=r1=r+1;
				r1=find(r1);
			}
		}
		for(int i=1;i<=n;i++)
			printf("%d ",ans[i]);
		printf("\n");
	}
}

猜你喜欢

转载自blog.csdn.net/jay_zai/article/details/107562504
今日推荐