BZOJ5322:[JXOI2018]排序问题——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=5322
https://loj.ac/problem/2543 <-可以看数据,要没有这数据我死活调不出来。
https://www.luogu.org/problemnew/show/P4561
题面见上。
我们知道可重元素全排列=元素个数!/(每个元素个数!加和)。
所以一个很显然的贪心就是我们取元素使得每个元素的个数都尽可能小。
我们先离散化,然后二分出sum,表示l~r我每个元素都取至少sum个(“至少”表示原数列可能有l~r的元素且其个数可能超过了sum个)。
然后各种处理我们就能得到(每个元素个数!加和)了,怎么样,是不是很好想呢?
然而不好写。
注意二分上界为n+m原因就是l~r的元素可能出现在原序列且其个数为n。
#include<cmath> #include<queue> #include<cstdio> #include<cctype> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int N=2e5+5; const int M=1e7+N; const int p=998244353; inline int read(){ int X=0,w=0;char ch=0; while(!isdigit(ch)){w|=ch=='-';ch=getchar();} while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); return w?-X:X; } ll qpow(ll k,int n){ ll res=1; while(n){ if(n&1)res=res*k%p; k=k*k%p;n>>=1; } return res; } int jc[M]; int a[N],b[N],num[N]; int n,m,l,r,t,cnt; inline ll calc(int k){ ll sum=(ll)k*cnt; for(int i=l;i<=r;i++){ if(num[i]<k)sum+=k-num[i]; } return sum; } void work(){ for(int i=1;i<=n;i++)num[a[i]]++; int L=0,R=n+m; while(L<R){ int mid=(L+R+1)>>1; if(calc(mid)<=m)L=mid; else R=mid-1; } ll ans=1; int last=m-calc(L); for(int i=1;i<=t;i++){ if(i<l||r<i)ans=ans*jc[num[i]]%p; else{ if(num[i]>L)ans=ans*jc[num[i]]%p; else if(last)last--,ans=ans*jc[L+1]%p; else ans=ans*jc[L]%p; } } ans=ans*qpow(jc[L+1],last)%p*qpow(jc[L],cnt-last)%p; printf("%lld\n",qpow(ans,p-2)*jc[n+m]%p); } void LSH(){ sort(b+1,b+n+1); t=unique(b+1,b+n+1)-b-1; for(int i=1;i<=t;i++)num[i]=0; for(int i=1;i<=n;i++){ a[i]=lower_bound(b+1,b+t+1,a[i])-b; } cnt=r-l; l=lower_bound(b+1,b+t+1,l)-b; r=upper_bound(b+1,b+t+1,r)-b-1; cnt-=r-l; } int main(){ jc[0]=1; for(int i=1;i<M;i++)jc[i]=(ll)jc[i-1]*i%p; int T=read(); while(T--){ n=read(),m=read(),l=read(),r=read(); for(int i=1;i<=n;i++)a[i]=b[i]=read(); LSH(); work(); } return 0; }
+++++++++++++++++++++++++++++++++++++++++++
+本文作者:luyouqi233。 +
+欢迎访问我的博客:http://www.cnblogs.com/luyouqi233/+
+++++++++++++++++++++++++++++++++++++++++++