03-07考试总结
3月7日考试总结
结果3月8号才写
T1
得分情况 :
预计分数 : 40pts
实际得分 : 40pts
网上翻了自己的KMP板子写的 , 其实算没分
正解 :
由于我是看这道题AC代码理解的题解 , 所以下文的解释如果锅了 , 请务必联系我 出来挨打
考虑\(f(t)\)的实际意思 , 我(题)们(解)发现 :
一个串的\(f\)值即为有多少个非前缀子串与前缀相同
证明的话可看到KMP的next(fail)数组相关
而题目要求的串的价值是它的每一个连续子串的\(f\)的和
那么考虑一对相同的子串 , 设左边的串为\(x\) , 右边的为 \(y\)
那么我们发现 , 对于所有左端点在\(x_l\) , 右端点在\(y_r\)后的原串的连续子串 , 这对\(x\),\(y\)有1点贡献
众所周知这样的串有\(S_{len} - y_r+1\)个 , 即\((x,y)\)的贡献为\(S_{len}-y_r+1\)
然后我们发现要求的是所有前缀的价值
那么就考虑每在当前串尾部加入一个字符的贡献
首先 , \(y_r < now\)的\((x,y)\)都又产生了1点贡献 , 这里一发前缀和就好
然后考虑所有\(y_r = now\)的\((x,y)\) , 这个的数量就是与当前串的后缀相同的串的数量
这玩意可以用SAM的parent树算 , 我觉得我写不清楚语文过差QAQ , 就具体见代码8
核(全)心(部)代码 :
#include<bits/stdc++.h>
#define r(a) (a)=read<int>()
#define rl(a) (a)=read<ll>()
#define MAXN(n) ((max##n)+10)
using namespace std;
typedef long long ll;
const int maxn=100000;
const int mod=998244353;
struct Sam{
struct node{
int link,len;
int next[30];
}st[MAXN(n)<<1];
int sz,last;
Sam(){init();}
inline void init(){
for(int i=1;i<=sz;i=-~i)st[i]=st[sz+1];
sz=1;last=1;st[1].len=0;st[1].link=-1;
}
inline void insert(int c){
int cur=++sz,p=last;
st[cur].len=st[p].len+1;
while(p!=-1&&(!st[p].next[c]))
st[p].next[c]=cur,p=st[p].link;
if(p==-1)st[cur].link=1;
else{
int q=st[p].next[c];
if(st[q].len==st[p].len+1)st[cur].link=q;
else{
int clone=++sz;
st[clone].len=st[p].len+1;st[clone].link=st[q].link;
memcpy(st[clone].next,st[q].next,sizeof(st[q].next));
while(p!=-1&&st[p].next[c]==q)
st[p].next[c]=clone,p=st[p].link;
st[q].link=st[cur].link=clone;
}
}last=cur;
}
inline void Build_tree();
}sam;
inline int Add(int a,int b){return (a%mod+b%mod)%mod;}
inline int Mul(int a,int b){return (1ll*(a%mod)*(b%mod)%mod);}
struct Graph{
struct edge{int v,next;};
edge e[MAXN(n)<<2];
int n,tail,head[MAXN(n)<<1],Val[MAXN(n)<<1];
int fa[MAXN(n)<<1],dep[MAXN(n)<<1],size[MAXN(n)<<1],son[MAXN(n)<<1];
int top[MAXN(n)<<1],id[MAXN(n)<<1],Rid[MAXN(n)<<1],cnt;
inline void add(int u,int v){e[++tail].next=head[u],head[u]=tail,e[tail].v=v;}
void dfs1(int u,int Fa){
fa[u]=Fa,dep[u]=dep[fa[u]]+1;size[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;dfs1(v,u),size[u]+=size[v];
if(size[v]>size[son[u]])son[u]=v;
}
}
void dfs2(int u,int tFa){
id[u]=++cnt,Rid[cnt]=u,top[u]=tFa;
if(!son[u])return;
dfs2(son[u],tFa);
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;if(v!=son[u])dfs2(v,v);
}
}
struct SGT{
#define ls (rt<<1)
#define rs (rt<<1|1)
int det[MAXN(n)<<3],sum[MAXN(n)<<3],add[MAXN(n)<<3],*Val,*Rid;
SGT(){
memset(det,0,sizeof(det));
memset(sum,0,sizeof(sum));
memset(add,0,sizeof(add));
}
inline void push_up(int rt){sum[rt]=Add(sum[ls],sum[rs]);}
inline void push_down(int rt){
if(!add[rt])return;
sum[ls]=Add(sum[ls],Mul(det[ls],add[rt])),add[ls]=Add(add[ls],add[rt]);
sum[rs]=Add(sum[rs],Mul(det[rs],add[rt])),add[rs]=Add(add[rs],add[rt]);
add[rt]=0;
}
inline void build(int rt,int l,int r){
if(l==r){
det[rt]=Val[Rid[l]];
return;
}
int mid=(l+r)>>1;
build(ls,l, mid );
build(rs,mid+1,r);
det[rt]=Add(det[ls],det[rs]);
}
inline void update(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R){
sum[rt]=Add(sum[rt],det[rt]);
add[rt]=Add(add[rt],1);return;
}
int mid=(l+r)>>1;push_down(rt);
if(L<=mid)update(ls,l, mid ,L,R);
if(R> mid)update(rs,mid+1,r,L,R);
push_up(rt);
}
inline int query(int rt,int l,int r,int L,int R){
if(L<=l&&r<=R){
return sum[rt];
}
int mid=(l+r)>>1,ans=0;push_down(rt);
if(L<=mid)ans=Add(ans,query(ls,l, mid ,L,R));
if(R> mid)ans=Add(ans,query(rs,mid+1,r,L,R));
return ans;
}
#undef ls
#undef rs
}T;
void build_SGT(){
dfs1(1,0);dfs2(1,1);
T.Val=(*this).Val,T.Rid=(*this).Rid;
T.build(1,1,n);
}
inline void update(int u){
while(u){
T.update(1,1,n,id[top[u]],id[u]);
u=fa[top[u]];
}return;
}
inline int query(int u){
int ans=0;
while(u){
int res=T.query(1,1,n,id[top[u]],id[u]);
ans=Add(ans,res);u=fa[top[u]];
}return ans;
}
}G;
int n;
char s[MAXN(n)];
int Sam_num[MAXN(n)];
template<class T>inline T read();
inline void Sam::Build_tree(){
G.n=sz;
for(int i=1;i<=sz;i=-~i){
G.add(st[i].link,i);
if(st[i].link!=-1)G.Val[i]=st[i].len-st[st[i].link].len;
}return;
}
int main(){
freopen("s.in","r",stdin);
freopen("s.out","w",stdout);
r(n);scanf("%s",s+1);
for(int i=1;i<=n;i=-~i){
Sam_num[i]=sam.sz+1;
sam.insert(s[i]-'a'+1);
}
sam.Build_tree();
G.build_SGT();
int Sum=0,Ans=0;
for(int i=1;i<=n;i=-~i){
int Res=G.query(Sam_num[i]);
Sum=Add(Sum,Res);Ans=Add(Ans,Sum);
G.update(Sam_num[i]);printf("%d\n",Ans);
}
return 0;
}
template<class T>inline T read(){
T sum=0,f=1;char k=getchar();
for(;k< '0'||'9'< k;k=getchar())
if(k=='-')f=-1;
for(;'0'<=k&&k<='9';k=getchar())
sum=(sum<<1)+(sum<<3)+(k^48);
return sum*f;
}
太长了而且忘记压行了quq 麻烦将就一哈
T2
得分情况 :
预计分数 : 40pts
实际得分 : 10pts
像7号T3一样挂在了边界条件上
可笑的是我把自己之前考虑了边界的40pts代码删了
挂了30pts
正解 :
首先我(题)们(解)发现对于一个已知的序列\(a\) , 我们可以用网络流来判断是否合法
流程如下(摘自题解) :
S连向每一行 , 流量为\(a_i\) ; 每一列连向T , 流量为\(b_i\) ; 所有行向所有列连边 , 流量为1 ;
如果\(\sum_{i=1}^{n} a_i=\sum_{i=1}^{m} b=MaxFlow\),则合法;否则不合法
好了口胡一哈这玩意为啥是对的 :
首先 , 我们有 : \(\sum_{i=1}^{n} a_i=\sum_{i=1}^{m} b=MaxFlow\) , 这说明\(a_i\)和\(b_i\)都跑满了
然后发现每行每列都连了流量1的边 , 那么这之中的一些边也是满的
回到原来的图上 , 使得 : \(bok_{ij}=1 \; [edge_{i->j} \; is \; full]\)
看得出来这样的棋子的放置一定合法
好那么我们可以把这个原题吃掉了 , 现在要求的是有多少种合法序列\(a\) , 满足上面那个情况
发现也不好算 , 于是转成最小割 : \(min \{ prea_x + preb_y + (n-x)*(m-y)\}\)
\(prea\;preb\)表示\(a\;b\)排序后的前缀和
然后发现可以靠这玩意求出\(prea_x\)的下界 , 设为\(low_x\)
然后DP :
设 \(f_{i,j,k}\)表示在序列\(a\)中填了\(i\)个数 , 这\(i\)个数最大为\(j\)且和为\(k\)的方案数
则当 : \(x \in \left[ 0,n-i \right] \; \& \& \; k + \left( j+1 \right) * x \geq low_{i+x}\)
有 : \(f_{i+x,j+1,k+(j+1)*x}+=f_{i,j,k}*C_{n-i}^{x}\)
解释 : 在剩下的空位之中放入\(x\)个\(j+1\) , 方案数\(*C_{n-i}^{x}\)
对于后继状态 , 它比之前多放了\(x\)个数 , 最大值变为\(j+1\) , 和多了\(x*(j+1)\)
于是就有了上面的那个方程
剩下的就是跑这玩意了 , 细节见代码
for(int i=0;i<=n;i=-~i){
for(int j=0;j<=m;j=-~j)
lowa[i]=max(lowa[i],sumb-preb[j]-(n-i)*(m-j));
if(lowa[i]==0)f[i][0][0]=C[n][i];
}
for(int i=0;i<=n;i=-~i){
for(int j=0;j<=m;j=-~j){
for(int k=0;k<=n*m;k=-~k){
if(!f[i][j][k])continue;
for(int x=0;x<=n-i;x=-~x)
if(lowa[i+x]<=k+(j+1)*x&&k+(j+1)*x<=sumb)
f[i+x][j+1][k+(j+1)*x]=(f[i+x][j+1][k+(j+1)*x]
+(1ll*f[i][j][k]*C[n-i][x]%mod))%mod;
}
}
}
T3
得分情况 :
预计分数 : 0pts
实际得分 : 0pts
写T240分去了 , 没时间了
正解 :
因为不会NTT所以暂且不考虑改这个