牛客练习赛42 出题的诀窍

在这里插入图片描述
在这里插入图片描述

题解

对于每一个数,算贡献,对于有相同的数,我们只计算先出现的数的贡献。
对于第 i i i行的数 x x x,设前 i i i行中与 x x x重复的个数分别为 a 1 , a 2 , . . . . . . , a i − 1 , a i a_1,a_2,......,a_{i-1},a_{i} a1,a2,......,ai1,ai,那么这一行这个数的贡献为
a i ∗ ( n − a 1 ) ∗ ( n − a 2 ) ∗ . . . . . . ∗ ( n − a i − 1 ) ∗ n m − i a_i*(n-a_1)*(n-a_2)*......*(n-a_{i-1})*n^{m-i} ai(na1)(na2)......(nai1)nmi
其中
( n − a 1 ) ∗ ( n − a 2 ) ∗ . . . . . . ∗ ( n − a i − 1 ) (n-a_1)*(n-a_2)*......*(n-a_{i-1}) (na1)(na2)......(nai1)保证了前 i − 1 i-1 i1行不会出现相同的数
n m − i n^{m-i} nmi是因为即使后 m − i m-i mi行出现了相同的数,贡献也只会算在第 i i i行的数上

代码实现

由于题目的时限比较紧,使用unordered_map会超时,手写是一个选择,这里用的是不离散化的方法

首先将所有数字从小到大排序,第二关键字为所在的行,这样一来,相同的数字排到了一起,并且相同数字且在同一行的数也排在了一起,这样就能很方便地统计每种数字,在每一行出现的次数

用前后是否相同来判断是否到了分界点,具体实现见代码

代码
#include<bits/stdc++.h>
#define N 2010
#define INF 0x3f3f3f3f
#define eps 1e-10
#define pi 3.141592653589793
#define P 1000000007
#define LL long long
#define pb push_back
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;
namespace IO{ 
    #define BUF_SIZE 100000 
    #define OUT_SIZE 100000 
    #define ll long long 
    bool IOerror=0; 
    inline char nc(){ 
        static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE; 
        if (p1==pend){ 
            p1=buf; pend=buf+fread(buf,1,BUF_SIZE,stdin); 
            if (pend==p1){IOerror=1;return -1;} 
        } 
        return *p1++; 
    } 
    inline bool blank(char ch){return ch==' '||ch=='\n'||ch=='\r'||ch=='\t';} 
    inline void read(int &x){ 
        bool sign=0; char ch=nc(); x=0; 
        for (;blank(ch);ch=nc()); 
        if (IOerror)return; 
        if (ch=='-')sign=1,ch=nc(); 
        for (;ch>='0'&&ch<='9';ch=nc())x=x*10+ch-'0'; 
        if (sign)x=-x; 
    } 
};
using namespace IO;
struct node
{
    int a,b;
    bool operator < (const node z) const {
        return a==z.a?b<z.b:a<z.a;
    }
};
node q[N*N];
LL p[N];

int main()
{
    int n,m; read(n);read(m);
    p[0]=1;for (int i=1;i<=2000;i++) p[i]=p[i-1]*n%P;
    int cnt=0;
    for (int i=1;i<=m;i++) for (int j=1;j<=n;j++) 
    {
        int x; read(x);
        q[++cnt]=node{x,i};
    }
    sort(q+1,q+cnt+1); LL ans=0;
    q[cnt+1].a=2e9; int la=1;
    for (int i=1;i<=cnt;i++)  
        if (q[i].a!=q[i+1].a){
            int t=1,k=0;LL s=1;
            for (int j=la;j<i;j++){
                if (q[j].b==q[j+1].b) t++;else{
                    k++;
                    ans=(ans+(LL)q[j].a*s*t%P*p[m-k]%P)%P;
                    s=s*(n-t)%P;
                    t=1;
                }
            }
            if (la<i && q[i-1].b!=q[i].b) t=1;
            ans=(ans+(LL)q[i].a*s%P*t*p[m-k-1]%P)%P;
            la=i+1;
        }
    cout<<ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值