总结
这次人直接掉没
\(T1\) 期望不会
\(T2\) 字符串不会
\(T3\) 打了一个多小时的表拿了 \(10\) 分
A. Expectation
分析
神仙期望题
只会 \(50\) 分做法
对于链的情况,答案是 \(\frac{n-1}{2}\),因为每一条边的期望长度都是 \(\frac{1}{2}\),一共有 \(n-1\) 条这样的边
对于菊花图的情况,我们肯定要选择两个较长的边拼在一起
一条边的期望长度为 \(\frac{n-1}{n}\)
另一条边的期望长度为 \(\frac{n-2}{n}\)
加起来就是 \(\frac{2n-3}{n}\)
对于 \(n \leq 5\) 的情况,除了样例就是菊花图或者链,特判一下就行了
B. Sequence
分析
对于 \(40\%\) 的数据,设 \(f[i]\) 为当前以 \(i\) 位置结尾的本质不同子序列数
设 \(cnt[i]\) 为以 \(i\) 号字符结尾的本质不同子序列数
转移的时候为了去重我们要把上一个该种字符的贡献减去,并把当前的贡献加上
int solve(rg int l,rg int r){
rg int sum=0;
for(rg int i=l;i<=r;i++){
f[i]=addmod(sum,1);
sum=addmod(sum,f[i]);
sum=delmod(sum,cnt[getrk(s[i])]);
cnt[getrk(s[i])]=f[i];
}
sum=1;
for(rg int i=l;i<=r;i++){
sum=addmod(sum,cnt[getrk(s[i])]);
f[i]=cnt[getrk(s[i])]=0;
}
return sum;
}
正解是对一种只能拿 \(20\) 分的 \(dp\) 的优化
设 \(f[i][j]\) 当前在位置 \(i\),以 \(j\) 结尾的本质不同的子序列数
为了方便,新开一个字符 \(53\) ,表示之前没有选字符
int solve(rg int l,rg int r){
f[l-1][53]=1;
for(rg int i=l;i<=r;i++){
for(rg int j=1;j<=53;j++){
f[i][getrk(s[i])]=addmod(f[i][getrk(s[i])],f[i-1][j]);
if(j!=getrk(s[i])) f[i][j]=addmod(f[i][j],f[i-1][j]);
}
}
rg int sum=0;
for(rg int i=1;i<=53;i++) sum=addmod(sum,f[r][i]);
f[l-1][53]=0;
for(rg int i=l;i<=r;i++){
for(rg int j=1;j<=53;j++){
f[i][j]=0;
}
}
return sum;
}
我们发现这个东西可以写成矩阵快速幂的形式
当前字符结尾的可以由所有的转移过来
其它字符结尾的只能由自己转移过来
矩阵形如:
1 0 0 0 1 0
0 1 0 0 1 0
0 0 1 0 1 0
0 0 0 1 1 0
0 0 0 0 1 0
0 0 0 0 1 1
也就是个单位矩阵,当前字符所代表的那一列变为1
因为要使用前缀和优化
所以需要处理的就是当前原矩阵的前缀积以及逆元矩阵的前缀积
逆元矩阵形如
1 0 0 0 -1 0
0 1 0 0 -1 0
0 0 1 0 -1 0
0 0 0 1 -1 0
0 0 0 0 1 0
0 0 0 0 -1 1
我们的初始状态是空串,也就是一个行向量 \(H=[1,0,0,0,...,0]\)
最终我们转移完成后需要求和,也就是要乘上一个列向量 \(L=[1,1,1,...,1,1,1]\)
那么答案就应该是 \(H×inv_{l−1}×pre_r×L\)
我们发现行向量乘上一个值,相当于只要第一行
列向量乘上一个一个值,相当于对一行求和
所以我们只需要对逆元矩阵维护第一行的值
对原矩阵维护每一行的和就行了
暴力去乘肯定是不可以的
一个矩阵乘上某一位的原矩阵,发现结果是对于矩阵的每一行,第 \(s_i\)
列要加上其余所有列的值
逆元矩阵乘上一个矩阵的效果是,除了第 \(s_i\) 行以外,其余每行都要对位减去这一行
打个标记就行了
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=1e6+5,maxk=58,mod=998244353;
inline int addmod(rg int now1,rg int now2){
return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
return now1*=now2,now1>=mod?now1%mod:now1;
}
int pre[maxn][maxk],inv[maxn][maxk],jz[maxk][maxk],sum[maxk];
char s[maxn];
int Q,p,q,xs,n,latans,a[maxn],b[maxn],z[maxn],x[maxn],y[maxn],laz[maxn];
int getrk(rg char ch){
if(ch>='a' && ch<='z') return ch-'a'+2;
else return ch-'A'+28;
}
void init(){
rg int tmp=getrk(s[1]),jl;
for(rg int i=1;i<=53;i++) jz[i][i]=1,sum[i]=2;
for(rg int i=1;i<=53;i++) jz[i][tmp]=1;
sum[tmp]=1;
for(rg int i=1;i<=53;i++) pre[1][i]=sum[i];
for(rg int i=2;i<=n;i++){
tmp=getrk(s[i]);
for(rg int j=1;j<=53;j++){
jl=jz[j][tmp];
jz[j][tmp]=sum[j];
sum[j]=delmod(sum[j],jl);
sum[j]=addmod(sum[j],jz[j][tmp]);
}
for(rg int j=1;j<=53;j++) pre[i][j]=sum[j];
}
memset(jz,0,sizeof(jz));
inv[0][1]=1;
tmp=getrk(s[1]);
for(rg int i=1;i<=53;i++) jz[i][tmp]=mod-1;
for(rg int i=1;i<=53;i++) jz[i][i]=1,sum[i]=0;
sum[tmp]=1;
for(rg int i=1;i<=53;i++) inv[1][i]=jz[1][i];
for(rg int i=2;i<=n;i++){
tmp=getrk(s[i]);
for(rg int j=1;j<=53;j++) jz[tmp][j]=addmod(jz[tmp][j],laz[j]);
for(rg int j=1;j<=53;j++) laz[j]=delmod(laz[j],jz[tmp][j]);
for(rg int j=1;j<=53;j++) jz[tmp][j]=delmod(jz[tmp][j],laz[j]);
for(rg int j=1;j<=53;j++) inv[i][j]=addmod(jz[1][j],laz[j]);
}
}
int solve(rg int l,rg int r){
rg int sum=0;
for(rg int i=1;i<=53;i++){
sum=addmod(sum,mulmod(inv[l-1][i],pre[r][i]));
}
return sum;
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
init();
Q=read(),a[0]=read(),b[0]=read(),p=read(),q=read(),xs=read();
for(rg int i=1;i<=Q;i++){
a[i]=1LL*(1LL*p*a[i-1]+1LL*q*b[i-1]+1LL*z[i-1]+1LL*xs)%mod;
b[i]=1LL*(1LL*p*b[i-1]+1LL*q*a[i-1]+1LL*z[i-1]+1LL*xs)%mod;
x[i]=std::min(a[i]%n,b[i]%n)+1;
y[i]=std::max(a[i]%n,b[i]%n)+1;
z[i]=solve(x[i],y[i])%mod;
}
printf("%d\n",z[Q]);
return 0;
}
C. Counting
分析
对于每一个 \(E\) 序列中的状态,一定可以表示成一个大强联通分量拖着一条长链和一些散点的形式
因为随着我们去加边图中强联通分量的个数一定会减少或者不变
对应着图,我们可以把一条长链接到环上或者延长我们的链
所以考虑用这种图去 \(dp\)
设 \(f[i][j][k]\) 为已经加了 \(i\) 条边,大联通分量里有 \(j\) 个点,\(k\) 层圈的方案数
考虑新加入一条边的贡献
\(f[e][i][j]=f[e-1][i][j]+\sum_{k=0}^{i-1}f[e-1][k][j-1]\)
前面表示加到了链的后面
后面表示新形成了一个环
之所以要记圈的个数是因为我们要判断当前加入的边的数量是否合法
最少的边数肯定是 \(j+k-1\),一个点也没有接在后面
最多的边数就是强联通分量里的点已经满了 \(j(j-1)\),其它的点形成满边 \(DAG\) \((n-j)(n-j-1)/2\),强连通内部和外部连边 \(j(n-j)\)
转移的时候记一下前缀和就能达到 \(n^4\) 的复杂度了
代码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rg register
inline int read(){
rg int x=0,fh=1;
rg char ch=getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') fh=-1;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*fh;
}
const int maxn=105,mod=998244353;
inline int addmod(rg int now1,rg int now2){
return now1+=now2,now1>=mod?now1-mod:now1;
}
int n,f[maxn*maxn][maxn][maxn],sum[maxn*maxn][maxn][maxn],m;
//边 点 环
int main(){
n=read(),m=n*(n-1);
f[0][1][0]=1;
for(rg int i=1;i<=n;i++) sum[0][i][0]=1;
rg int l,r;
for(rg int i=1;i<=m;i++){
for(rg int j=1;j<=n;j++){
for(rg int k=0;k<j;k++){
l=j+k-1,r=j*(j-1)+(n-j)*(n-j-1)/2+j*(n-j);
if(i>=l && i<=r) f[i][j][k]=f[i-1][j][k];
if(k && i>=l && i<=r) f[i][j][k]=addmod(f[i][j][k],sum[i-1][j-1][k-1]);
sum[i][j][k]=addmod(sum[i][j-1][k],f[i][j][k]);
}
}
}
rg int ans=0;
for(rg int i=1;i<=m;i++){
ans=0;
for(rg int j=0;j<=n;j++){
ans=addmod(ans,sum[i][n][j]);
}
printf("%d ",ans);
}
printf("\n");
return 0;
}