test20190925 老L
100+0+0=100。概率题套路见的太少了,做题策略不是最优的。
排列
给出 n 个二元组,第 i 个二元组为(ai,bi)。
将 n 个二元组按照一定顺序排成一列,可以得到一个排列。显然,这样的排列有 n!种。
我们定义一个排列为坏的的排列,当且仅当这个排列的二元组的第一个元素单调不减或者第二个元素单调不减。
定义一个好的排列为一个非坏排列
如: (1,3) (3,3) (2,2)是一个好排列,(1,1) (2,2) (3,3)是一个坏排列。
请问给出的二元组能构成多少种不同的好排列。由于答案过大,请对 998244353 取膜。
对于 100%的数据 1<=ai,bi<=n<=3e5
题解
容斥一下就完了。时间复杂度 \(O(n \log n)\)。
co int N=300000+10;
int fac[N];
struct node {int x,y;}p[N];
il bool cmp_x(co node&a,co node&b){
return a.x<b.x;
}
il bool cmp_y(co node&a,co node&b){
return a.y<b.y or (a.y==b.y and a.x<b.x);
}
int main(){
freopen("per.in","r",stdin),freopen("per.out","w",stdout);
int n=read<int>();
fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
for(int i=1;i<=n;++i) read(p[i].x),read(p[i].y);
int ans=fac[n];
sort(p+1,p+n+1,cmp_x);
// cerr<<"ans="<<ans<<endl;
int sum=1;
for(int i=1,j=0;i<=n+1;++i)
if(p[i].x!=p[j].x) sum=mul(sum,fac[i-j]),j=i;
ans=add(ans,mod-sum);
// cerr<<"sum="<<sum<<endl;
sort(p+1,p+n+1,cmp_y);
sum=1;
for(int i=1,j=0;i<=n+1;++i)
if(p[i].y!=p[j].y) sum=mul(sum,fac[i-j]),j=i;
ans=add(ans,mod-sum);
// cerr<<"sum="<<sum<<endl;
sum=1;
for(int i=2;i<=n;++i)
if(p[i].x < p[i-1].x) {sum=0;break;}
if(sum)for(int i=1,j=0;i<=n+1;++i){
if(p[i].y!=p[j].y or p[i].x!=p[j].x) sum=mul(sum,fac[i-j]),j=i;
}
ans=add(ans,sum);
// cerr<<"sum="<<sum<<endl;
printf("%d\n",ans);
return 0;
}
中档题
n 个人参加比赛,比赛方式如下:
- 两两比一场,比赛后将每个选手看做一个点,若 i 赢了 j 则连边 i->j
- 将图中强连通分量内继续比,即上一步操作中强连通分量内的所有选手递归进行 1,2,直到每个强连通分量大小为 1。
i<j 时 i 有 a/b 的概率赢 j,问每个人比赛的场数的总和的期望,答案对 998244353 取模
对于 100%的数据 1<=n<=2000,1<=a,b<=100,0<a/b<1
CF913F Strongly Connected Tournament
答案只和点数有关,设\(dp_n\)表示\(n\)个点游戏总场次的期望,\(dp_0=dp_1=0\)。对于有向完全图,一定有且仅有一个入度为0的强连通分量,据此转移。(入度为\(0\)也行)
- 首先选择\(i\)个点形成强连通分量,设\(scc_i\)表示\(i\)个点形成强连通分量的概率。
- 然后这\(i\)个点必须是出度为\(0\)的强连通分量(拓扑序最后一个),换句话说必须被所有其它\(n-i\)个点打败。设\(cw_{n,i}\)表示\(n\)个点中选\(i\)个点满足它们都打败了其它\(n-i\)个点的概率。
- 假设确定了最后一个强连通分量是\(i\)个点,那么这\(i\)个点进行了一轮游戏\(\frac{i(i-1)}{2}\),然后这\(i\)个点进入下一轮\(dp_i\),其它\(n-i\)个点视为正常继续游戏\(dp_{n-i}\),本轮游戏相互之间还有\(i(n-i)\)场。
移项解方程即可。
接下来计算\(cw_{n,i}\)表示\(n\)个点中选\(i\)个点满足它们都打败了其它\(n-i\)个点的概率。
显然\(cw_{n,0}=1\)。打败的概率和编号密切相关,所以通过依赖于点\(n\)的归属来计算:
第\(n\)个点要么是集合中的点,要么是集合外的点。
接下来计算\(scc_n\)表示\(n\)个点形成强连通分量的概率,显然\(s_1=1\)。直接算形成强连通分量相当困难,考虑容斥,用图论套路枚举\(1\)所在SCC大小进行计算。
复杂度\(O(n^2)\)。
co int N=2000+10;
int pp[N],pq[N];
int cw[N][N],scc[N],dp[N];
int main(){
freopen("medium.in","r",stdin),freopen("medium.out","w",stdout);
int n=read<int>(),a=read<int>(),b=read<int>();
int p=mul(a,fpow(b,mod-2)),q=add(1,mod-p);
pp[0]=pq[0]=1;
for(int i=1;i<=n;++i)
pp[i]=mul(pp[i-1],p),pq[i]=mul(pq[i-1],q);
// cerr<<"pp="<<pp[n]<<" pq="<<pq[n]<<endl;
cw[0][0]=1;
for(int s=1;s<=n;++s){
cw[s][0]=1;
for(int i=1;i<=s;++i)
cw[s][i]=add(mul(pp[i],cw[s-1][i]),mul(pq[s-i],cw[s-1][i-1]));
}
// cerr<<"cw="<<cw[n][n/2]<<endl;
for(int s=1;s<=n;++s){
scc[s]=1;
for(int i=0;i<s-1;++i)
scc[s]=add(scc[s],mod-mul(scc[i+1],cw[s][i+1]));
}
// cerr<<"scc="<<scc[n]<<endl;
for(int s=2;s<=n;++s){
int sum=0;
for(int i=1;i<s;++i)
sum=add(sum,mul(scc[i],mul(cw[s][i],add(dp[i],add(dp[s-i],add(i*(s-i),i*(i-1)/2))))));
sum=add(sum,mul(scc[s],s*(s-1)/2));
dp[s]=mul(sum,fpow(add(1,mod-scc[s]),mod-2));
}
printf("%d\n",dp[n]);
return 0;
}
简单题
给些俄罗斯套娃,每个套娃有一个 in,一个 out,in 表示里面空腔的大小,out 表示整体大小,out-in就是套娃厚度,out[i]<=in[j]时 i 可以放进 j,给 n 个套娃,问满足条件的组合有多少种。
- 是内外嵌套的一套(该组合只能有一个最外层的套娃)
- 足够大,这一套不能放进其他套娃里,也就是这一套最外层的 out 要大于所有的 in;
- 空气的体积之和最小。
对于 100%的数据 1<=n<=2e5
CF1197E Culture Code
每个套娃建一个点,每一个套娃向可以套上的套娃连边,建立源点汇点跑最短路条数。
考虑优化建边,将每个点拆成一个 in 和一个 out,只有通过同时两个点才表示选了这一个套娃。
相邻的 out 点大的向小的连边,源汇点向可能作为起点和终点的点连边。
最后形成的图一定是个 DAG,所以可以用拓扑排序求最短路计数。注意一开始需要把所有度数为0的点入队,虽然它们不会造成贡献,但是需要它们来维护拓扑序。
co int N=200000+10;
struct node{int out,in;}a[N];
il bool operator<(co node&a,co node&b){
return a.out<b.out or (a.out==b.out and a.in<b.in);
}
vector<int> to[N*2],we[N*2];
bool vis[N*2];
co int mod=1000000000+7;
int deg[N*2],dis[N*2],cnt[N*2];
deque<int> Q;
il void add_edge(int x,int y,int w){
// cerr<<"add "<<x<<" "<<y<<" w="<<w<<endl;
to[x].push_back(y),we[x].push_back(w),++deg[y];
}
int main(){
freopen("easy.in","r",stdin),freopen("easy.out","w",stdout);
int n=read<int>();
for(int i=1;i<=n;++i) read(a[i].out),read(a[i].in);
sort(a+1,a+n+1);
int s=2*n+1,t=2*n+2;
for(int i=2;i<=n;++i) add_edge(2*i-1,2*i-3,0);
for(int i=1;i<=n;++i) add_edge(2*i-1,2*i,-a[i].out);
for(int i=1;i<=n;++i){
int l=0,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(a[mid].out<=a[i].in) l=mid;
else r=mid-1;
}
if(l) add_edge(2*i,2*l-1,a[i].in),vis[2*i]=vis[2*l-1]=1;
}
for(int i=1;i<=n;++i)
if(!vis[2*i]) add_edge(2*i,t,a[i].in);
for(int i=n;i>=1;--i){
if(vis[2*i-1]) break;
add_edge(s,2*i,0);
}
fill(dis+1,dis+t+1,1e9);
dis[s]=0,cnt[s]=1;
for(int i=1;i<=t;++i)
if(!deg[i]) Q.push_back(i);
while(Q.size()){
int x=Q.front();Q.pop_front();
for(int i=0;i<(int)to[x].size();++i){
int y=to[x][i],w=we[x][i];
if(dis[y]>dis[x]+w) dis[y]=dis[x]+w,cnt[y]=cnt[x];
else if(dis[y]==dis[x]+w) cnt[y]=(cnt[y]+cnt[x])%mod;
if(--deg[y]==0) Q.push_back(y);
}
}
// cerr<<"check"<<endl;
// for(int i=1;i<=t;++i)
// cerr<<i<<" dis="<<dis[i]<<" cnt="<<cnt[i]<<endl;
printf("%d\n",cnt[t]);
return 0;
}