E. I Love Balls
显然两个人的得分期望加起来等于全部得分总和,于是我们只用算一个就行了,那就算Alice的吧,假如没有特殊球,记sum为普通球的总分数,那么Alice的得分为:
n为偶数:那么Alice得到一半的球那么期望就是sum/2
n为奇数Alice得到n/2+1个球,期望就是(n/2+1)/n*sum
在考虑特殊球,假如我们把球被取出来的顺序当成一个排列,那么原本就是n-k个普通球的排列,而特殊球要随机的出现在n-k+1个空位当中,我们可以惊奇的发现特殊球的出现对普通球的归属并不影响,原本第奇数个普通球是Alice的,出现特殊球后第奇数个球仍然是Alice的,举个例子,第一个普通球是Alice的,第二个是Bob的,那么如果在他们中间出现任意多个特殊球,最后第二个球还是Bob的,所以特殊球对普通球所产生的贡献并不影响,因此可以单独考虑两者的贡献,普通球的贡献上面已经算过了接下来考虑特殊球的贡献
特殊球是随机出现在那n-k+1个空位里面的,而且出现在奇数位前面的特殊球一定是Alice的,例如出现在第一个普通球或第三个普通球的前面一定都被Alice所得,记特殊球的总分为sum2,那么Alice的特殊球期望得分就是(奇数位的个数)/(n-k+1)*sum2,总期望两者加起来即可
附上代码(相当简短)
#include<iostream>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,m,k,d,a[400010];
int qsm(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=res*a%mod;
a=a*a%mod;
b=b>>1;
}
return res;
}
void solve()
{
int sum1=0,sum2=0;
cin>>n>>k;
int m=n-k+1;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=k;i++)sum2+=a[i];
for(int i=k+1;i<=n;i++)sum1+=a[i];
int res1=sum1*(m/2)%mod*qsm(n-k,mod-2)%mod;//普通球的贡献
int res2=sum2*((m+1)/2)%mod*qsm(m,mod-2)%mod;//特殊球的贡献
cout<<(res1+res2)%mod<<' '<<((sum1+sum2-res1-res2)%mod+mod)%mod<<endl;
}
signed main()
{
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
int T;cin>>T;
while(T--)solve();
return 0;
}
F. array-value
二分+异或字典tire
看到第k大,并且k是1e10的范围那么肯定是不支持线性的,那么就尝试二分,二分一个答案x,看区间价值小于等于的区间个数是否小于k,那么这个区间个数怎么求呢,我们可以发现如果r固定,那么那么区间价值一定随着左端点l的减小减小的,那么就有单调性,那么必然存在一个点记为f[r],使得,左端点在[1,f[r]],右端点为r的区间的价值全部小于x,对于这个f[r],我们可以用01字典tire来找,具体怎么找这个还是有点不太好理解的,等下会写在代码注释里,这里不过多叙述,但是我们的维护一个01前缀的最大位置(代表最后的位置),那么我们每次二分的只需比较所有f[i]的总和和k就好了,f[i]的总和就是比区间价值小于等于x的区间数
附上代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5*40+114;
int id[N];
int tr[N][2],idx;
int n,a[N],rt;
int dp[N];
long long k;
void insert(int x,int y)
{
int p = 1;
for (int i = 30; i >= 0; i -- )
{
int &s = tr[p][x >> i & 1];
if (!s) s = ++ idx;
p = s;
id[p]=max(id[p],y);
//假如有两个数10011010 在数组a中的位置是4,10010111在数组中的位置是5,那么1001这个前缀的id就是5,取最靠后的位置
//想必有了这个例子应当可以明白id数组是什么意思了
}
}
int query(int x,int y)
{
int p=1;
int res=0;
for(int i=30;i>=0;i--)
{
if(y>>i&1)
{
if(x>>i&1)//如果两个数在这一位都为1
{
res=max(res,id[tr[p][1]]);//直接给y或上一个1,那么就不用往下走下去了,直接算答案
p=tr[p][0];//给y或上一个0,那么x和y这一位还是一,无法比出大小,所以要继续走
}
else{
p=tr[p][1];//如果这一位y是1,x是0那么必需给y或上一个1,不然y就比x大了,并往下走,继续比较
}
}
else
{
if(x>>i&1)//这一位y为0,x为1
{
res=max(res,id[tr[p][0]]);//直接给y或上一个0,那么y就比x小了,后面不用比了,直接算答案
p=tr[p][1];//给y或上一个1,两者相等继续比下去
}
else{
p=tr[p][0];//两者都为0,或上一个0,继续比
}
}
}
res=max(res,id[p]);
return res;
}
bool check(int x)
{
++idx;
long long sum=0;
for(int i=1;i<=n;i++)//这里每次二分都要将字典树重新建一遍,或者用可持久化字典tire
{
dp[i]=query(x,a[i]);//查找和a[i]异或起来小于等于x的数的最后一个位置在哪
insert(a[i],i);//将a[i]插入字典树中
}
for(int i=1;i<=n;i++)dp[i]=max(dp[i-1],dp[i]);//之前算的dp[i]只是a[i]自己,要让整个区间合法,必然是之前的区间的max
for(int i=1;i<=n;i++)sum+=dp[i];
for(int i=0;i<=idx;i++)tr[i][0]=tr[i][1]=id[i]=0;//清空字典树
idx=0;
if(sum>=k)return true;
return false;
}
void solve()
{
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
long long l=-1,r=2e9+114;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)==true) r=mid;
else l=mid+1;
}
cout<<r<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--)solve();
return 0;
}