虽然期末考只有 \(3\) 天了,但是还是忍不住 OI 了。
019 AGC044E Random Pawn
令 \(f_i\) 为 \(i\) 位置开始能得到的最大权值,那么有:
如果 \(b_i\) 均为零,那么 \(f_i\geqslant\frac{f_{i-1}+f_{i+1}}{2}\),这实际上是对 \((i,a_i)\) 做一个凸壳。由于是环,我们不妨在 \(\max a\) 处断环为链。
我们想把 \(b_i\) 消除,不妨使用一组偏移量 \(c_i\) 使得 \(f_i\leftarrow f_i+c_i\),可解得:
令 \(c_1=c_2=0\) 可直接递推,最后找个凸壳就好了,复杂度 \(O(n)\)。
#include<stdio.h>
const int maxn=200005;
int n,pos,top;
long long a[maxn],c[maxn],ta[maxn];
int b[maxn],tb[maxn],stk[maxn];
long double ans;
struct vec{
long long x,y;
inline vec operator -(const vec&p){
return vec{x-p.x,y-p.y};
}
inline long long operator ^(const vec&p){
return x*p.y-p.x*y;
}
}p[maxn];
inline int anticlock(vec a,vec b,vec c){
return ((b-a)^(c-a))>0;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&ta[i]);
if(pos==0||ta[i]>ta[pos])
pos=i;
}
for(int i=1;i<=n;i++)
scanf("%d",&tb[i]);
for(int i=1;i<=n+1;i++)
a[i]=ta[(pos+i-2)%n+1],b[i]=tb[(pos+i-2)%n+1];
n++;
for(int i=3;i<=n;i++)
c[i]=2*b[i-1]+2*c[i-1]-c[i-2];
for(int i=1;i<=n;i++){
a[i]-=c[i],p[i]=vec{a[i],0ll+i};
if(i<n)
ans+=c[i];
}
stk[++top]=1,stk[++top]=2;
for(int i=3;i<=n;i++){
while(top>=2&&anticlock(p[stk[top-1]],p[stk[top]],p[i])==0)
top--;
stk[++top]=i;
}
n--;
for(int i=1,j=1;i<=n;i++){
while(j<top&&i>=stk[j])
j++;
ans+=1.0L*(a[stk[j-1]]*(stk[j]-i)+a[stk[j]]*(i-stk[j-1]))/(stk[j]-stk[j-1]);
}
printf("%.10Lf\n",ans/n);
return 0;
}
020 CFgym102798E So Many Possibilities...
比较简单的题。
看到数据范围就应该想到状压零集合 \(S\),令 \(f_{i,S}\) 表示走了 \(i\) 步,目前零集合为 \(S\) 的概率。
由于不能记录每个位置剩余的值,我们不妨钦定这次转移将哪个位置变成 \(0\) 并一次性分配其概率。(也可以无事发生)
虽说除去已经分配的操作,每个数被某次操作分配到的概率都是均等的,但是不同阶段的概率还是不同,于是我们需要提前把这个概率放到 dp 状态里,也就是每一步都钦定了一个位置放。
最后还会剩下一些操作,再用一个 dp 统计一下。令 \(g_{i,S}\) 为对集合 \(S\) 操作 \(i\) 步后没有一个零的方案数,转移显然。
复杂度 \(O((n+m)m2^n)\),可以通过。
#include<stdio.h>
const int maxn=20,maxm=105,maxs=1<<15;
int n,m,S;
int a[maxn],sum[maxs];
double ans;
double f[maxm][maxs],C[maxm][maxm],g[maxm][maxs];
int main(){
for(int i=0;i<=100;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
scanf("%d%d",&n,&m),S=(1<<n)-1;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0][0]=1;
for(int i=0;i<=S;i++){
g[0][i]=1;
if(i){
int j=__builtin_ctz(i);
sum[i]=sum[i^(1<<j)]+a[j+1];
}
}
for(int i=1;i<=m;i++)
for(int j=0;j<=S;j++){
if(j){
for(int k=1;k<=n;k++)
if((j>>(k-1))&1){
int lst=j^(1<<(k-1));
if(i-1>=sum[lst])
f[i][j]+=f[i-1][lst]*C[i-1-sum[lst]][a[k]-1];
}
int l=__builtin_ctz(j);
for(int k=0;k<a[l+1]&&k<=i;k++)
g[i][j]+=g[i-k][j^(1<<l)]*C[i][k];
f[i][j]/=(n-__builtin_popcount(j)+1);
}
if(__builtin_popcount(j)<n)
f[i][j]+=f[i-1][j]/(n-__builtin_popcount(j));
}
for(int i=0;i<=S;i++)
if(m>=sum[i])
ans+=__builtin_popcount(i)*f[m][i]*g[m-sum[i]][S^i];
printf("%.6lf\n",ans);
return 0;
}
021 CFgym102956B Beautiful Sequence Unraveling
和上一道题存在相似之处,不过还简单一点。
令 \(f_{i,j}\) 为值域 \([1,i]\),长度为 \(j\) 的序列,值域不一定填满,最大是 \(i\) 的方案数,我们将其 dp 出来后再容斥出一个 \(g_i\) 表示离散化后值域为 \([1,i]\) 的方案数就好了。
\(f\) 的转移可以考虑找到最后一个不合法的位置 \(k\) 以及其取值 \(v\),\(k\) 之后的是一个 \([v,i]\) 的子问题,而前面就是一个 \(\max=t\) 的任意序列,可以直接算。
我们把 \(v^{-?}\) 带到状态里就可以前缀和优化了,复杂度 \(O(n^3)\)。
#include<stdio.h>
const int maxn=405;
int n,m,mod,ans;
int inv[maxn],f[maxn][maxn],g[maxn],sum[maxn][maxn][2],mul[maxn][maxn],imul[maxn][maxn];
int C(int a,int b){
int res=1;
for(int i=a,j=1;j<=b;i--,j++)
res=1ll*res*i%mod*inv[j]%mod;
return res;
}
int main(){
scanf("%d%d%d",&n,&m,&mod);
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
for(int i=1;i<=n;i++){
mul[i][0]=imul[i][0]=1;
for(int j=1;j<=n;j++)
mul[i][j]=1ll*mul[i][j-1]*i%mod,imul[i][j]=1ll*imul[i][j-1]*inv[i]%mod;
}
for(int t=1;t<=n;t++){
for(int i=1;i<=n;i++)
sum[0][i][0]=sum[0][i][1]=0;
for(int i=1;i<=n;i++){
int inc=mul[t][i],dec=0;
for(int j=1;j<=t;j++)
dec=(dec+1ll*mul[j][i]*sum[i-1][j][0])%mod,inc=(inc+1ll*mul[j-1][i]*sum[i-1][j][1])%mod;
f[t][i]=(inc-dec+mod)%mod;
for(int j=1;j<=t;j++){
sum[i][j][0]=(sum[i-1][j][0]+1ll*imul[j][i]*(f[t-j+1][i]-f[t-j][i]+mod))%mod;
sum[i][j][1]=(sum[i-1][j][1]+1ll*imul[j-1][i]*(f[t-j+1][i]-f[t-j][i]+mod))%mod;
}
}
}
for(int i=1;i<=n;i++){
g[i]=f[i][n];
for(int j=1;j<i;j++)
g[i]=(g[i]-1ll*C(i,j)*g[j]%mod+mod)%mod;
ans=(ans+1ll*C(m,i)*g[i])%mod;
}
printf("%d\n",ans);
return 0;
}
023 CFgym102979E Expected Distance
两点距离还是根据期望线性性拆成到根距离之和减去 lca 期望到根距离。
令 \(f_i\) 为 \(i\) 期望到根距离,转移显然。
我们考虑怎么求两个点 \(x,y(x<y)\) 的 lca 期望到根距离 \(h(x,y)\),考察 \(y\) 祖先中最深的编号大于 \(x\) 的结点 \(z\),可以发现 \(z\) 取值的概率分布和权值 \(a\) 相同(即与 \(y\) 无关),列出转移:
可以发现式子与 \(y\) 无关,令 \(g_i=h(i,*)\) 即可。
复杂度 \(O(n)\),如果懒可以带个 \(\log\)。
#include<stdio.h>
const int maxn=300005,mod=1000000007;
int n,m;
int a[maxn],c[maxn],f[maxn],g[maxn],v[maxn];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)
res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,s=0;i<n;i++)
scanf("%d",&a[i]),s+=a[i],v[i]=ksm(s,mod-2);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=2,sf=1ll*a[1]*c[1]%mod,sg=0;i<=n;i++){
f[i]=(c[i]+1ll*sf*v[i-1])%mod,g[i]=1ll*(1ll*a[i]*f[i]+sg)%mod*v[i]%mod;
sf=(sf+1ll*a[i]*(f[i]+c[i]))%mod,sg=(sg+1ll*a[i]*g[i])%mod;
}
for(int i=1,x,y;i<=m;i++)
scanf("%d%d",&x,&y),printf("%d\n",x==y? 0:(f[x]+f[y]-2ll*g[x<y? x:y]+mod+mod)%mod);
return 0;
}