牛客-出题的诀窍(统计次数)

出题的诀窍

 

思路:

这道题目如果不考虑重复就很简单了,就是先求出总和sum,ans = sum*m^(n-1)(这里我n表示行数,m表示列数,

与题中给出的n,m表示的意义不同)。

 

考虑重复的就是对每一行进行考虑,对每一行的每一个元素x进行计算,

用序列a1,a2,a3……ai……an表示元素x在前i行(1-i行)出现的次数,(求解ai可以用a2 = a1*k,k表示在第i行x出现的次数)

所以每行的不同元素x对整个排序的贡献是(m-a1)*(m-a2)*(m-a3)……(m-ai-1)*ai*x*m^(n-i),

其中m*(n-i)表示后面的元素序列的组合,(这里不用考虑后面重复的元素,因为就算选到了的相同的元素也只算当前的

元素个数,后面的不算,以样例中的的3为例,只有

(1,3,1),(1,3,3),(2,3,1),(2,2,3),四种情况,其中我们统计第二行3时会统计(1,3,1),(1,3,3),(2,3,1),(2,2,3)四种情况,所以3出现4次,而且后面的3也没算,因为这里统计的只是当前的3,再统计第三行的3时会统计到

(1,2,3),(2,2,3)两种情况。

 

具体细节参见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn = 4000400;
const LL MOD = 1e9+7;
LL dd[maxn]={0};
struct Node{
	LL id,data;
	Node(LL x,LL y):data(x),id(y){}
	Node(){}
}cur[maxn];
bool cmp(Node a,Node b){
	if(a.data!=b.data) return a.data<b.data;
	else return a.id<b.id;
}
int main(void)
{
	LL m,n,i,j,cnt=0,x;
	scanf("%lld%lld",&m,&n);
	for(dd[0]=1,i=1;i<=n;i++) dd[i]=dd[i-1]*m%MOD; //预处理m^i 
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			scanf("%lld",&x);
			cur[++cnt] = Node(x,i);  //存入节点 
		}
	}
	sort(cur+1,cur+cnt+1,cmp); 
	LL pos=1,t1,t2,ans=0,tp;
	for(i=1;i<=cnt;i++)
	if(cur[i].data!=cur[i+1].data){ //统计同一个数在不同层出现的次数 
		t1=1,t2=0,tp=1;
		for(j=pos;j<=i;j++){
			if(cur[j].id==cur[j+1].id) t1++; //t1表示它在同一层出现的次数 
			else{
				t2++; //t2表示它当前在第几层 
				ans = (ans+(cur[j].data%MOD*t1%MOD*tp%MOD*dd[n-t2]%MOD)%MOD)%MOD;//统计一个数字在第t2层出现的次数对所有出现次数做出的贡献。 
				tp = (tp%MOD*(m-t1))%MOD;//tp表示在前i层(就是1~i-1层中与ai不同的数字的组合方式),就是(n-a1)*(n-a2)……(n-ai)的值。 
				t1 = 1;
			}
		}
		//单独处理最后一个,因为最后一个的层数和前面的层数没有关系。 
		ans = (ans+(cur[i].data%MOD*t1%MOD*tp%MOD*dd[n-t2-1]%MOD)%MOD)%MOD; 
		pos = i+1;
	}
	printf("%lld\n",ans);
	return 0;
}

参考文章

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值