UOJ Logo kczno1的博客

博客

共找到 27 篇包含 “题解” 标签的博客:

小C的字符串的简单做法

2018-07-03 23:42:30 By kczno1

https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1825

好久之前的坑.今天想了一下,忽然发现了一个简单做法.

记两个串为$a,b$,长度为$n,m$

设$dp[i][j]$表示$a_{1..i}$和$b_{1..j}$的$lcs$

考虑从$1$到$m$枚举$h$,动态维护无视$b_{1..h}$的$dp$数组(也就是$dp[i][j]$表示$a_{1..i}$和$b_{h+1..j}$的$lcs$)

假如现在枚举到$h$,对于$h$这一列,第一个变化的$dp[i][h]$必定满足$a[i]==b[h]$,并且$dp[i..n][h]$全部会减$1$.

再依次考虑列$h+1,..m$的变化,可以发现每一列变化的都是一个后缀,并且不长于前一列.

所以枚举每一列,维护后缀长度即可.

现在唯一的问题就是判断一个点可以从哪里转移过来.

每次变化的转移边只有轮廓上那些,所以可以轻松维护.

时间$O(n^2)$

(然而被卡常了...不是很懂为什么$n^2$要出成$5000$)

#include<bits/stdc++.h>
using namespace std;

template <typename T> void chmin(T&x,const T &y)
{
    if(x>y)x=y;
}
template <typename T> void chmax(T &x,const T &y)
{
    if(x<y)x=y;
}
typedef long long s64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef pair<int,int> pii;
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)
#define rep0(i,l,r) for(int i=l;i<r;++i)
#define gc (c=getchar())
int read()
{
    char c;
    while(gc<'-');
    if(c=='-')
    {
        int x=gc-'0';
        while(gc>='0')x=x*10+c-'0';
        return -x;
    }
    int x=c-'0';
    while(gc>='0')x=x*10+c-'0';
    return x;
}
#undef gc

const int N=5000+5,D=998244353;
bool fr[N][N][3];
char s1[N],s2[N];
int dp[N][N],dpn[N];

int main()
{
#ifdef kcz
    freopen("1.in","r",stdin);//freopen("1.out","w",stdout);
#endif
    scanf("%s%s",s1+1,s2+1);
    int n=strlen(s1+1),m=strlen(s2+1);
    rep(i,1,n)
    rep(j,1,m)
    {
        int a=dp[i][j-1],b=dp[i-1][j-1]+(s1[i]==s2[j]),c=dp[i-1][j];
        dp[i][j]=max(max(a,b),c);
        fr[i][j][0]=a==dp[i][j];
        fr[i][j][1]=b==dp[i][j];
        fr[i][j][2]=c==dp[i][j];
    }
    rep(j,1,m)dpn[j]=dp[n][j];
    s64 ans=0;
    rep(h,1,m)
    {
        rep(j,h,m)ans=(ans*233+dpn[j])%D;
        int i=1;
        while(fr[i][h][0]||fr[i][h][2])++i;
        if(i>n)continue;
        rep(j,h+1,m)
        {
            if(!fr[i][j][1]&&!fr[i][j][2])fr[i][j][1]=fr[i][j][2]=1;
            else
            {
                fr[i][j][0]=0;
                while(++i,fr[i][j][2])fr[i][j][0]=fr[i][j][1]=0;
                if(i>n)break;
                fr[i][j][2]=1;
            }
            --dpn[j];
        }
    }
    cout<<ans;
}

[Ynoi2018]末日时在做什么?有没有空?可以来拯救吗?题解&&求证复杂度

2018-01-26 11:57:02 By kczno1

分块

块内需要维护前缀后缀全局的最大子段和

前缀后缀的求个凸包就行了

全局的话,每次求出当前最大子段和及长度,并求出使长度变化全局至少要加多少

因为加的是正数,所以一旦变化长度一定是变长的

所以只会变$n$次

求这个可以直接二分,但是是$\log{值域}$的

更好的方法是考虑以$i$为右端点时,就是要求$\min{(ans_{s}-(s_{i}-s_{j}))/((i-j)-ans_{len})}$,其中$j < i-ans_{len}$

所以可以从左到右枚举$i$,维护$(j,-s_{j})$的凸包,然后在凸包上二分.

时间$O(n \sqrt{n \log{n} })$

但是其实还有一个问题,就是当加在一个块的一个部分时,其答案长度可能变短

这样时间就没法证了..

我既不会证也不会卡,只能求出大佬了..

(其实我向$lxl$要了题解..但感觉太麻烦了而且常数大就写了这个)

(如果能有人优化下面代码或者自己写一个然后打败标算也是极好的)

(其实这个代码是很难过的,我在$luogu$上试了十几个分块长度才过的,不过一过就踩了标算.)


#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define pb push_back
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)
#define mid (l+r>>1)

template <typename T> inline void chmin(T &x,const T &y)
{
    if(x>y)x=y;
}
template <typename T> inline void chmax(T &x,const T &y)
{
    if(x<y)x=y;
}

const int ch_top=6e6;
char ch[ch_top],*now_r=ch-1;
char ch_w[ch_top],*now_w=ch_w-1;
#define gc (*++now_r)
#define c (*now_r)
int read()
{
    while(gc<'-');
    if(c=='-')
    {
        int x=gc-'0';
        while(gc>='0')x=x*10+c-'0';
        return -x;
    }
    int x=c-'0';
    while(gc>='0')x=x*10+c-'0';
    return x;
}
int readz()
{
    while(gc<'-');
    int x=c-'0';
    while(gc>='0')x=x*10+c-'0';
    return x;
}
#undef c
void write(ll &ans)
{
    if(ans<10){*++now_w='0'+ans;*++now_w='\n';return ;}
    static char st[20];
    int top=0;
    do{st[++top]='0'+ans%10;}while(ans/=10);
    do{*++now_w=st[top];}while(--top);
    *++now_w='\n';
}

#define B(x) ((x-1)/L)
const int N=1e5+5,L=/*4*/192,K=N/L+5;
const ll inf=1e11+2e9+1;
int n;
ll a[N];
struct TU
{
int q[L];ll s[L];
int head,tail;
};
struct BLOCK
{
int l,r;
ll ans;int anslen;ll nex;
ll ad;
TU pre,suf;ll sum;
bool full;
};
BLOCK block[K],*b,*e,*belong[N];

namespace BUILD
{
ll a[L];
ll ns[L];int nq[L];
ll ans;int anslen;
bool ok(const ll &x)
{
    ll s=0,li=ans+anslen*x;
    rep(i,0,L-1)
    {
        s=(s+a[i]+x<0)?0:s+a[i]+x;
        if(s>li)return 1;
    }

    return 0;
}
void build(BLOCK &k,bool init=1)
{
    ll x=k.ad,*_a=::a+k.l;
    k.ad=0;
    rep(i,0,L-1)a[i]=(_a[i]+=x);
    ll s;
   ans=0;anslen=0;
    s=0;int len=0;
    rep(i,0,L-1)
    {
        s+=a[i];
        if(s<0){s=0;len=0;}
        else
        {
            ++len;
            if(s>ans){ans=s;anslen=len;}
            else
            if(s==ans&&len>anslen)anslen=len;
        }
    }
    k.ans=ans;k.anslen=anslen;

    if(/*!ok(inf)*/anslen==L){k.nex=inf;k.pre.head=k.suf.head=k.pre.tail=k.suf.tail=0;k.pre.s[0]=k.suf.s[0]=k.sum=ans;k.full=1;return ;}
if(init)
{
    int /*head=0,*/tail=-1;
    ll *ns=k.pre.s;int *nq=k.pre.q;
    s=0;
    rep(i,1,L)
    {
        s+=a[i-1];
        while(tail>=0&&ns[tail]<=s)--tail;
        while(tail>=1&&(nq[tail-1]-i)*(ns[tail]-s)>=(nq[tail]-i)*(ns[tail-1]-s))--tail;
        ++tail;
        nq[tail]=i;ns[tail]=s;
    }
    k.pre.head=/*head*/0;k.pre.tail=tail;
    k.sum=s;

    /*head=0;*/tail=-1;
    ns=k.suf.s;nq=k.suf.q;
    s=0;
    rep(i,1,L)
    {
        s+=a[L-i];
        while(tail>=0&&ns[tail]<=s)--tail;
        while(tail>=1&&(nq[tail-1]-i)*(ns[tail]-s)>=(nq[tail]-i)*(ns[tail-1]-s))--tail;
        ++tail;
        nq[tail]=i;ns[tail]=s;
    }
    k.suf.head=/*head*/0;k.suf.tail=tail;
}
else
{
    ll *ns=k.pre.s;int *nq=k.pre.q;
    int i=k.pre.tail,head=k.pre.head;
    ns[i]+=x*nq[i];
    k.sum=ns[i];
    while(--i>=head)
    {
        ns[i]+=x*nq[i];
        if(ns[i]<=ns[i+1]){k.pre.head=i+1;break;}
    }


    ns=k.suf.s;nq=k.suf.q;
    i=k.suf.tail;head=k.suf.head;
    ns[i]+=x*nq[i];
    while(--i>=head)
    {
        ns[i]+=x*nq[i];
        if(ns[i]<=ns[i+1]){k.suf.head=i+1;break;}
    }
}
/*  ll l=0,r=10000;
    while(!ok(r)){r<<=1;if(r>=inf){r=inf;break;}}
    while(l+1!=r)
    if(ok(mid))r=mid;
    else l=mid;
    k.nex=r;*/
    ll nex=inf;
static bool may[L];
    s=0;
    per(i,L-1,anslen)
    {
        may[i]=s<=0;
        s=s+a[i]<0?0:s+a[i];
    }

    s=0;ll y=ans;
    int top=0;
    ns[0]=0;nq[0]=-1;
    int i=anslen,j=0;
    rep(k,0,i-1)y-=a[k];
    while(i<L)
    {
        s-=a[j];y-=a[i];

        if(may[i])
        {   
        //int x=i-anslen;
#define x j
        if(top<5)
        {
            rep(h,0,top)
            if((x-nq[h])*nex>y-ns[h])nex=(y-ns[h])/(x-nq[h]);
        }
        else
        {
            int l=0,r=top;
            while(l!=r)
            if((nq[mid+1]-x)*(ns[mid]-y)>=(nq[mid]-x)*(ns[mid+1]-y))l=mid+1;
            else r=mid;

            if((x-nq[l])*nex>y-ns[l])nex=(y-ns[l])/(x-nq[l]);
        }
#undef x
        }

        while(top>=1&&(nq[top-1]-j)*(ns[top]-s)>=(nq[top]-j)*(ns[top-1]-s))--top;
        if(s>ns[top]){ns[++top]=s;nq[top]=j;}
        ++i;++j;
    }

    k.nex=ok(nex)?nex:nex+1;
//  printf("%lld\n",k.nex);
}
};
using BUILD::build;

int main()
{
//  freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    ch[fread(ch,1,ch_top,stdin)]=0;
    n=readz();int m=readz();
    rep(i,1,n)a[i]=read();
    b=block;e=b-1;
    for(int l=1;l<=n;)
    {
        int r=l+L-1;
        if(l==1)r=l+L*2; 
        if(l+L*2>=n)r=n;
        ++e;
        e->l=l;e->r=r;
        while(l<=r){belong[l]=e;++l;}
    }
    for(BLOCK *p=b+1;p<e;++p)build(*p);
    while(m--)
    {
        while(gc<'0');
        if(*now_r=='1')
        {
            int l=readz(),r=readz(),x=readz();
            BLOCK *bl=belong[l],*br=belong[r];
            if(bl==br)
            {
                rep(i,l,r)a[i]+=x;
                if(bl>b&&bl<e)
                {
                    if(bl->full)bl->ans+=(ll)x*(r-l+1);
                    else build(*bl);
                }
            }
            else
            {
                int bl_r=bl->r;
                rep(i,l,bl_r)a[i]+=x;
                if(bl>b)
                {
                    if(bl->full)bl->ans+=(ll)x*(bl_r-l+1);
                    else build(*bl);
                }

                while(++bl<br)bl->ad+=x;

                int br_l=br->l;
                rep(i,br_l,r)a[i]+=x;
                if(br<e)
                {
                    if(br->full)br->ans+=(ll)x*(r-br_l+1);
                    else build(*br);
                }
            }
        }
        else
        {
            int l=readz(),r=readz();
        //  if(r-l<=L*2)
            if(belong[l]==belong[r])
            {   
                ll ans=0,s=0,x=belong[l]->ad;
                rep(i,l,r)
                {
                    s=(s+a[i]+x<0)?0:s+a[i]+x;
                    if(s>ans)ans=s;
                }
                write(ans);
                continue;
            }
            ll ans=0,s=0;
            BLOCK *bl=belong[l],*br=belong[r];
            int bl_r=bl->r;ll x=bl->ad;
            rep(i,l,bl_r)
            {
                s=(s+a[i]+x<0)?0:s+a[i]+x;
                if(s>ans)ans=s;
            }
            while(++bl<br)
            {
                x=bl->ad;
                if(bl->full)
                {
                    s+=bl->ans+x*L;
                    if(s>ans)ans=s;
                }
                else
                if(x>=bl->nex)
                {
                    build(*bl,0);
                    if(bl->ans>ans)ans=bl->ans;
                    if(s+bl->pre.s[bl->pre.head]>ans)ans=s+bl->pre.s[bl->pre.head];
                    s=max(0LL,max(s+bl->sum,bl->suf.s[bl->suf.head]));
                }
                else
                {
                    if(bl->ans+x*bl->anslen>ans)ans=bl->ans+x*bl->anslen;
                #define get q->s[q->head]+x*q->q[q->head]
                #define pop while(q->head<q->tail&&q->s[q->head]<q->s[q->head+1]+x*(q->q[q->head+1]-q->q[q->head]))++q->head;
                    TU *q=&bl->pre;
                    pop
                    if(s+get>ans)ans=s+get;
                    q=&bl->suf;
                    pop
                    s=max(0LL,max(s+bl->sum+x*L,get));
                #undef get
                #undef pop
                }
            }
            int br_l=br->l;x=br->ad;
            rep(i,br_l,r)
            {
                s=(s+a[i]+x<0)?0:s+a[i]+x;
                if(s>ans)ans=s;
            }
            write(ans);
        }
    }
    fwrite(ch_w,1,now_w-ch_w+1,stdout);
}

hackerrank Suffix Rotation

2017-12-25 18:04:14 By kczno1

https://www.hackerrank.com/contests/w28/challenges/suffix-rotation/problem

首先最后一定是能排成从小到大的,且每次一定是把最小的字符往前移。

而且无论如何移动前i个字符,第i+1之后的字符在环上的相对位置一定是不变的。

那么当第i个字符最后一位确定了之后,后面的字符串一定是固定的。

用$dp[j]$表示排完前$i(i=s[j])$个字符,最后一位为$j$的最小步数。

考虑从第$i-1$个字符到第$i$个字符的过程,每一块连续的$i$都是一起移动的,而且每次都是把一个块的开头移动到前面。

所以说$dp[j]$的意思更正为最后一块为$j$。(可以让每个块用开头代表)

考虑每个第$i-1$个字符的最后一块$k$的贡献,

定义$next[k]$为删去前$i-1$个字符后块$k$后面的第一个位置,$cnt[i]$为字符$i$的块的个数。

若$s[next[k]]!=i$,则对任意$j,dp[j]=dp[k]+cnt[i]$

若$s[next[k]]=i$,

设$next[k]$所属的块为$j'$

(1)若$next[k]$不是$j'$的块首,那么同上

(2)若$next[k]$是$j'$块首

对$j!=j'$,因为块$j'$不需要移动,所以$dp[j]=dp[k]+cnt[i]-1$

对$j'$,若$cnt[i]>1$,则其他$j$必须先移动,所以$j'$仍需要移动,所以$dp[j']=dp[k]+cnt[i]$;

          若$cnt[i]=1$,则$dp[j']=dp[k]$

记个最小值就$O(n)$了

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define pb push_back
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)

template <typename T> inline void chmin(T &x,const T &y)
{
    if(x>y)x=y;
}
template <typename T> inline void chmax(T &x,const T &y)
{
    if(x<y)x=y;
}

const int N=1e3+5,U=256;
int n;
char s[N];
int pre[N],nex[N];
int a[U][N];
int dp[N];

int main()
{
    freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    int tt;
    cin>>tt;
    while(tt--)
    {
        scanf("%s",s+1);
        int n=strlen(s+1);
        char mx=0;
        rep(i,1,n)chmax(mx,s[i]);

        rep(i,1,n) {nex[i]=i+1;pre[i]=i-1;}
        pre[1]=n;nex[n]=1;

        rep(i,0,mx)*a[i]=0;
        rep(i,1,n)a[s[i]][++*a[s[i]]]=i;
        rep(c,0,mx-1)
        {
            int top=0;
            rep(i,1,*a[c])
            {
                int x=a[c][i];
                if(s[pre[x]]==c) 
                {
                    nex[pre[x]]=nex[x];
                    pre[nex[x]]=pre[x];
                }
                else a[c][++top]=a[c][i];
            }
            *a[c]=top;
            rep(i,1,top) 
            {
                int x=a[c][i];
                nex[pre[x]]=nex[x];
                pre[nex[x]]=pre[x];
            }
        }

        int lastc=0;
        *a[0]=1;
        a[0][1]=0;
        nex[0]=1;
        dp[0]=0;
        rep(c,1,mx-1)
        if(*a[c])
        {
            int mn=N,cant=-1;
            rep(i,1,*a[lastc])
            {
                int k=a[lastc][i];
                int j=nex[k];
                if(s[j]==c&&s[pre[j]]!=c) 
                {
                    int now=dp[k]-1;
                    if(now<mn)
                    {
                        mn=now;
                        if(*a[c]==1) cant=-1;
                        else cant=j;
                    }
                    else 
                    if(now==mn)cant=-1;
                }
                else
                {
                    int now=dp[k];
                    if(now<mn){mn=now;cant=-1;}
                    else 
                    if(now==mn)cant=-1;
                }
            }
            mn+=*a[c];
            rep(i,1,*a[c])
            {
                int j=a[c][i];
                if(j!=cant)dp[j]=mn;
                else dp[j]=mn+1;
            }
            lastc=c;
        }

        int ans=N;
        rep(i,1,*a[lastc])chmin(ans,dp[a[lastc][i]]);
        printf("%d\n",ans);
    }
}

51nod 1303 交叉矩阵

2017-12-24 16:14:46 By kczno1

https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1303

所求=总矩阵对数-相离矩阵对数-相包含矩阵对数

总矩阵对数=${矩阵数 \choose 2}$

考虑如何求有多少个全1矩阵。

先枚举右下角的端点$(i,j)$,维护以$(i,j)$为右下角的全1矩阵个数

枚举行$i$,从左到右枚举列$j$

用单调栈维护$k=1..j$到$j$的$up$的$min$ ($up[i][j]$表示点$(i,j)$向上有连续多少个1)

以$(i,j)$为右下角的矩阵个数=$min_k$的和,可以维护。

考虑如何求相包含矩阵对数。

枚举较大的矩阵($n*m$),则较小矩阵有${n+1 \choose 2} * {m+1 \choose 2}$个

所以用类似上面的方法,维护$1*f(m),n*f(m),n^2*f(m)$的和即可。($f(m)为{m+1 \choose 2} 的前缀和$)

考虑如何求相离矩阵对数。

即左右相离+上下相离-左上右下相离-右上左下相离

求出每个点作为四个方向的端点的矩阵个数,之后就轻松解决了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define pb push_back
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)

const int N=1e3+5,D=1e9+7;
ll mi(ll x,int y=D-2)
{
    ll ans=1;
    while(y)
    {
        if(y&1)ans=ans*x%D;
        x=x*x%D;y>>=1;
    }
    return ans;
}
int n,m;
char b[N][N];
int up[N][N],down[N][N];
ll S1[N],S2[N],f[N],f2[N];
//S1[n]=sigma i; S2[n]=sigma i^2; f[n]=sigma S[i]
ll a[N],h[N];
ll cnt[N][N],cnt1[N][N],cnt2[N][N],cnt3[N][N];
//cnt 右下角 ;cnt1 左下角 ;cnt2 右上角 ;cnt3 左上角 
ll c[N],c1[N];

int main()
{
    freopen("1.in","r",stdin);//freopen("2.out","w",stdout);
    cin>>n>>m;
    rep(i,1,n)scanf("%s",b[i]+1);

    int mn=max(n,m);
    rep(i,1,mn){S1[i]=S1[i-1]+i;f[i]=f[i-1]+S1[i];}
    rep(i,1,mn){S2[i]=S2[i-1]+i*i;(f2[i]=f2[i-1]+S2[i])%D;}

    rep(i,1,n)
    rep(j,1,m) 
    if(b[i][j]=='1')up[i][j]=up[i-1][j]+1;
    per(i,n,1)
    rep(j,1,m) 
    if(b[i][j]=='1')down[i][j]=down[i+1][j]+1;

    ll ans=0;
    //+C2(tot_cnt) 
    //-相包含
    //-横相离-纵相离+左上右下相离+右上左下相离

    //-相包含 
    rep(i,1,n)
    {
        int top=0,ncnt=0;
        //ncnt sigma h_k
        ll s0=0,s1=0,s2=0;
        //s0=sigma f[h_k]
        //s1=sigma (j-k+1)*f[h_k]
        //s2=sigma (j-k+1)^2*f[h_k]
        rep(j,1,m)
        {
            (s2+=2*s1+s0)%=D;
            (s1+=s0)%=D;
            while(h[top]>up[i][j])
            {
                ncnt-=h[top]*(a[top]-a[top-1]);
                (s0-=f[h[top]]*(a[top]-a[top-1]))%=D;
                (s1-=f[h[top]]*(S1[j-a[top-1]]-S1[j-a[top]]))%=D;
                (s2-=f[h[top]]*(S2[j-a[top-1]]-S2[j-a[top]]))%=D;
                --top;
            }
            h[++top]=up[i][j];
            a[top]=j;
            ncnt+=h[top]*(a[top]-a[top-1]);
            (s0+=f[h[top]]*(a[top]-a[top-1]))%=D;
            (s1+=f[h[top]]*(S1[j-a[top-1]]-S1[j-a[top]]))%=D;
            (s2+=f[h[top]]*(S2[j-a[top-1]]-S2[j-a[top]]))%=D;

            cnt[i][j]=ncnt;
            (ans+=s2+s1)%=D;
        }
    }
    (ans*=mi(2))%=D;
    ans=-ans;

        //get cnt2
    rep(i,1,n)
    {
        int top=0,ncnt=0;
        rep(j,1,m) 
        {
            while(h[top]>down[i][j])
            {
                ncnt-=h[top]*(a[top]-a[top-1]);
                --top;
            }
            h[++top]=down[i][j];
            a[top]=j;
            ncnt+=h[top]*(a[top]-a[top-1]);
            cnt2[i][j]=ncnt;
        }
    }

    a[0]=m+1;
    //get cnt1
    rep(i,1,n)
    {
        int top=0,ncnt=0;
        per(j,m,1) 
        {
            while(h[top]>up[i][j])
            {
                ncnt-=h[top]*(a[top-1]-a[top]);
                --top;
            }
            h[++top]=up[i][j];
            a[top]=j;
            ncnt+=h[top]*(a[top-1]-a[top]);
            cnt1[i][j]=ncnt;
        }
    }
    //get cnt3
    rep(i,1,n)
    {
        int top=0,ncnt=0;
        per(j,m,1)
        {
            while(h[top]>down[i][j])
            {
                ncnt-=h[top]*(a[top-1]-a[top]);
                --top;
            }
            h[++top]=down[i][j];
            a[top]=j;
            ncnt+=h[top]*(a[top-1]-a[top]);
            cnt3[i][j]=ncnt;
        }
    }

    //-横相离 
    rep(i,1,n)
    rep(j,1,m) c[j]+=cnt[i][j];
    rep(i,1,n)
    rep(j,1,m) c1[j]+=cnt1[i][j];
    ll sum=0;
    rep(i,1,m)sum+=c[i];
    sum%=D;
//-----  +C2(tot_cnt) 
    (ans+=(sum+1)*sum/2)%=D;
//-----    
    per(i,m,1)
    {
        (sum-=c[i])%=D;
        (ans-=sum*c1[i])%=D;
    }

    //-纵相离 
memset(c,0,sizeof(c));memset(c1,0,sizeof(c1));
    rep(i,1,n)
    rep(j,1,m) c[i]+=cnt[i][j];
    rep(i,1,n)
    rep(j,1,m) c1[i]+=cnt2[i][j];
    sum=0;
    rep(i,1,n)sum+=c[i];
    sum%=D;
    per(i,n,1)
    {
        (sum-=c[i])%=D;
        (ans-=sum*c1[i])%=D;
    }

    //+左上右下相离
    rep(i,1,n)
    rep(j,1,m)cnt[i][j]+=cnt[i][j-1];
    rep(i,1,n)
    rep(j,1,m)(cnt[i][j]+=cnt[i-1][j])%=D;
    rep(i,1,n)
    rep(j,1,m)(ans+=cnt3[i][j]*cnt[i-1][j-1])%=D;

    //+左下右上相离 
    rep(i,1,n)
    per(j,m,1)cnt1[i][j]+=cnt1[i][j+1];
    rep(i,1,n)
    rep(j,1,m)(cnt1[i][j]+=cnt1[i-1][j])%=D;
    rep(i,1,n)
    rep(j,1,m)(ans+=cnt2[i][j]*cnt1[i-1][j+1])%=D;

    cout<<(ans%D+D)%D;
}

bzoj3508

2017-11-29 14:29:06 By kczno1

定义数组$a:$$a[i]=1$表示最终第$i$个灯泡亮,$0$表示暗。

则操作相当于对$a$区间异或。

差分,即令$b[i]=a[i]$ $xor$ $a[i-1]$

则$a$的区间$[l,r]$异或等价于$b[l],b[r+1]$异或。

把所有$b[i]=1$的$i$拿出来,记作数组$q$

就是每次解决掉一对$q[i],q[j]$,代价为$g[q[j]-q[i]]$

( $g$可以$O(nl)$ $bfs$得到 )

状压$dp$,$O(2^{len}*len)$ ($len$为$q$的长度,$<=2*k$)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define pb push_back
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)

template <typename T> inline void chmin(T &x,const T &y)
{
    if(x>y)x=y;
}
template <typename T> inline void chmax(T &x,const T &y)
{
    if(x<y)x=y;
}

#define gc (c=getchar())
int read()
{
    char c;
    while(gc<'-');
    if(c=='-')
    {
        int x=gc-'0';
        while(gc>='0')x=x*10+c-'0';
        return -x;
    }
    int x=c-'0';
    while(gc>='0')x=x*10+c-'0';
    return x;
}

const int N=1e4+5,L=100+5,K=10,inf=N*N;
int n,k,l;
int a[N],b[N],g[N];
int top,q[K*2];
int dp[1<<K*2];

namespace GET_G
{
int q[N];
int a[L];
void get()
{
    rep(i,1,l)a[i]=read();
    rep(i,1,n) g[i]=N;
    g[0]=0;
    int tail=1;
    q[1]=0;
    rep(head,1,tail)
    {
        int x=q[head];
        rep(i,1,l)
        {
            int y=x+a[i];
            if(y<=n&&g[y]==N) g[q[++tail]=y]=g[x]+1;
            y=x-a[i];
            if(y>0&&g[y]==N) g[q[++tail]=y]=g[x]+1;
        }
    }
}
};

int DP(int u)
{
    if(dp[u]!=-1)return dp[u];
    dp[u]=inf;
    int i=0;
    while(!(u&(1<<i))) ++i;
    rep(j,i+1,top-1)
    if(g[q[j]-q[i]]!=N)
    if(u&(1<<j)) chmin(dp[u],DP(u-(1<<i)-(1<<j))+g[q[j]-q[i]]);
    return dp[u];
}

int main()
{
    freopen("1.in","r",stdin);//freopen("1.out","w",stdout);
    int tt;
    cin>>tt;
    while(tt--)
    {
        cin>>n>>k>>l;

        memset(a,0,sizeof(a));
        rep(i,1,k)a[read()]=1;
        top=0;
        rep(i,1,n+1)
        if(a[i]^a[i-1])q[top++]=i;

        GET_G::get();

        int u=(1<<top)-1;
        memset(dp+1,-1,u*sizeof(int));
        dp[0]=0;
        DP(u);
        if(dp[u]==inf) puts("-1");
        else printf("%d\n",dp[u]);
    }
}

一道挺妙的矩乘优化递推的题

2017-11-06 21:27:45 By kczno1

题意:

$f_{n}=\sum_{i=1}^{20} w_i*f_{n-i}$ (模一个常数)

给定$w_{1..20}$ , $f_{1..20}$ , 询问$f_n$

其中$n$以$k$进制给出,$k<=10$,$n$的位数$l<=1000$

$T$组询问,$k$都一样($T<=200$)

题解:

朴素想法是将$n$转化成$2$进制($O(l^2)$),快速幂优化矩乘($ O(l * \log_{2}{10} * 20^3 ) $)

然而这两个操作复杂度都过高,会$TLE$

考虑直接使用$k$进制,

$dp$求出所有$M^{j*k^i} ( j\in[0,k) )$

但这样$dp$复杂度为$O(l * 10 * 20^3 )$

但注意到只用$dp$一遍而不是$T$遍,所以是可以接受的。

这样单次询问复杂度就变成$ O(l * 20^3 ) $

注意到,我们要算的其实是一个$1*20$的矩阵乘多个$20*20$的矩阵

其实不用先把$20*20$的都乘起来再乘$1*20$的

可以直接依次乘

这样就只用$1*20$的乘$20*20$的

复杂度变成$ O(l * 20^2 ) $

可以通过。

[Usaco2012 Open]Bookshelf 的O(n)做法

2017-09-23 22:53:10 By kczno1

官方,网上题解似乎都是$O(nlogn)$的

前面应该都差不多:

$dp[i]$表示$1..i$的$min$

则$dp[i]=min(dp[j-1]+max(h[j..i]))$ 且$w(j)+..w(i)<=L$

从左到右枚举$i$,

则所有$j$被所有$h[k]=max(h[k..i])$的$k$分成一段一段

每段由于$dp[j]$的单调递增肯定取最左边的那个$j$

$k$可以用单调栈维护

由于$w(j)+..w(i)$的限制,可以的$j$的左边界是单调左移的

所以问题就变成了两端删除,一端插入,维护最值。

我想了一个$O(n)$的方法,而且是能解决两端插入的 (求问有没有别的方法)

就是左右各开一个栈维护最值,

左边删除插入就用左边的

右边删除插入就用右边的

如果左或右的栈删没了,就重构,把队列分成两半,左边分一半,右边分一半

每次重构的时间复杂度和下次删完所需的时间是一样的,所以复杂度是线性的。

卡常后拿了bzoj rank1

bzoj3011

2017-08-30 18:08:05 By kczno1

网上题解似乎都是可并堆。

这题可以给每个点自己+1,上面第一个距离>=l的祖先-1,然后统计子树和,即为答案。

求那个祖先可以dfs一遍,把点存在栈里,然后二分。

$O(nlogn)$

能拿bzoj rank1。

#include<cstdio>
using namespace std;

typedef long long ll;
#define pb push_back
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)

template <typename T> inline void chmin(T &x,const T &y)
{
    if(x>y)x=y;
}
template <typename T> inline void chmax(T &x,const T &y)
{
    if(x<y)x=y;
}

const int ch_top=1e7;
char ch[ch_top],*now_r=ch-1,*now_w=ch-1;
#define gc (*++now_r)
#define c *now_r
void read(int &x)
{
    while(gc<'0');
    x=c-'0';
    while(gc>='0')x=x*10+c-'0';
}
void read(ll &x)
{
    while(gc<'0');
    x=c-'0';
    while(gc>='0')x=x*10+c-'0';
}
#undef gc
#undef c
void write(int x)
{
    static char st[20];
    int top=0;
    do{st[++top]='0'+x%10;}while(x/=10);
    do{*++now_w=st[top];}while(--top);
    *++now_w='\n';
}

const int N=2e5+5;
int n,t[N],nex[N];
ll l,a[N];
ll st[N];int stx[N],top;
int s[N];
void find(const ll &ns,int r)//将第一个<=ns的祖先-1 
{
    int l=1;
    if(st[r]<=ns){--s[stx[r]];return ;}
#define mid (l+r>>1)
    while(l+1!=r)
    if(st[mid]<=ns)l=mid;
    else r=mid;
    --s[stx[l]];
}
void dfs(int x,const ll &ns,int dep)
{
    st[dep]=ns;stx[dep]=x;
    if(ns>=l)
    {
        find(ns-l,dep-1);
    }
    s[x]=1;
    for(int y=t[x];y;y=nex[y]) 
    {
      dfs(y,ns+a[y],dep+1);
      s[x]+=s[y];
    }
}

int main()
{
    freopen("1.in","r",stdin);//freopen("1.out","w",stdout);
    ch[fread(ch,1,ch_top,stdin)]=0;
    read(n);read(l);l+=1;
    rep(i,2,n)
    {
        int x;
        read(x);read(a[i]);
        nex[i]=t[x];t[x]=i;        
    }
    dfs(1,0,1);
    rep(i,1,n)write(s[i]);
    fwrite(ch,1,now_w-ch+1,stdout);
}

[Cqoi2016]伪光滑数

2017-08-27 08:03:28 By kczno1

这题很多人都用DP+可并堆

也有很多人暴力上堆 复杂度是$klogk$乘上128以内的质因数个数,约30(不过实际效果挺好的)

然而直接上堆,可以做到$2*k$次$push$,$k$次$pop$ 。

首先枚举所有可能的最大质因子及质因子分解的项数

这样题目的限制就没了,每次拓展时就可以除掉一个质因数再乘上一个质因数(只要别把最大质因子除没了)

考虑如何可以使得所有数会被拓展到且只会被拓展到一次

一个想法就是每次拓展时除掉最小质因子再乘上其前驱(比它小的最大质数)

但对于一个有多个质因子的数这样还是不能拓展到

所以再加一个方向:除掉最大质因子再乘上最小质因子

这样就ok了。

$O(klogk)$

拿到了bzoj rank3。 (下面这个直接用priority_queue的就能拿rank5,后来我手写了二叉堆。 我还写了配对堆,但不知道为什么慢了一倍多)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define pb push_back
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)

const int N=128;
int pr[N],cnt;
struct point
{
    ll a;
    int b,c,d;
    point( ll a , int b , int c , int d ) : a( a ) , b( b ) , c( c ) , d( d ) {}
};
bool operator <(const point &p1,const point &p2)
{
    return p1.a<p2.a;
}
priority_queue<point> h;

bool is_p(int x)
{
    for(int i=2;i*i<=x;++i)
        if(x%i!=0) return 0;
    return 1;
}

int main()
{
    //freopen("1.in","r",stdin);//freopen("1.out","w",stdout);
    ll n;int k;
    cin>>n>>k;
    rep(i,2,N)
    if(is_p(i))
    {
        pr[++cnt]=i;
        ll a=1;
        for(int j=1;a<=n/i;++j)
        {
            a*=i;
            h.push( point(a,i,0,0) );
            if(cnt>1&&j>1)
                h.push( point(a/i*pr[cnt-1],i,j-1,cnt-1) );
        }
    }

    while(--k)
    {
        point now=h.top();
        h.pop();
        if(now.d>1)
            h.push( point( now.a/pr[now.d]*pr[now.d-1],now.b,now.c,now.d-1 ) );
        if(now.c>1)
            h.push( point( now.a/now.b*pr[now.d],now.b,now.c-1,now.d ) );
    }
    cout<<h.top().a;
}

泉州一中oj 建造 题解

2017-08-19 11:06:11 By kczno1

https://v.qzyz.com/problem/show/2711

没有题解我来发一个。

有环删安全值最小,相当于维护安全值的最大生成树;

安全值一样删最后,相当于以排列中的位置为第二关键字(升序)。

如果要求最大价值,就按安全值为第一关键字(降序),价值为第二关键字(降序)排序做kruscal

但还要让排列的字典序最小

就贪心枚举每一位,从小到大枚举这一位填什么,然后判断是否可以达到最大价值。

判断的方法就是按安全值为第一关键字(降序),排列中的位置为第二关键字(升序),价值为第三关键字(降序)排序做kruscal
(对不在排列中的边就令他们位置都为最大)
(一开始我令在排列中的边位置都为最小。。居然还过了)

每次只改变一条边的位置,不用重新排序,可以直接特判

所以复杂度$O(m^3)$

(好像说有$mlogm$的做法?我不会)

#include<bits/stdc++.h>
using namespace std;

#define rep(i,l,r) for(int i=l;i<=r;++i)
#define rep0(i,n) for(int i=0;i<n;++i)

#define gc (c=getchar())
int read()
{
    char c;
    while(gc<'0');
    int x=c-'0';
    while(gc>='0')x=x*10+c-'0';
    return x;
}

const int N=300+5;
int n,m,fa[N];
struct edge
{
    int x,y,p,c,dy;
}l[N];int nex[N],pre[N];
int dy[N];
bool operator <(const edge &e1,const edge &e2)
{
    if(e1.p!=e2.p) return e1.p>e2.p;
    return e1.c>e2.c;
}
int ans;

namespace get
{
int ans;
int find(int x)
{
    return fa[x]==0?x:fa[x]=find(fa[x]);
}
void add(int i)
{
    edge *it=l+i;
    int x=find(it->x),y=find(it->y);
    if(x==y)return ;
    fa[x]=y;ans+=it->c;
}
int g(int x)
{
    memset(fa+1,0,n*sizeof(int));
    ans=0;
    int i=1;
    while(i<=m)
    {
        if(i==pre[x])add(x);
        int j=nex[i];
        rep(k,i,j-1)add(k);
        i=j;
    }
    return ans;
}
};

int main()
{
    freopen("1.in","r",stdin);
    cin>>n>>m;
    rep(i,1,m)l[i]=(edge){read(),read(),read(),read(),i};
    sort(l+1,l+m+1);
    rep(i,1,m)dy[l[i].dy]=i;
    nex[m]=m+1;
    for(int i=m;--i;)
    if(l[i].p==l[i+1].p) nex[i]=nex[i+1];
    else nex[i]=i+1;
    nex[0]=1;
    rep(i,1,m)
    if(nex[i-1]==i)
    rep(j,i,nex[i]-1)
     pre[j]=i;

    ans=get::g(0);
    cout<<ans<<endl;
    static bool in[N];
    int top=0;
    while(++top<=m)
    {
        int i=0;
        while(++i<=m)
        if(!in[i])
        {
            in[i]=1;
            int x=dy[i];
            if(get::g(x)==ans)
            {
               int j=pre[x];
               while(x!=j)
               {
                 swap(l[x],l[x-1]);swap(dy[l[x].dy],dy[l[x-1].dy]);
                 --x;
               }
               while(++j<nex[x]) pre[j]=x+1;
               nex[x]=x+1;
               break;
            }
            in[i]=0;
        }
        printf("%d ",i);
    }
}