BZOJ4640 决斗

好神的一道题,orz ljss……

因为每次要把用过的牌堆扔掉,所以其实相当于固定你的选择牌堆顺序,然后对面选择牌的堆顺序是在1~n的全排列里随机一个

首先求出p[i],代表小于等于你的第i个数的他的数的数量

先考虑一下n<=2000,k<=1e9的做法

我们的目标就是求出赢x场的情况有多少种,然后除以n!就得到了赢x场的几率,然后就可以算期望了

我们用f[i][j]表示考虑你的前i个数,从中选出j个数,并且对面任意选出j个数,你的选出的每个数与他选出的数中的一个数一一对应,并且你的每个数都大于等于其对应的那个数的方案数

转移的时候考虑i在不在选出的那j个数里,得到dp式f[i][j]=f[i-1][j]+f[i-1][j-1]*(p[i]-(j-1))

p[i]-(j-1)代表的是当前对手能选出的小于等于你的第i个数的数的数量,因为之前你有j-1个数都大于等于他的j-1个数了,而题目保证了两个序列是递增的,所以可以直接减j-1

第一维可以滚动掉,这样的话我们就可以O(n^2)求出f[n][i],以下都用f[i]来表示f[n][i]

用g[i]表示赢x场的方法数,则

接下来考虑n<=1e6,k=1或2的做法

先考虑k=1,一共有n!种排列,我们要求的是,也就是

P枚举1~n的全排列,win(P)表示在全排列P下你赢的次数,w(i)表示在P下第i场赢没赢

考虑你的第i个数对分子的贡献,他会贡献p[i]*((n-1)!)

意义是他一定要与一个小于等于他的配对,而剩下的n-1个可以任意配对

所以答案就是(sigma p[i])/n

再考虑k=2,我们要求的是,也就是

因为w(i)为0或1,所以w(i)^2那项其实相当于w(i),与k得1的情况是一样的,那么考虑后边,w(i)w(j)为1当且仅当w(i)为1且w(j)为1

当i固定时,考虑这时后边的值,可以视为i的贡献,w(j)为1的方法数为sigma k=1 to i-1 p[k],而在w(j)为1的情况下w(i)为1的方法数为p[i]-1

所以k=2也解决了

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<bitset>
using namespace std;
#define MAXN 1000010
#define MAXM 1010
#define ll long long
#define INF 1000000000
#define MOD 1000000007
#define eps 1e-8
char xB[1<<15],*xS=xB,*xT=xB;
#define getc() (xS==xT&&(xT=(xS=xB)+fread(xB,1,1<<15,stdin),xS==xT)?0:*xS++)
inline int read(){
    char ch=getc();
    int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+(ch-'0');ch=getc();}
    return x*f;
}
int n,k;
int a[MAXN],b[MAXN];
ll f[MAXN],g[MAXN];
ll p[MAXN];
ll nn,nn1;
ll fac[MAXN],ine[MAXN];
ll mi(ll x,ll y){
	ll re=1;
	while(y){
		if(y&1){
			(re*=x)%=MOD;
		}
		(x*=x)%=MOD;
		y>>=1;
	}
	return re;
}
ll C(int n,int m){
	return fac[n]*ine[m]%MOD*ine[n-m]%MOD;
}
int main(){
	int i,j;
	scanf("%d%d",&n,&k);
	nn=mi(n,MOD-2);
	nn1=mi(n-1,MOD-2);
	for(i=1;i<=n;i++){
		a[i]=read();
	}
	for(i=1;i<=n;i++){
		b[i]=read();
	}
	for(i=1,j=1;i<=n;i++){
		while(b[j]<=a[i]&&j<=n){
			j++;
		}
		p[i]=j-1;
	}
	if(k==1){
		for(i=1;i<=n;i++){
			g[i]=(g[i-1]+p[i])%MOD;
		}
		printf("%lld\n",g[n]*nn%MOD);
	}else if(k==2){
		for(i=1;i<=n;i++){
			g[i]=(g[i-1]+p[i])%MOD;
			f[i]=(f[i-1]+2*g[i-1]%MOD*(p[i]-1))%MOD;
		}
		printf("%lld\n",(f[n]*nn1%MOD+g[n])*nn%MOD);
	}else{
		fac[0]=ine[0]=ine[1]=1;
		for(i=1;i<=n;i++){
			fac[i]=(fac[i-1]*i)%MOD;
		}
		for(i=2;i<=n;i++){
			ine[i]=(MOD-MOD/i)*ine[MOD%i]%MOD;
		}
		for(i=1;i<=n;i++){
			ine[i]=(ine[i-1]*ine[i])%MOD;
		}
		f[0]=1;
		for(i=1;i<=n;i++){
			for(j=i;j;j--){
				f[j]=(f[j]+f[j-1]*(p[i]-(j-1)+MOD))%MOD;
			}
		}
		ll ans=0;
		for(i=n;i;i--){
			g[i]=f[i]*fac[n-i]%MOD;
			for(j=i+1;j<=n;j++){
				(g[i]+=MOD-g[j]*C(j,i)%MOD)%=MOD;
			}
			(ans+=g[i]*mi(i,k))%=MOD;
		}
		printf("%lld\n",ans*ine[n]%MOD);
	}
	return 0;
}

/*
\
*/


d是ffdgfdg

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BZOJ 2908 题目是一个数据下载任务。这个任务要求下载指定的数据文件,并统计文件中小于等于给定整数的数字个数。 为了完成这个任务,首先需要选择一个合适的网址来下载文件。我们可以使用一个网络爬虫库,如Python中的Requests库,来帮助我们完成文件下载的操作。 首先,我们需要使用Requests库中的get()方法来访问目标网址,并将目标文件下载到我们的本地计算机中。可以使用以下代码实现文件下载: ```python import requests url = '目标文件的网址' response = requests.get(url) with open('本地保存文件的路径', 'wb') as file: file.write(response.content) ``` 下载完成后,我们可以使用Python内置的open()函数打开已下载的文件,并按行读取文件内容。可以使用以下代码实现文件内容读取: ```python count = 0 with open('本地保存文件的路径', 'r') as file: for line in file: # 在这里实现对每一行数据的判断 # 如果小于等于给定整数,count 加 1 # 否则,不进行任何操作 ``` 在每一行的处理过程中,我们可以使用split()方法将一行数据分割成多个字符串,并使用int()函数将其转换为整数。然后,我们可以将该整数与给定整数进行比较,以判断是否小于等于给定整数。 最后,我们可以将统计结果打印出来,以满足题目的要求。 综上所述,以上是关于解决 BZOJ 2908 数据下载任务的简要步骤和代码实现。 希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值