[NOI online2022提高C] 如何正确地排序

题目描述
有一个 m×n 的数组 ai,j
定义:

f(i,j)=mink=1m(ak,i+ak,j)+maxk=1m(ak,i+ak,j)
你需要求出 i=1nj=1nf(i,j)
输入格式
第一行两个正整数 m,n

接下来 m 行,每行 n 个正整数表示 ai,j

输出格式
一行一个正整数,表示答案。

输入输出样例
输入 #1

3 5
1 7 2 2 7
9 10 4 10 3
7 7 8 10 2

输出 #1复制

564

说明/提示
【样例 1 解释】

f(3,5) 为例:

f(3,5)=max(a1,3+a1,5,a2,3+a2,5,a3,3+a3,5)+min(a1,3+a1,5,a2,3+a2,5,a3,3+a3,5)=max(9,7,10)+min(9,7,10)=10+7=17
下面给出 f(i,j) 的数表,第 i 行第 j 列表示 f(i,j)

20271822202734242923182420221722292224222023172218
它们的和是答案 564

统一一下,为了避免繁琐的分类讨论,我们把第一行多复制几次,填满四行。这样子答案很明显是不会变的,本来的最大值和最小值不会变。所以我们同意m=4处理。

首先我们考虑计算出一个数ai,j会被计算多少次,然后乘上。但是这样子计算需要三维偏序,代码量巨大,同时容易被卡常。所以我们反着来考虑,设他在每次涉及这一列时都会被计算一次,那么总答案为2×n×sum,sum为ai,j的和。要注意f(i,j)f(j,i)都会被计算到。

然后考虑一个数什么时候是没有贡献的,当存在ai+ajbi+bjci+cj时,bj在计算f(i,j)时没有贡献。我们可以枚举a,b,c数组然后减去b的在上面这种情况中的不合法现象。

如何计算是一个重点。首先对式子进行移项,得到aibibjaj

bicicjbj

对于j这个位置来说,要求有多少个数是满足上面两个式子的,这很明显是一个二位偏序问题。我们把所有aibibjaj进行排序,然后遍历。如果是前者那就更新,如果是后者那就询问。题目为了帮我们卡常,很良心地把范围缩小。

其实大体就是这样了。注意其实一个不在范围里的数他也会被减去两次,设a<b<c<d,在a,b,da,b,c两次枚举中都会减去b的不合法情况,刚好和一开始算的两倍抵消。

还有一个值得注意的地方,设a<b=c,那么在a,b,ca,c,b两次都会减去b,c的情况,然而我们需要保留一个。所以我们需要给b和c定一个大小的顺序,我这里是额定行数大的数大,在排序时做一个偏移就可以了。就是在aibi上增加上a的行数是否大于b的判断。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int m,n,a[5][N],tr[N+N],lsh[N+N],ret,mx,mn,t;
long long sum;
struct node{
	int x,y,val,z;
	bool operator<(const node&n)const{
		if(x!=n.x)
			return x<n.x;
		return z<n.z;
	}
}q[N+N];
int ask(int x)
{
	ret=0;
	for(;x;x-=x&-x)
		ret+=tr[x];
	return ret;
}
void update(int x)
{
	for(;x<=N+N;x+=x&-x)
		tr[x]++;
}
void solve(int x,int y,int z)
{
	memset(tr,0,sizeof(tr));
	for(int i=1;i<=n;i++)
	{
		q[i<<1]=(node){a[y][i]-a[x][i],a[z][i]-a[y][i],a[y][i],1};
		q[2*i-1]=(node){a[x][i]-a[y][i]+(x>y),a[y][i]-a[z][i]+(y>z),0,0};
	}
	sort(q+1,q+n+n+1);
	for(int i=1;i<=n+n;i++)
	{
		if(q[i].z)
			sum-=1LL*q[i].val*ask(q[i].y+N);
		else
			update(q[i].y+N);
	}
}
int main()
{
	scanf("%d%d",&m,&n);
	for(int i=1;i<=m;i++) 
		for(int j=1;j<=n;j++)
			scanf("%d",a[i]+j);
	for(int i=m+1;i<=4;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=a[1][j];
	for(int i=1;i<=4;i++)
		for(int j=1;j<=n;j++)
			sum+=a[i][j];
	sum=sum*n*2;
	for(int i=1;i<=4;i++)
		for(int j=1;j<=4;j++)
			if(i!=j)
				for(int k=1;k<=4;k++)
					if(i!=k&&j!=k)
						solve(i,j,k);
	printf("%lld",sum);
	return 0;
}
posted @   灰鲭鲨  阅读(78)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示