T1:Expectation
给出
n
n
n个点的树,树的边权是
[
0
,
1
]
[0,1]
[0,1]中随机的一个实数,求直径的期望长度。
n
≤
100
n\le100
n≤100
题解:
Code:待补。
T2:Sequence
一个长度为
n
n
n的包含大小写字母的字符串,
Q
Q
Q次询问区间本质不同的子序列个数,强制在线。
n
,
Q
≤
1
0
6
n,Q\le10^6
n,Q≤106
题解:
一般求本质不同的子序列数是设
f
[
i
]
f[i]
f[i]表示前
i
i
i个字母的序列数:
如果
s
[
i
]
s[i]
s[i]没有出现过,
f
[
i
]
=
f
[
i
−
1
]
+
f
[
i
−
1
]
+
1
f[i]=f[i-1]+f[i-1]+1
f[i]=f[i−1]+f[i−1]+1;反之,记
x
x
x为上次出现位置,
f
[
i
]
=
f
[
i
−
1
]
+
f
[
i
−
1
]
−
f
[
x
−
1
]
f[i]=f[i-1]+f[i-1]-f[x-1]
f[i]=f[i−1]+f[i−1]−f[x−1]。
但是这样做与
x
x
x相关,不好拓展到区间询问。
更改状态为
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i个字母中以字符
j
j
j结尾的本质不同的子序列数。
{
j
=
s
[
i
]
,
f
[
i
]
[
j
]
=
1
+
∑
f
[
i
−
1
]
[
k
]
j
≠
s
[
i
]
,
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
\begin{cases} j=s[i],~f[i][j]=1+\sum f[i-1][k]\\ j\neq s[i],~f[i][j]=f[i-1][j] \end{cases}
{j=s[i], f[i][j]=1+∑f[i−1][k]j=s[i], f[i][j]=f[i−1][j]
这个转移可以很方便地表示成矩阵形式。就是单位矩阵的第
s
[
i
]
s[i]
s[i]列全部改为1。
没有修改,可以预处理从左到右乘的前缀积矩阵,和从右到左乘的前缀逆矩阵。(从右到左是因为矩阵不一定满足交换律,
A
n
s
[
l
,
r
]
=
M
l
−
1
→
1
−
1
∗
M
1
→
r
Ans[l,r]=M_{l-1\to 1}^{-1}*M_{1\to r}
Ans[l,r]=Ml−1→1−1∗M1→r)
暴力转移是
O
(
∑
3
)
O(\sum^3)
O(∑3)的,需要挖掘矩阵性质。下面的分析截自LXno_name
Code:
#include<bits/stdc++.h>
#define maxn 1000005
#define M 52
using namespace std;
const int mod = 998244353;
int n,Q,a,b,p,q,r,ans,A[M+1][M+1],S[maxn][M+1],sum[M+1],tag[M+1],B[maxn][M+1];
char s[maxn];
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%s%d%d%d%d%d%d",s+1,&Q,&a,&b,&p,&q,&r),n=strlen(s+1);
for(int i=0;i<=M;i++) sum[i]=A[i][i]=1;
for(int i=1;i<=n;i++){
int t=s[i]<='Z'?s[i]-'A':s[i]-'a'+26; long long x;
for(int j=0;j<=M;j++) x=A[j][t],A[j][t]=sum[j],S[i][j]=sum[j]=(sum[j]+A[j][t]-x+mod)%mod;
}
memset(A,0,sizeof A);
for(int i=0;i<=M;i++) A[i][i]=1;
for(int i=1;i<=n;i++){
int t=s[i]<='Z'?s[i]-'A':s[i]-'a'+26;
for(int j=0;j<=M;j++){
A[t][j]=(A[t][j]+tag[j])%mod;
tag[j]=(tag[j]+mod-A[t][j])%mod,A[t][j]=(A[t][j]+mod-tag[j])%mod;
}
for(int j=0;j<=M;j++) B[i][j]=(A[M][j]+tag[j])%mod;
}
while(Q--){
int a0=a,b0=b;
a=(1ll*p*a0+1ll*q*b0+ans+r)%mod;
b=(1ll*p*b0+1ll*q*a0+ans+r)%mod;
int l=min(a%n,b%n)+1,r=max(a%n,b%n)+1;
if(l==1) ans=S[r][M];
else {ans=0;for(int i=0;i<=M;i++) ans=(ans+1ll*B[l-1][i]*S[r][i])%mod;}
}
printf("%d\n",ans);
}
T3:Counting
V
≤
100
V\le100
V≤100
题解:
因为关心的只是强连通分量序列的变化,所以可以尝试贪心地构造一种加边的方法使其可以涵盖所有强连通分量的变化情况。
可以看做从1往外连了一条链,其余的点都是单独的强连通分量,在链上不断缩强连通分量,就可以涵盖所有的情况。下面的分析同样截自LXno_name
Code:
#include<bits/stdc++.h>
#define maxn 105
using namespace std;
const int mod = 998244353;
int n,f[maxn*maxn][maxn][maxn];
inline int add(int x,int y){return (x+=y)>=mod?x-mod:x;}
inline int dec(int x,int y){return (x-=y)<0?x+mod:x;}
int main()
{
freopen("counting.in","r",stdin);
freopen("counting.out","w",stdout);
scanf("%d",&n);
f[0][1][1]=1;
for(int i=0;i<n*(n-1);i++)
for(int j=1,lim=min(n,i+(i==0));j<=lim;j++)
for(int k=j,lk=min(n,i+1);k<=lk;k++) if(f[i][j][k]){
int x=f[i][j][k];
if(k<n) f[i+1][j][k+1]=add(f[i+1][j][k+1],x);
else if((k*(k-1)+j*(j-1))/2>i) f[i+1][j][k]=add(f[i+1][j][k],x);
for(int l=1;l<=k-j;l++) f[i+1][j+l][k]=add(f[i+1][j+l][k],x);
}
for(int i=1;i<=n*(n-1);i++){
int ans=0;
for(int j=1,lim=min(n,i);j<=lim;j++)
for(int k=j,lk=min(n,i+1);k<=lk;k++)
ans=add(ans,f[i][j][k]);
printf("%d%c",ans,i==n*(n-1)?10:32);
}
}