码迷,mamicode.com
首页 > 其他好文 > 详细

2019年7月博客汇总下

时间:2020-01-17 00:06:26      阅读:58      评论:0      收藏:0      [点我收藏+]

标签:矩阵乘法   基本   坐标   usaco   org   his   move   解决   重载   

[ZJOI2007]捉迷藏

这是我最近写过最长的代码QAQ
码力太弱了QAQ
动态点分治模板题。
我们可以用三种堆来维护答案,这些堆要求支持删除非顶元素,以及查询次小值。我们把两个STL堆封装起来就可以实现。

三种堆:

d[x]表示以x为根的点分树中所有黑点到它分治爹的距离
c[x]表示以x为根的所有点分儿子d堆中的最大值
ans表示全局的最大值

我们从c中取出最大值和次大值就可以得到过这个点分根的最长链。我们不断用它来更新答案。

注意d的定义是到分治父亲的父亲的最大值
c数组要加入一个0

Code

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
namespace orz{
const int N=210000;
const int inf=2147483647;
struct heap{
    priority_queue<int>q,rm;
    heap(){
        q.push(-inf);
        rm.push(-inf+1);
    }
    inline void push(int x){
        q.push(x);
    }
    inline void remove(int x){
        if(q.top()==x)q.pop();
        else rm.push(x);
    }
    inline int top(){
        while(q.top()==rm.top())q.pop(),rm.pop();
        return q.top();
    }
    inline void pop(){
        while(q.top()==rm.top())q.pop(),rm.pop();
        q.pop();
    }
    inline int second(){
        int mx=top();
        if(mx==-inf)return -inf;
        q.pop();
        int ans=top();
        q.push(mx);
        return ans;
    }
    inline int size(){
        return q.size()-rm.size();
    }
    inline void show(){
        priority_queue<int>res;
        while(size()){
            res.push(top());
            printf("%d ",top());
            pop();
        }
        putchar('\n');
        while(res.size()){
            q.push(res.top());
            res.pop();
        }
    }
}ans,d[N],c[N];
//d表示所有的黑点到这个分治根的距离
//c是所有分治儿子的最长链
int head[N],ver[N],next[N],tot;
int size[N];
int father[N];
int dis[N][20];
int color[N],cnt;
int depth[N];
int n,m;
int root,all,rootmax;
bool rm[N];
inline void add(int x,int y){
    next[++tot]=head[x],head[x]=tot,ver[tot]=y;
    next[++tot]=head[y],head[y]=tot,ver[tot]=x;
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
void write(int x){
    if(x)write(x/10),putchar('0'+x%10);
}
inline void print(int x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    else if(!x){
        putchar('0');
    }
    write(x);
    putchar('\n');
}
inline bool getOrder(){
    char t;
    do{t=getchar();}while(t!='G'&&t!='C');
    return t=='G';
}
void getroot(int x,int fa){
    size[x]=1;
    int maxpart=-inf;
    for(int i=head[x];i;i=next[i]){
        if(rm[ver[i]]||ver[i]==fa)continue;
        getroot(ver[i],x);
        size[x]+=size[ver[i]];
        maxpart=max(maxpart,size[ver[i]]);
    }
    maxpart=max(maxpart,all-size[x]);
    if(maxpart<rootmax)rootmax=maxpart,root=x;
}
inline void addAns(heap &a){
    ans.push(a.top()+a.second());
}
inline void removeAns(heap &a){
    ans.remove(a.top()+a.second());
}
inline int ask(){
    if(cnt<=1)return cnt-1;
    return ans.top();
}
inline void BFS(int x,int dep){
    queue<int>q;
    q.push(x);
    int t;
    while(q.size()){
        t=q.front();
        q.pop();
        d[x].push(dis[t][depth[father[x]]]);
        for(int i=head[t];i;i=next[i]){
            if(rm[ver[i]])continue;
            if(dis[ver[i]][dep])continue;
            dis[ver[i]][dep]=dis[t][dep]+1;
            q.push(ver[i]);
        }
    }
}
int build(int x,int fa,int dep,int sum){
    //得到根
    rootmax=inf;
    all=sum;
    getroot(x,0);
    x=root;
    //保证size是对的
    getroot(x,0);
    //记录在点分树上的爸爸
    father[x]=fa;
    rm[x]=true;
    depth[x]=dep;
    //得到这一层所有点到分治根的距离
    BFS(x,dep);
    int son;
    //自己也算在所有链中
    c[x].push(0);
    for(int i=head[x];i;i=next[i]){
        if(rm[ver[i]])continue;
        son=build(ver[i],x,dep+1,size[ver[i]]);
        c[x].push(d[son].top());
    }
    addAns(c[x]);
    return x;
}
inline void turnOn(int x){
    //先更新对自己的影响
    removeAns(c[x]);
    c[x].remove(0);
    addAns(c[x]);
    //一层一层往上跳
    for(int i=x;father[i];i=father[i]){
        //先删去对答案为影响
        removeAns(c[father[i]]);
        c[father[i]].remove(d[i].top());
        d[i].remove(dis[x][depth[father[i]]]);
        c[father[i]].push(d[i].top());
        addAns(c[father[i]]);
    }
}
inline void turnOff(int x){
    removeAns(c[x]);
    c[x].push(0);
    addAns(c[x]);
    for(int i=x;father[i];i=father[i]){
        removeAns(c[father[i]]);
        c[father[i]].remove(d[i].top());
        d[i].push(dis[x][depth[father[i]]]);
        c[father[i]].push(d[i].top());
        addAns(c[father[i]]);
    }
}
inline void work(){
//  for(int i=1;i<=n;++i)
//      c[i].show();
//  for(int i=1;i<=3;++i){
//      for(int j=1;j<=n;++j)
//          printf("%d ",dis[j][i]);
//      putchar('\n');
//  }
    cnt=n;
    int x;
    m=read();
    for(int i=1;i<=m;++i){
        switch(getOrder()){
            case true:
                print(ask());
                break;
            case false:
                x=read();
                switch(color[x]){
                    case 0:
                        //开灯
                        turnOn(x);
                        color[x]=1;
                        --cnt;
                        break;
                    case 1:
                        //熄灯
                        turnOff(x);
                        color[x]=0;
                        ++cnt;
                        break;
                }
                break;
        }
    }
}
int QAQ(){
    n=read();
    for(int i=1;i<n;++i)
        add(read(),read());
    build(1,0,1,n);
    work();
    return false;
}
}
int main(){
    return orz::QAQ();
}

7.27爆零赛

这次考试题起名比较随意,题目难度不是按顺序排的,大概是倒序的。

给出\(n\)个正整数\(a_1,a_2…a_n\)和一个质数\(mod\).一个变量\(x\)初始为\(1\).进行\(m\)次操作.每次在\(n\)个数中随机选一个\(a_i\),然后\(x=xa_i\).问\(m\)次操作之后\(x\)的取值的期望。
答案输出a乘b的逆元的形式

NOIP模拟赛T1考O(n^2)矩阵乘法和原根,我佛了。
首先我们可以把问题转化为一个假期望,我们只需要求出每一种数值的方案数乘上数值大小除以\(n^m\)就可以了。
我们可以很容易的发现dp的转移是类似矩阵转移的形式。
所以就去打了一个\(O(mod^3\log m)\)的矩阵快速幂。然后因为常数太大被卡常卡死了。
题解中说这样可以得80分,然而一些人尝试去卡常只得了50分,而我卡了半天的才卡到了30分。
这个数据把mod设得太卡矩阵乘法导致了暴力反而能得更多的分,我觉得这很不合理QAQ。
我们很容易看出来这里需要一个\(O(n^2)\)的矩阵乘法,但我们现在只知道一个循环矩阵能做到。
所以正解肯定是一个循环矩阵。
但是我们发现因为原题中做的是乘法,所以转移是乱跳的,这不好。而出题人给了我们一些提示.

孙金宁教你学数学
质数P的原根g满足1<=rt<P,且rt的1次方,2次方…(P-1)次方在模P意义下可以取遍1到(P-1)的所有整数.
欧拉定理:对于质数P,1<=x<P的任意x的P-1次方在模P意义下都为1.
显然,原根的1次方,2次方…(P-2)次方在模P意义下都不为1,只有(P-1)次方在模P意义下为1.这也是一个数成为原根的充分必要条件.

因为原根的几次方可以取遍模p剩余系的所有数,反过来说就是每一个数都可以用原根的几次方的形式表示。那就很棒了,因为这样就可以将乘法变成加法,而加法是明显会形成一个循环矩阵的,所以这题就A了。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=1100;
const int MOD=1000000007;
bool vis[N];
int b[N];
int c[N];
int n,m,mod;
int root;
int ans[N],base[N];
int res[N];
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline bool check(long long x){
    long long res=x;
    for(int i=1;i<mod-1;++i){
        if(res==1)return false;
        res=res*x%mod;
    }   
    return true;
}
inline int pow(long long x,int y){
    long long ans=1;
    while(y){
        if(y&1)ans=ans*x%MOD;
        x=x*x%MOD;
        y>>=1;
    }
    return ans;
}
inline void mul(int *a,int *b){
    for(int i=0;i<mod-1;++i)
        res[i]=0;
    for(int i=0;i<mod-1;++i)
        for(int j=0;j<mod-1;++j)
            res[(i+j)%(mod-1)]=(res[(i+j)%(mod-1)]+1ll*a[i]*b[j]%MOD)%MOD;
    for(int i=0;i<mod-1;++i)
        a[i]=res[i];
}
int QAQ(){
    n=read(),m=read(),mod=read();
    for(int i=1;i<mod;++i)
        if(check(i)){
            root=i;
            break;
        }
    for(int i=1,x=root;i<mod-1;++i,x=x*root%mod)
        b[x]=i,c[i]=x;
    c[0]=1,b[1]=0;
    for(int i=1;i<=n;++i)
        ++base[b[read()]];
    ans[0]=1;
    int y=m;
    while(y){
        if(y&1)mul(ans,base);
        mul(base,base);
        y>>=1;  
    }
    long long fans=0;
    for(int i=0;i<mod;++i)
        fans=(fans+(long long)ans[i]*c[i]%MOD)%MOD;
    printf("%lld\n",fans*pow(pow(n,m),MOD-2)%MOD);
    return false;
}
}
int main(){
    return orz::QAQ();
}

后面两道题懒得转格式了

单车联通大街小巷.这就是出题人没有写题目背景的原因.
对于一棵树,认为每条边长度为1,每个点有一个权值a[i].dis(u,v)为点u到v的最短路径的边数.dis(u,u)=0.对每个点求出一个重要程度.点x的重要程度b[x]定义为其他点到这个点的距离乘上对应的点权再求和. 即:b[x]=a[1]dis(1,x)+a[2]dis(2,x)+....+a[n]*dis(n,x)
现在有很多树和对应的a数组,并求出了b数组.不幸的是,记录变得模糊不清了.幸运的是,树的形态完好地保存了下来,a数组和b数组至少有一个是完好无损的,但另一个数组完全看不清了.
希望你求出受损的数组.多组数据.

从a求b:
换根DP,秒了。
从b求a:
我们取1号节点为根。
sum表示所有节点a值之和,size表示子树a值之和。
我们考虑从a求b的过程,因为我们原来是换根求的b数组,所以相邻两个节点的b值相减后为\(sum-2size_y\)
如果我们能知道sum的话所有值就能求出来了,但是要怎么求sum呢,考场上没想出来QAQ。
总是想着把它们加起来可以搞出sum,结果不行。
我们发现一号节点没有这个式子,所以我们把它的式子暴力写出来
\(b_1=\sum\limits_{i=1}^{n}a_idep_i\)
我们惊奇的发现这个式子等于
\(\sum\limits_{i=2}^nsize_i\)
减一下就出来了。
考场上就差最后两行了,Orz。

Code

#include<cstdio>
#include<algorithm>
#define tkj 0
using namespace std;
namespace orz{
const int N=300000;
long long a[N],b[N],size[N];
int head[N],next[N],ver[N],tot;
long long qwq[N];
long long sum=0;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void add(int x,int y){
    next[++tot]=head[x],head[x]=tot,ver[tot]=y;
    next[++tot]=head[y],head[y]=tot,ver[tot]=x;
}
inline void dky(int x,int father){
    size[x]=a[x];b[x]=0;
    for(int i=head[x];i;i=next[i]){
        if(ver[i]==father)continue;
        dky(ver[i],x);
        size[x]+=size[ver[i]];
        b[x]+=b[ver[i]]+size[ver[i]];
    }
}
inline void ykd(int x,int father){
    for(int i=head[x];i;i=next[i]){
        if(ver[i]==father)continue;
        qwq[ver[i]]=b[ver[i]]-b[x];
        ykd(ver[i],x);
    }
}
inline void sfd(int x,int father){
    a[x]=size[x]=(sum-qwq[x])/2;
    for(int i=head[x];i;i=next[i]){
        if(ver[i]==father)continue;
        sfd(ver[i],x);
        a[x]-=size[ver[i]];
    }
}
inline void dfs(int x,int father){
    for(int i=head[x];i;i=next[i]){
        if(ver[i]==father)continue;
        b[ver[i]]=b[x]+sum-2*size[ver[i]];
        dfs(ver[i],x);
    }
}
int QAQ(){
//  freopen("qaq.in","r",stdin);
    int t;
    t=read();
    while(t--){
        int n=read();
        for(int i=1;i<=n;++i)
            head[i]=0;
        tot=0;
        for(int i=1;i<n;++i)
            add(read(),read());
        switch(read()){
            case 0:
                sum=0;
                for(int i=1;i<=n;++i)
                    sum+=(a[i]=read());
                dky(1,tkj);
                dfs(1,tkj);
                for(int i=1;i<=n;++i)
                    printf("%lld ",b[i]);
                break;
            case 1:
                sum=0;
                for(int i=1;i<=n;++i)
                    b[i]=read();
                ykd(1,tkj);
                for(int i=2;i<=n;++i)
                    sum+=qwq[i];
                sum=(sum+2*b[1])/(long long)(n-1);
                sfd(1,tkj);
                a[1]=sum;
                for(int i=2;i<=n;++i)
                    a[1]-=a[i];
                for(int i=1;i<=n;++i)
                    printf("%lld ",a[i]);
                break;
        }
        putchar('\n');
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

出个题就好了.这就是出题人没有写题目背景的原因.
你在平面直角坐标系上.
你一开始位于(0,0).
每次可以在上/下/左/右四个方向中选一个走一步.
即:从(x,y)走到(x,y+1),(x,y-1),(x-1,y),(x+1,y)四个位置中的其中一个.
允许你走的步数已经确定为n.现在你想走n步之后回到(0,0).但这太简单了.你希望知道有多少种不同的方案能够使你在n步之后回到(0,0).当且仅当两种方案至少有一步走的方向不同,这两种方案被认为是不同的.
答案可能很大所以只需要输出答案对10^9+7取模后的结果.(10^9+7=1000000007,1和7之间有8个0)
这还是太简单了,所以你给能够到达的格点加上了一些限制.一共有三种限制,加上没有限制的情况,一共有四种情况,用0,1,2,3标号:
0.没有任何限制,可以到达坐标系上所有的点,即能到达的点集为{(x,y)|x,y为整数}
1.只允许到达x轴非负半轴上的点.即能到达的点集为{(x,y)|x为非负数,y=0}
2.只允许到达坐标轴上的点.即能到达的点集为{(x,y)|x=0或y=0}
3.只允许到达x轴非负半轴上的点,y轴非负半轴上的点以及第1象限的点.即能到达的点集为{(x,y)|x>=0,y>=0}

type0,1,3 组合数,秒了
type2的数据较小,明显是一个DP,可是我不会。
有10分数据是<=100的,复杂度大一些也能过,暴力建图后跑矩阵快速幂。复杂度\(O((2n)^3log n)\),成功水到分。
正解是DP,我们把问题转化,变成了从(0,0)出发沿一个方向瞎走,回到(0,0),换(或不换)个方向再次瞎走。于是就变成了一个类似背包的东西,原行走序列被分成了一段一段的,考场上想到这里就没往下想,因为如果在半路走回零会导致统计重复,所以我们的一个单位应该是从零出去再回来中间不经过(0,0),这是(i-2)/2的卡特兰数,DP就完了。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=210000;
const int MOD=1000000007;
long long fac[N];
long long inv[N];
long long f[N];
int tot;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline int pow(long long x,int y){
    long long ans=1;
    while(y){
        if(y&1)ans=ans*x%MOD;
        x=x*x%MOD;
        y>>=1;
    }
    return ans;
}
inline long long C(int n,int m){
    return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
inline long long Catalan(int n){
    return (C(2*n,n)-C(2*n,n-1)+MOD)%MOD;
}
int QAQ(){
    int n=read();
    fac[0]=1;
    for(int i=1;i<=n;++i)
        fac[i]=fac[i-1]*i%MOD;
    inv[n]=pow(fac[n],MOD-2);
    for(int i=n-1;i>=0;--i)
        inv[i]=inv[i+1]*(i+1)%MOD;
    long long ans;
    switch(read()){
        case 0:
            ans=0;
            for(int i=0;i<=n;i+=2){
                ans=(ans+C(n,i)*C(i,i/2)%MOD*C(n-i,(n-i)/2)%MOD)%MOD;
            }
            printf("%lld\n",ans);
            break;
        case 1:
            printf("%lld\n",Catalan(n/2));
            break;
        case 2:
            f[0]=1;
            for(int i=0;i<=n;i+=2)
                for(int j=0;j<i;j+=2)
                    f[i]=(f[i]+f[j]*4*Catalan((i-j)/2-1))%MOD;
            printf("%lld\n",f[n]);
            break;
        case 3:
            ans=0;
            for(int i=0;i<=n;i+=2)
                ans=(ans+C(n,i)*Catalan(i/2)%MOD*Catalan((n-i)/2)%MOD)%MOD;
            printf("%lld\n",ans);
            break;
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

循环矩阵学习笔记

循环矩阵是一个方阵,大概形式是这样的:
12345
51234
45123
34512
23451
它有一些很好的性质,比如循环矩阵加循环矩阵还是循环矩阵,循环矩阵乘循环矩阵还是一个循环矩阵。
所以我们的矩阵乘法就可以由\(O(n^3)\)优化到\(O(n^2)\)我们只需要把矩阵\(n^2\)的乘出来一行,之后就都是循环的了,最暴力的方法就是\(O(n^2)\)赋值,这样的复杂度是对的,可是它不够优美。我们可以用一个\(n^2\)的循环来代替这个过程,我们只需要计算好每一个位置对哪里有贡献就可以了。
如果矩阵是从0开始的,贡献的位置是\((i+j)\mod n\)
如果矩阵是从1开始的,贡献的位置是\((i+j-2)\mod n+1\)

[P5056][模板]插头DP

插头DP模板题,大力分类讨论就完了。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int MOD=233333,N=1000000;
int map[14][14];
int n,m;
int ex,ey;
long long ans;
struct HashMap{
    int head[MOD],val[N],next[N],tot;
    long long cnt[N];
    inline void clear(){
        memset(head,0,sizeof(head));
        tot=0;
    }
    inline long long& operator[](const int &x){
        int pos=x%MOD;
        for(int i=head[pos];i;i=next[i])
            if(val[i]==x)
                return cnt[i];
        next[++tot]=head[pos],head[pos]=tot,val[tot]=x,cnt[tot]=0;
        return cnt[tot];
    }
}f[2];
inline int find(int &s,int id){
    return s>>((id-1)<<1)&3;
}
inline void set(int &s,int id,int val){
    id=(id-1)<<1;
    s&=~(3<<id);
    s|=val<<id;
}
inline int link(int &s,int id){
    int delta=(find(s,id)==1?1:-1);
    int t,cnt=0;
    for(int pos=id;pos<=m+1;pos+=delta){
        t=find(s,pos);
        if(t==1)++cnt;
        else if(t==2)--cnt;
        if(!cnt)return pos;
    }
    return -1;
}
inline void solve(int x,int y){
    HashMap &now=f[((x-1)*m+y)&1];
    HashMap &last=f[(((x-1)*m+y)&1)^1];
    now.clear();
    int tot=last.tot;
    int t1,t2;
    long long val;
    int state;
    for(int i=1;i<=tot;++i){
        state=last.val[i];
        val=last.cnt[i];
        t1=find(state,y);
        t2=find(state,y+1);
        if(!t1&&!t2){
            if(map[x][y]&&map[x+1][y]&&map[x][y+1]){
                set(state,y,1);
                set(state,y+1,2);
                now[state]+=val;
            }
            else if(!map[x][y]){
                now[state]+=val;
            }
        }
        else if(!t1&&t2){
            if(map[x][y+1])now[state]+=val;
            if(map[x+1][y]){
                set(state,y,t2);
                set(state,y+1,0);
                now[state]+=val;
            }   
        }
        else if(t1&&!t2){
            if(map[x+1][y])now[state]+=val;
            if(map[x][y+1]){
                set(state,y+1,t1);
                set(state,y,0);
                now[state]+=val;    
            }
        }
        else if(t1==1&&t2==1){
            set(state,link(state,y+1),1);
            set(state,y,0);
            set(state,y+1,0);
            now[state]+=val;
        }
        else if(t1==1&&t2==2){
            if(x==ex&&y==ey)
                ans+=val;
        }
        else if(t1==2&&t2==1){
            set(state,y,0);
            set(state,y+1,0);
            now[state]+=val;
        }
        else if(t1==2&&t2==2){
            set(state,link(state,y),2);
            set(state,y,0);
            set(state,y+1,0);
            now[state]+=val;
        }
    }
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline int getMap(){
    char t;
    do{t=getchar();}while(t!='*'&&t!='.');
    return t=='.';
}
int QAQ(){
//  freopen("qaq.in","r",stdin);
    n=read(),m=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            map[i][j]=getMap();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(map[i][j])
                ex=i,ey=j;
    f[0][0]=1;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j)
            solve(i,j);
        if(i!=n){
            HashMap &now=f[(i*m)&1];
            for(int i=1;i<=now.tot;++i)
                now.val[i]<<=2;
        }
    }
    printf("%lld\n",ans);
    return false;
}
}
int main(){
    return orz::QAQ();
}

BigInt类

Code

class BigInt{
#define LEN 6
#define base 100000000
    private:
        int len,s[LEN];
    public:
        BigInt(){
            len=0;
            memset(s,0,sizeof(s));
        }
        BigInt(int x){
            len=0;
            memset(s,0,sizeof(s));
            while(x){
                s[++len]=x%base;
                x/=base;
            }
        }
        BigInt operator+(const BigInt &b)const{
            BigInt res;
            res.len=max(this->len,b.len);
            for(int i=1;i<=res.len;++i)
                res.s[i]+=this->s[i]+b.s[i],res.s[i+1]+=res.s[i]/base,res.s[i]%=base;
            if(res.s[res.len+1])++res.len;
            return res;
        }
        void operator+=(const BigInt &b){
            *this=*this+b;
        }
        inline void print(){
            printf("%d",s[len]);
            for(int i=len-1;i>=0;--i)
                printf("%08d",s[i]);
        }
};

[SDOI2011]地板

这一天,OIer们终于想起了,被插头DP支配的恐惧

毒瘤插头DP题又写了170行。
不过好像是因为我没有去压行。
如果把转移封装起来的话应该能短不少。

在这一题中我们设2中插头,转过向的和没转过向的,然后就可以大力分类讨论了。
最后输出答案可以输出最后一次的状态0。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=2000000;
const int MOD=499211;
const int P=20110520;
struct HashMap{
    int head[MOD],next[N],val[N],cnt[N],tot;
    HashMap(){
        memset(head,0,sizeof(head));
        tot=0;
    }
    inline void clear(){
        memset(head,0,sizeof(head));
        tot=0;
    }
    inline int& operator[](const int &x){
        int pos=x%MOD;
        for(int i=head[pos];i;i=next[i])
            if(val[i]==x)
                return cnt[i];
        next[++tot]=head[pos],head[pos]=tot,val[tot]=x,cnt[tot]=0;
        return cnt[tot];
    }
}f[2];
inline int find(int &s,int id){
    return (s>>((id-1)<<1))&3;
}
inline void set(int &s,int id,int val){
    id=(id-1)<<1;
    s&=~(3<<id);
    s|=(val<<id);
}
bool qwq[200][200];
bool map[200][200];
int n,m;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline bool getMap(){
    char t;
    do{t=getchar();}while(t!='*'&&t!='_');
    return t=='_';
}
inline void show(int s){
    for(int i=1;i<=m+1;++i)
        printf("%d",find(s,i));
}
inline void solve(int x,int y){
    HashMap &now=f[((x-1)*m+y)&1];
    HashMap &last=f[(((x-1)*m+y)&1)^1];
    now.clear();
    int tot=last.tot;
    int t1,t2,s,cnt;
    for(int i=1;i<=tot;++i){
        s=last.val[i];
        cnt=last.cnt[i];
        t1=find(s,y);
        t2=find(s,y+1);
        if(!t1&&!t2){
            if(!map[x][y]){
                set(s,y,0);
                set(s,y+1,0);
                now[s]=(now[s]+cnt)%P;
                continue;
            }
            if(map[x][y]&&map[x+1][y]&&map[x][y+1]){
                set(s,y,2);
                set(s,y+1,2);
                now[s]=(now[s]+cnt)%P;
            }
            if(map[x][y]&&map[x+1][y]){
                set(s,y,1);
                set(s,y+1,0);
                now[s]=(now[s]+cnt)%P;
            }
            if(map[x][y]&&map[x][y+1]){
                set(s,y,0);
                set(s,y+1,1);
                now[s]=(now[s]+cnt)%P;
            }
        }
        else if(!t1&&t2==1){
            if(map[x][y+1]){
                set(s,y,0);
                set(s,y+1,2);
                now[s]=(now[s]+cnt)%P;
            }
            if(map[x+1][y]){
                set(s,y,1);
                set(s,y+1,0);
                now[s]=(now[s]+cnt)%P;
            }
        }
        else if(!t1&&t2==2){
            if(map[x+1][y]){
                set(s,y,2);
                set(s,y+1,0);
                now[s]=(now[s]+cnt)%P;
            }
            set(s,y,0);
            set(s,y+1,0);
            now[s]=(now[s]+cnt)%P;
        }
        else if(t1==1&&!t2){
            if(map[x][y+1]){
                set(s,y,0);
                set(s,y+1,1);
                now[s]=(now[s]+cnt)%P;
            }
            if(map[x+1][y]){
                set(s,y,2);
                set(s,y+1,0);
                now[s]=(now[s]+cnt)%P;
            }
        }
        else if(t1==1&&t2==1){
            set(s,y,0);
            set(s,y+1,0);
            now[s]=(now[s]+cnt)%P;
        }
        else if(t1==2&&!t2){
            if(map[x][y+1]){
                set(s,y,0);
                set(s,y+1,2);
                now[s]=(now[s]+cnt)%P;
            }
            set(s,y,0);
            set(s,y+1,0);
            now[s]=(now[s]+cnt)%P;
        }
    }
}
int QAQ(){
    n=read(),m=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            qwq[i][j]=getMap();
    if(n<m){
        for(int i=1;i<=n;++i)
            for(int j=1;j<=m;++j)
                map[j][i]=qwq[i][j];
        swap(n,m);
    }
    else {
        for(int i=1;i<=n;++i)
            for(int j=1;j<=m;++j)
                map[i][j]=qwq[i][j];
    }
    f[0][0]=1;
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j)
            solve(i,j);
        if(i!=n){
            HashMap &last=f[(i*m)&1];
            for(int i=1;i<=last.tot;++i)
                last.val[i]<<=2;
        }
    }
    HashMap &ans=f[(n*m)&1];
    printf("%d\n",ans[0]);
    return false;
}
}
int main(){
    return orz::QAQ();
}

[COGS775]山海经

听说是线段树恶心题,结果一遍过了。
就是区间最长子段和,但是要求询问具体方案。而且不设SpecialJudge,要求输出字典序最小的解。
区间最长子段和很容易维护,细节全在答案的维护。
因为要求字典序最小,所以我们在答案相等是时候也尽量要取靠左的,所以很容易就A了。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
#define lc x<<1
#define rc x<<1|1
const int N=1100000;
int a[N];
int sum[N];
struct node{
    int l,r;
    int ans,lans,rans;
    int lp,rp,llp,rrp;
}t[(N<<2)+233];
inline int calc(const int &l,const int &r){
    return sum[r]-sum[l-1]; 
}
inline node merge(node a,node b){
    node res;
    //先维护无关紧要的信息
    res.l=a.l;
    res.r=b.r;
    //=================
    //从左往右更新值
    //更新左答案,左答案位置
    //左答案的右端点要尽量小
    res.lans=a.lans;
    res.llp=a.llp;
    if(calc(a.l,a.r)+b.lans>res.lans){
        res.llp=b.llp;
        res.lans=calc(a.l,a.r)+b.lans;
    }
    //更新右答案,右答案位置
    res.rans=b.rans;
    res.rrp=b.rrp;
    if(calc(b.l,b.r)+a.rans>=res.rans){
        res.rrp=a.rrp;
        res.rans=calc(b.l,b.r)+a.rans;
    }
    //更新答案,更新答案位置
    if(a.ans>=b.ans){
        res.ans=a.ans;
        res.lp=a.lp;
        res.rp=a.rp;
    }
    else{
        res.ans=b.ans;
        res.lp=b.lp;
        res.rp=b.rp;
    }
    if(a.rans+b.lans>res.ans){
        res.ans=a.rans+b.lans;
        res.lp=a.rrp;
        res.rp=b.llp;
    }
    else if(a.rans+b.lans==res.ans){
        if(a.rrp<res.lp){
            res.ans=a.rans+b.lans;
            res.lp=a.rrp;
            res.rp=b.llp;
        }
    }
    return res;
}
void build(int x,int l,int r){
    if(l==r){
        t[x].ans=t[x].lans=t[x].rans=a[l];
        t[x].l=t[x].r=t[x].lp=t[x].rp=t[x].llp=t[x].rrp=l;
        return ;
    }
    int mid=(l+r)>>1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    t[x]=merge(t[lc],t[rc]);
}
inline node query(int x,int l,int r){
    if(t[x].l==l&&t[x].r==r)return t[x];
    int mid=(t[x].l+t[x].r)>>1;
    if(r<=mid)return query(lc,l,r);
    else if(l>mid)return query(rc,l,r);
    else return merge(query(lc,l,mid),query(rc,mid+1,r));
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int QAQ(){
    int n,m;
    int l,r;
    node res;
    n=read(),m=read();
    for(int i=1;i<=n;++i)
        a[i]=read();
    for(int i=1;i<=n;++i)
        sum[i]=sum[i-1]+a[i];
    build(1,1,n);
    while(m--){
        l=read(),r=read();
        res=query(1,l,r);
        printf("%d %d %d\n",res.lp,res.rp,res.ans);
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

[LOJ10222]佳佳的Fibonacci

矩阵乘法模板题
我们把nfn的式子拆开就可以求出矩阵
乘就完事了

t(n) n+1fn+1 nfn fn+1 fn
t(n-1) 1
nfn 1 1 1
(n-1)fn-1 1
fn 1 1 1
fn-1 2 1

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=6;
int MOD;
struct Matrix{
    int a[N][N];
    Matrix(){
        memset(a,0,sizeof(a));
    }
    Matrix operator*(const Matrix &b)const{
        Matrix res;
        for(int i=1;i<=5;++i)
            for(int j=1;j<=5;++j)
                for(int k=1;k<=5;++k)
                    res.a[i][j]=(res.a[i][j]+1ll*a[i][k]*b.a[k][j]%MOD)%MOD;
        return res;
    }
}ans,base;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int QAQ(){
    int n=read();
    MOD=read();
    Matrix ans,base;
    base.a[1][1]=1;
    base.a[2][1]=1;
    base.a[2][2]=1;
    base.a[3][2]=1;
    base.a[4][2]=1;
    base.a[5][2]=2;
    base.a[2][3]=1;
    base.a[4][4]=1;
    base.a[5][4]=1;
    base.a[4][5]=1;
    ans.a[1][1]=1;
    ans.a[1][2]=2;
    ans.a[1][3]=1;
    ans.a[1][4]=1;
    ans.a[1][5]=1;
    int y=n-1;
    while(y){
        if(y&1)ans=ans*base;
        y>>=1;
        base=base*base;
    }
    printf("%d\n",ans.a[1][1]);
    return false;
}
}
int main(){
    return orz::QAQ();
}

7.29爆零赛

爆零了QAQ
上来一看,T1扫描线,T2启发式合并平衡树,T3期望不会。
自闭了,打了暴力分。

这三道题目名字也挺不正经的说。

辣鸡

这题其实你如果要扫描线拧干的话估计也能干出来,可是我并不会扫描线。
这题\(n^2\)就完事了,循环的时候加一个小优化,先把它排了序,如果以后再也不能对答案贡献了就跳出。
然后就可以见证\(n^2\)\(100000\)了。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=200000;
struct point{
    long long x1,y1,x2,y2;
    bool operator<(const point &b)const{
        return this->x1^b.x1?this->x1<b.x1:this->y1<b.y1;
    }
}a[N];
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline long long calc(long long l1,long long r1,long long l2,long long r2){
    long long res=0,tl=max(l1-1,l2),tr=min(r1-1,r2);
    res+=max(0ll,tr-tl+1);
    tl=max(l1+1,l2),tr=min(r1+1,r2);
    res+=max(0ll,tr-tl+1);
    return res;
}
int QAQ(){
    int n=read();
    long long ans=0;
    for(register int i=1;i<=n;++i)
        a[i].x1=read()+1,a[i].y1=read()+1,a[i].x2=read()+1,a[i].y2=read()+1;
    for(register int i=1;i<=n;++i)
        ans+=(a[i].x2-a[i].x1)*(a[i].y2-a[i].y1)*2ll;
    sort(a+1,a+n+1);
    for(register int i=1;i<=n;++i){
        for(register int j=i+1;j<=n;++j){
            if(a[j].x1>a[i].x2+1)break;
            if(a[j].x1==a[i].x2+1){ans+=calc(a[i].y1,a[i].y2,a[j].y1,a[j].y2);}
            else if(a[j].y1==a[i].y2+1){ans+=calc(a[i].x1,a[i].x2,a[j].x1,a[j].x2);}
            else if(a[j].x2==a[i].x1-1){ans+=calc(a[i].y1,a[i].y2,a[j].y1,a[j].y2);}
            else if(a[j].y2==a[i].y1-1){ans+=calc(a[i].x1,a[i].x2,a[j].x1,a[j].x2);}
        }
    }
    printf("%lld\n",ans);
    return false;
}
}
int main(){
    return orz::QAQ();
}

模板

题解是启发式合并动态开点线段树,因为我比较懒,所以还是去写启发式合并Splay了。
平衡树启发式合并跑的还挺快的。
过了样例之后出了两个问题,一个是没判断k=0的情况T了,第二个是边数组没开2倍T了。
Orz。

Code

#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
namespace orz{
#define IT set<WSL>::iterator
const int N=110000;
struct SplayNode{
    int son[2],father,size,cnt,val,time,color;
}t[N*18];
int tot=0;
int res;
int head[N],ver[N*2],next[N*2],cwy;
inline void add(int x,int y){
    next[++cwy]=head[x],head[x]=cwy,ver[cwy]=y;
    next[++cwy]=head[y],head[y]=cwy,ver[cwy]=x;
}
struct WSL{
    int color;
    mutable int time;
    WSL(int c,int t){
        color=c;
        time=t;
    }
    bool operator<(const WSL&b)const{
        return this->color<b.color;
    }
};
struct Splay{
    int root;
    inline void update(int x){
        t[x].size=t[t[x].son[0]].size+t[t[x].son[1]].size+1;
        t[x].cnt=t[t[x].son[0]].cnt+t[t[x].son[1]].cnt+t[x].val;
    }
    inline bool get(int x){
        return t[t[x].father].son[1]==x;
    }
    inline void rotate(int x){
        int y=t[x].father,z=t[y].father,k=get(x);
        t[z].son[get(y)]=x;
        t[x].father=z;
        t[y].son[k]=t[x].son[k^1];
        t[t[y].son[k]].father=y;
        t[x].son[k^1]=y;
        t[y].father=x;
        update(y);
    }
    inline void splay(int x){
        int y,z;
        while(t[x].father){
            y=t[x].father;z=t[y].father;
            if(z)
                rotate(get(x)^get(y)?x:y);
            rotate(x);
        }
        update(x);
        root=x;
    }
    inline int findAns(int k){
        int x=root;
        int ans=0;
        if(k>=t[x].size){
            return t[x].cnt;
        }
        if(k==0){
            return 0;
        }
        while(true){
            if(k<=t[t[x].son[0]].size)x=t[x].son[0];
            else if(k<=t[t[x].son[0]].size+1)return ans+t[t[x].son[0]].cnt+t[x].val;
            else ans+=t[t[x].son[0]].cnt+t[x].val,k-=t[t[x].son[0]].size+1,x=t[x].son[1];
        }
    }
    inline void insert(int time,int color,int val){
        int father=0,x=root;
        while(x&&t[x].time!=time){
            father=x;
            x=t[x].son[time>t[x].time];
        }
        x=++tot;
        if(father)t[father].son[time>t[father].time]=x;
        t[x].time=time;t[x].size=1;t[x].cnt=t[x].val=val;
        t[x].father=father;t[x].color=color;
        splay(x);
    }
    inline void change(int time){
        int x=root;
        while(x&&time!=t[x].time){
            x=t[x].son[time>t[x].time];
        }
        t[x].val=0;
        splay(x);
    }
    inline int size(){
        return t[root].size;
    }
};
struct QWQ{
    set<WSL>dky;
    Splay s;
    inline int size(){
        return s.size();
    }
    inline void insert(int time,int color){
        IT it=dky.find(WSL(color,0));
        if(it==dky.end()){
            dky.insert(WSL(color,time));
            s.insert(time,color,1);
        }
        else if(time>it->time){
            s.insert(time,color,0);
        }
        else{
            s.change(it->time);
            it->time=time;
            s.insert(time,color,1);
        }
    }
}a[N];
int p[N],tong[N],ans[N];
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void merge(int x){
    if(!x)return;
    merge(t[x].son[0]);
    a[res].insert(t[x].time,t[x].color);
    merge(t[x].son[1]);
}
inline void dfs(int x,int father){
    for(int i=head[x];i;i=next[i]){
        if(ver[i]==father)continue;
        dfs(ver[i],x);
        if(a[p[x]].size()<a[p[ver[i]]].size()){
            res=p[ver[i]];
            merge(a[p[x]].s.root);
            p[x]=p[ver[i]];
        }
        else{
            res=p[x];
            merge(a[p[ver[i]]].s.root);
        }
    }
    ans[x]=a[p[x]].s.findAns(tong[x]);
}
int QAQ(){
    int n,m,q,x,c;
    n=read();
    for(int i=1;i<n;++i)
        add(read(),read());
    for(int i=1;i<=n;++i)
        tong[i]=read(),p[i]=i;
    m=read();
    for(int i=1;i<=m;++i)
        x=read(),c=read(),a[p[x]].insert(i,c);  
    dfs(1,0);
    q=read();
    for(int i=1;i<=q;++i)
        printf("%d\n",ans[read()]);
    return false;
}
}
int main(){
    return orz::QAQ();
}

大佬

题解有看不懂的DP做法,实际上有很优秀的快速幂做法,时间复杂度是\(m\log k\)的,比题解的\(n^2m\)不知道高到哪里去了。
开始的时候我设的dp状态一维是天数,因为前面对后面有影响导致不可转移。实际上我们的所有情况包括了m取值的所有情况,而所有长度为k的区间的所有情况也都存在,所以我们只需要考虑长度为k的区间,乘上\(n-k+1\)就可以了。
而对于长度为k的区间,最大值为i的方案数为\(i^k-(i-1)^k\)

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const long long MOD=1000000007;
inline long long read(){
    long long a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline long long pow(long long x,long long y){
    long long ans=1;
    while(y){
        if(y&1)ans=ans*x%MOD;
        y>>=1;
        x=x*x%MOD;
    }
    return ans%MOD;
}
int QAQ(){
    long long n=read(),m=read(),k=read();
    long long ans=0;
    if(n<k){printf("0\n");return false;}
    for(int i=1;i<=m;++i)
        ans=(ans+read()*((pow(i,k)-pow(i-1,k))%MOD+MOD)%MOD*(n-k+1)%MOD)%MOD;
    printf("%lld\n",ans*pow(pow(m,k),MOD-2)%MOD);
    return false;
}
}
int main(){
    return orz::QAQ();
}

[SCOI2014]方伯伯的玉米田

这是个DP
看起来好像是一个LIS,我们考虑如何处理区间同时加一。
因为我们求的是最长不下降子序列,所以一次区间加一定是从一个点开始加到最后,否则会导致后面的变小。
所以我们设\(f_{i,j}\)表示考虑了前i个玉米,进行了j次拔高的最大长度,之后就是一个LIS了。
注意第二层循环要反着来,否则会自己加自己。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=20000;
const int mx=5550;
const int mxk=550;
int a[N];
int c[5600][600];
int n,k;
inline int ask(int x,int y){
    int ans=0;
    for(int i=x;i;i-=i&-i)
        for(int j=y;j;j-=j&-j)
            ans=max(ans,c[i][j]);
    return ans;
}
inline void add(int x,int y,int val){
    for(int i=x;i<=mx;i+=i&-i)
        for(int j=y;j<=mxk;j+=j&-j)
            c[i][j]=max(c[i][j],val);
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int QAQ(){
    n=read(),k=read();
    for(int i=1;i<=n;++i)
        a[i]=read();
    int ans=0;
    int fans=0;
    for(int i=1;i<=n;++i)
        for(int j=k+1;j;--j){
            ans=ask(a[i]+j,j)+1;
            add(a[i]+j,j,ans);
            fans=max(fans,ans);
        }
    printf("%d\n",fans);
    return false;
}
}
int main(){
    return orz::QAQ();
}

[SDOI2011]拦截导弹

第一问很简单,裸的CDQ优化DP。
第二问就很难计算了。
它大概是一个假的概率,等于经过某个导弹的方案数除以总方案数。
我们考虑如何判断一个点是否在x到y的最短路上,我们可以从x跑一遍最短路,从y跑一遍最短路,如果一个点两个dis的值加起来等于x到y的最短路长度加一,那么这个点就是好的。这个显然。
我们用相同的思路处理这一道题。
我们先正着跑一遍最长不上升子序列,dp数组为\(f_i\),方案数为\(fc_i\),再倒着跑一遍最长不下降子序列,dp数组为\(g_i\),方案数为\(gc_i\)如果\(f_i+g_i=ans\)那么经过它的方案数为\(fc_i*gc_i\)

之后我们发现我们还是不会求,CDQ怎么统计方案数?
我们发现我们的树状数组只能求出最优方案,而不能求出方案数。
这时候就需要我们去魔改一下我们的树状数组了。
我们再记一个cnt数组,我们在更新最大值的时候把它更新,如果最大值和插入值相等就累加。
然后我们发现跑出来的全是0,因为我们的计数没有初值,把所有数初值赋为0吗?那你必WA,不用想也知道那样一定会出错。
我们发现只有一个数作为第一个数出现时才会从0转移,所以当右区间的一个数从0转移时我们将它跳过,最后跑到它本身如果还是0那说明它是从0转移的,我们为它赋上初值。
这题方案数还挺多的,需要开double,比如10 10 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1就有\(2^{10}\)种总方案。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=200000;
struct node{
    int t,h,v;
    bool operator<(const node&b)const{
        return this->h<b.h;
    }
}a[N],t[N];
int hc[N],vc[N],htot,vtot,c[N];
double cnt[N];
int n,f[N],g[N];
double fc[N],gc[N];
inline void add(int x,int ans,double tot){
    for(;x<=n+2;x+=x&-x){
        if(c[x]<ans)c[x]=ans,cnt[x]=tot;
        else if(ans==c[x])cnt[x]+=tot;
    }
}
inline int ask(int x,double &res){
    res=0;
    int ans=0;
    for(;x;x-=x&-x){
        if(ans<c[x])ans=c[x],res=cnt[x];
        else if(ans==c[x])res+=cnt[x];
    }
    return ans;
}
inline void remove(int x){
    for(;x<=n;x+=x&-x)
        c[x]=cnt[x]=0;
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
void cdq(int l,int r){
    if(l==r){
        if(!f[a[l].t]){
            f[a[l].t]=1;
            fc[a[l].t]=1;
        }
        return;
    }
    int mid=(l+r)>>1;
    cdq(l,mid);
    int tl=l,tr=mid+1,cwy;
    double res;
    for(int i=l;i<=r;++i)t[i]=a[i];
    sort(t+l,t+mid+1);
    sort(t+mid+1,t+r+1);
    for(int i=l;i<=r;++i){
        if((tl<=mid&&t[tl].h<=t[tr].h)||tr>r){
            add(t[tl].v,f[t[tl].t],fc[t[tl].t]);
            ++tl;
        }
        else{
            cwy=ask(t[tr].v,res)+1;
            if(cwy==1){++tr;continue;}
            if(f[t[tr].t]<cwy)f[t[tr].t]=cwy,fc[t[tr].t]=res;
            else if(f[t[tr].t]==cwy)fc[t[tr].t]+=res;
            ++tr;
        }
    }
    for(int i=l;i<=mid;++i)
        remove(t[i].v);
    cdq(mid+1,r);
}
int QAQ(){
    n=read();
    for(int i=1;i<=n;++i)
        a[i].h=hc[i]=read(),a[i].v=vc[i]=read(),a[i].t=i;
    sort(hc+1,hc+n+1);
    sort(vc+1,vc+n+1);
    htot=unique(hc+1,hc+n+1)-hc-1;
    vtot=unique(vc+1,vc+n+1)-vc-1;
    for(int i=1;i<=n;++i){
        a[i].h=lower_bound(hc+1,hc+htot+1,a[i].h)-hc;
        a[i].v=lower_bound(vc+1,vc+vtot+1,a[i].v)-vc;
    }
    for(int i=1;i<=n;++i)a[i].h=n+1-a[i].h;
    for(int i=1;i<=n;++i)a[i].v=n+1-a[i].v;
    cdq(1,n);
    for(int i=1;i<=n;++i)a[i].h=n+1-a[i].h;
    for(int i=1;i<=n;++i)a[i].v=n+1-a[i].v;
    for(int i=1;i<=n;++i)g[i]=f[i],gc[i]=fc[i];
    for(int i=1;i<=n;++i)f[i]=fc[i]=0;
    for(int i=1,j=n;i<j;++i,--j)swap(a[i],a[j]);
    cdq(1,n);
    int ans=0;
    double tot=0;
    for(int i=1;i<=n;++i)
        ans=max(ans,g[i]);
    for(int i=1;i<=n;++i)
        if(g[i]==ans)
            tot+=gc[i];
    printf("%d\n",ans);
    for(int i=1;i<=n;++i){
        if(f[i]+g[i]==ans+1)printf("%lf ",(fc[i]*gc[i])/tot);
        else printf("0.0000000 ");
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

后缀数组(SA)学习笔记

由于HZ学长讲的过于清晰,所以蒟蒻并没有听懂。
以下内容是我从网上各位大神的博客学习后总结的,感谢他们的无私付出。
后半部分基本来自2009年罗穗骞的论文,感谢他写出了如此清晰的国家集训队论文,这是少数几个蒟蒻也能看懂的国集论文。

没听懂QAQ
后缀数组是处理字符串的一个好东西。
它是一个数组。
我们先把所有的后缀排个序,然后我们就可以得到后缀数组。
以下所有第i个指的是原字符串i到len的子串。
排名第i的所有后缀排过序后的第i个后缀。
这里的排序指的是按照字典序排序。
a<ab
一些数组定义(以下都是数组):
rank表示第i个后缀的排名
sa表示排名第i的后缀是第几个后缀
还有一个常用的height数组之后再定义吧。

我们可以发现rank数组和sa数组是互逆的,求一个就可以了。
但是怎样优秀的求就是一个问题。
重载运算符?
复杂度为O(n^2logn)

有一些优秀的算法可以解决这个问题。
其中倍增法最为常用。
时间复杂度为\(O(n\log n)\)

倍增算法是基于后缀的一些性质。
第i+1的后缀的第一个字符是第i个后缀的第二个字符。
所以我们在算出第一个字符顺序的同时也得到了第二个字符的排序,我们算出1,2个字符的排序后也得到了第3,4个字符的排序。
所以我们就可以倍增的用双关键字的排序来做了。
直接用是sort是\(O(n\log^2n)\)的。
用基数排序会更优秀。

写了很多注释的代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=1100000;
char s[N];
int n,m;
int x[N],y[N],c[N],sa[N];
inline void SA(){
    //c是一个桶,现在统计了对应元素的个数
    for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    //求了一个前缀和
    for(int i=1;i<=m;++i)c[i]+=c[i-1];
    //求出只用第一个字母的SA值
    //如果所有字符都不相同的话,对应的排名就是前缀和
    //为了保证sa互不相同要每次减一
    //这里正着倒着都对
    //其实好像乱着也对
    for(int i=n;i>=1;--i)sa[c[x[i]]--]=i;
    //开始倍增
    //k的意义为目前已经处理出了关键字为前的SA,扩展到2×k的SA
    for(int k=1;k<=n;k<<=1){
        //x表示的是第i个后缀当前的rank,相同rank会重复。
        //p只是一个计数器
        int p=0;
        //y表示以第二关键字排序的SA值
        //先把长度不够的给排到前面
        //这里正着倒着都对,因为之前已经排好了
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        //倍增法求SA的核心操作
        //第二关键字可以从第一关键字得到
        for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
        //清空桶
        for(int i=1;i<=m;++i)c[i]=0;
        //用第一关键字信息更新桶
        for(int i=1;i<=n;++i)++c[x[i]];
        //做前缀和
        for(int i=1;i<=m;++i)c[i]+=c[i-1];
        //与上面的排序差不多,只是按y的顺序来
        for(int i=n;i>=1;--i)sa[c[x[y[i]]]--]=y[i];
        //更新出下一次的x
        //现在的SA数组已经是按2k排序的SA数组了
        //先把原来的y废了
        //现在的y是上一次的x
        swap(x,y);
        //计数器清空
        p=1;
        //设置第一位
        x[sa[1]]=1;
        //如果相同的话拥有相同rank
        for(int i=2;i<=n;++i)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        //如果p=n说明所有后缀都排好了
        if(p==n)break;
        m=p;
    }
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int QAQ(){
    scanf("%s",s+1);
    n=strlen(s+1),m='z';
    SA();
    for(int i=1;i<=n;++i)
        printf("%d ",sa[i]);
    return false;
}
}
int main(){
    return orz::QAQ();
}

没有注释的板子

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=1100000;
char s[N];
int x[N],y[N],c[N],sa[N];
int n,m;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void SA(){
    for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    for(int i=1;i<=m;++i)c[i]+=c[i-1];
    for(int i=n;i;--i)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
        for(int i=1;i<=m;++i)c[i]=0;
        for(int i=1;i<=n;++i)++c[x[i]];
        for(int i=1;i<=m;++i)c[i]+=c[i-1];
        for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
        swap(x,y);
        p=1;
        x[sa[1]]=1;
        for(int i=2;i<=n;++i)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        if(p==n)break;
        m=p;
    }
}
int QAQ(){
    scanf(" %s",s+1);
    n=strlen(s+1);
    m='z';
    SA();
    for(int i=1;i<=n;++i)
        printf("%d ",sa[i]);
    return false;
}
}
int main(){
    return orz::QAQ();
}

我们终于出了SA数组,可是仍然不知道该怎么用。
我们一般还需要height数组。
\(height_i\)表示排名为i的后缀与排名为i-1的后缀的LCP
如果直接求就\(O(n^2)\)了。
那就很不好
\(h_i=height_{rank_i}\)
即h数组表示第i个后缀的height值
我们有一个性质:\(h_i>=h_{i-1}-1\)

证明

我们设k为第i-1个后缀排名前一个后缀
\(lcp(k,i-1)=h_{i-1}\)
把这两个后缀都去掉首字符
变成了k+1和i
而它们的lcp少了1
这个只是方便理解的证明,实际上好像不是佷严谨。

带求height的板子

inline void SA(){
    for(int i=1;i<=n;++i)++c[x[i]=a[i]];
    for(int i=1;i<=m;++i)c[i]+=c[i-1];
    for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
        for(int i=1;i<=m;++i)c[i]=0;
        for(int i=1;i<=n;++i)++c[x[i]];
        for(int i=1;i<=m;++i)c[i]+=c[i-1];
        for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
        swap(x,y);
        p=1;
        x[sa[1]]=1;
        for(int i=2;i<=n;++i)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        if(p==n)break;
        m=p;
    }
    for(int i=1;i<=n;++i)
        rank[sa[i]]=i;
    for(int i=1,p=0;i<=n;++i){
        if(rank[i]==1)continue;
        if(p)--p;
        int j=sa[rank[i]-1];
        while(a[i+p]==a[j+p])++p;
        height[rank[i]]=p;
    }
}

SA一些常用套路的总结

最长可重叠重复子串

重复子串的定义:在字符串中至少出现两次的子串
输出height的最大值就可以了

最长不可重叠重复子串

最长可重叠k重复子串

我们依旧是把问题转化为判定,使用二分来解决.
二分长度。如果存在一段连续的height都大于等于这个长度且这些数的个数大于等于k-1,那么这个长度就是可行的。

USACO06DEC牛奶模式Milk Patterns

Code

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=40000;
int x[N],y[N],c[1100000],sa[N],rank[N],height[N];
int a[N];
int n,k,m;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void SA(){
    for(int i=1;i<=n;++i)++c[x[i]=a[i]];
    for(int i=1;i<=m;++i)c[i]+=c[i-1];
    for(int i=1;i<=n;++i)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
        for(int i=1;i<=m;++i)c[i]=0;
        for(int i=1;i<=n;++i)++c[x[i]];
        for(int i=1;i<=m;++i)c[i]+=c[i-1];
        for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
        swap(x,y);
        p=1;
        x[sa[1]]=1;
        for(int i=2;i<=n;++i)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        if(p==n)break;
        m=p;
    }
    for(int i=1;i<=n;++i)
        rank[sa[i]]=i;
    for(int i=1,p=0;i<=n;++i){
        if(rank[i]==1)continue;
        if(p)--p;
        int j=sa[rank[i]-1];
        while(a[i+p]==a[j+p])++p;
        height[rank[i]]=p;
    }
}
inline bool check(int x){
    int res=0;
    for(int i=2;i<=n;++i){
        if(height[i]>=x)++res;
        else res=0;
        if(res>=k-1)return true;
    }   
    return false;
}
int QAQ(){
    n=read(),k=read();
    for(int i=1;i<=n;++i)
        m=max(m,a[i]=read());
    SA();
    int l=0,r=n,mid;
    while(l<r){
        mid=(l+r+1)>>1;
        if(check(mid))l=mid;
        else r=mid-1;
    }
    printf("%d\n",l);
    return false;
}
}
int main(){
    return orz::QAQ();
}

不同子串个数

一个很显然的转化是将子串变成后缀的前缀。
所以问题也就变成了求不同的后缀的前缀的个数
我们按照排完的顺序依次插入后缀,每插入一个后缀,都会多\(n-sa_i-height_i+1\)个本质不同的子串。把它们加起来就可以得到个数,注意第一个后缀是\(n-sa_1+1\)

LuoguP2408不同子串个数

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=200000;
int x[N],y[N],c[N],sa[N],height[N],rank[N];
char s[N];
int n,m;
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void SA(){
    for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    for(int i=1;i<=m;++i)c[i]+=c[i-1];
    for(int i=n;i;--i)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
        for(int i=1;i<=m;++i)c[i]=0;
        for(int i=1;i<=n;++i)++c[x[i]];
        for(int i=1;i<=m;++i)c[i]+=c[i-1];
        for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
        swap(x,y);
        p=1;
        x[sa[1]]=1;
        for(int i=2;i<=n;++i)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        if(p==n)break;
        m=p;
    }
    for(int i=1;i<=n;++i)
        rank[sa[i]]=i;
    for(int i=1,p=0;i<=n;++i){
        if(rank[i]==1)continue;
        if(p)--p;
        int j=sa[rank[i]-1];
        while(s[i+p]==s[j+p])++p;
        height[rank[i]]=p;
    }
}
int QAQ(){
    n=read();
    scanf("%s",s+1);
    m='z';
    SA();
//  for(int i=1;i<=n;++i)
//      printf("%d %d\n",sa[i],height[i]);
//  putchar('\n');
    long long ans=0;
    ans+=n-sa[1]+1;
    for(int i=2;i<=n;++i)
        ans+=(long long)n-sa[i]+1-height[i];
    printf("%lld\n",ans);
    return false;
}
}
int main(){
    return orz::QAQ();
}

对于这个思路,我们还可以发现这样插入的子串是按照字典序从小到大的,所以我们还可以二分取出第k大的本质不同的字串。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
namespace orz{
const int N=100;
int x[N],y[N],c[N],sa[N],rank[N],height[N];
long long sum[N];
char s[N];
int f[N][20];
int Log[N];
int n,m;
inline long long read(){
    long long a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
inline void SA(){
    for(int i=1;i<=n;++i)++c[x[i]=s[i]];
    for(int i=1;i<=m;++i)c[i]+=c[i-1];
    for(int i=n;i;--i)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k+1;i<=n;++i)y[++p]=i;
        for(int i=1;i<=n;++i)if(sa[i]>k)y[++p]=sa[i]-k;
        for(int i=1;i<=m;++i)c[i]=0;
        for(int i=1;i<=n;++i)++c[x[i]];
        for(int i=1;i<=m;++i)c[i]+=c[i-1];
        for(int i=n;i;--i)sa[c[x[y[i]]]--]=y[i];
        swap(x,y);
        p=1;
        x[sa[1]]=1;
        for(int i=2;i<=n;++i)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        if(p==n)break;
        m=p;
    }
    for(int i=1;i<=n;++i)
        sa[rank[i]]=i;
    for(int i=1,p=0;i<=n;++i){
        if(rank[i]==1)continue;
        if(p)--p;
        int j=sa[rank[i]-1];
        while(s[i+p]==s[j+p])++p;
        height[rank[i]]=i;
    }
}
inline void preLog(){
    for(int i=0;(1<<i)<=n;++i)
        Log[1<<i]=i;
    for(int i=0,res=0;i<=n;++i){
        if(Log[i])res=Log[i];
        else Log[i]=res;
    }
}
inline void findString(long long x,int &tl,int &tr){
    int l=1,r=n,mid;
    while(l<r){
        mid=(l+r+1)>>1;
        if(sum[mid]>=x)r=mid-1;
        else l=mid;
    }
    tl=sa[l];
    tr=sa[l]+x-sum[l]+height[l];
}
int QAQ(){
    long long tot=0;
    int q;
    long long x,y;
    int xl,xr,yl,yr;
    n=read(),q=read();
    m='z';
    sum[1]=n-sa[1]+1;
    for(int i=2;i<=n;++i)
        sum[i]=n-sa[i]-height[i]+1;
    for(int i=1;i<=n;++i)
        sum[i]+=sum[i-1];
    while(m--){
        x=read(),y=read();
        findString(x,xl,xr);
        findString(y,yl,yr);
        printf("%d %d %d %d\n",xl,xr,yl,yr);
    }
    return false;
}
}
int main(){
    return orz::QAQ();
}

没写完2

#include<cstdio>
#include<algorithm>
using namespace std;
namespace orz{
const int N=100;
int n,m;
int c[N],x[N],y[N];
int Log[N];
inline void log2(int x){

}
inline void logPre(){
    
}
struct String{
    int f[N][20];
    inline void getMin(){
    
    }
    inline void SA(){
    
    }
}
inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
}
int QAQ(){
    return false;
}
}
int main(){
    return orz::QAQ();
}

2019年7月博客汇总下

标签:矩阵乘法   基本   坐标   usaco   org   his   move   解决   重载   

原文地址:https://www.cnblogs.com/oiertkj/p/12203542.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!