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所以暂且不考虑改这个

转载于:https://www.cnblogs.com/Pump-six/p/10495975.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值