UOJ Logo kczno1的博客

博客

hnoi2017 单旋

2017-04-17 15:30:22 By kczno1

首先考虑如何不利用树的本身来实现insert,单旋min,max的操作。

insert:

一定是插在前驱或者后继的下面。

用线段树得到前驱后继,

判断一下前驱后继哪一个能插即可。

单旋min:

min一定只有右儿子,并且到爸爸的一条链上都是往左的。

发现单旋后树的形态改变不大,只有右儿子和爸爸接了起来,并且他变成了根。

单旋max同理

旋到根后删除是很简单的。

现在需要支持询问每个点到根的距离。用lct维护即可。

#include<bits/stdc++.h>
using std::sort;
using std::cerr;
using std::endl;

const int N=100010;
struct query
{
    int type,x;
}q[N];
int m,i;
int n;
namespace lisan
{
    int *q[N];
    void push(int &x)
    {
        scanf("%d",&x);
        q[++n]=&x;
    }
    bool xiao(int *x,int *y)
    {
        return *x<*y;
    }
    void work()
    {
        sort(q+1,q+n+1,xiao);
        for(i=1;i<=n;++i) *q[i]=i;
    }
};

#define L (i<<1)
#define R (L+1)
bool a[N*3];int d;
void mark(int i)
{
    for(i+=d;!a[i];i>>=1)a[i]=1;
}
void clear(int i)
{
    a[i+=d]=0;
    while(i>>=1)a[i]=a[L]||a[R];
}
int get_min(int i=1)
{
    while(i<=d)
    if(a[L])i=L;
    else i=R;
    return i-d;
}
int get_max(int i=1)
{
    while(i<=d)
    if(a[R])i=R;
    else i=L;
    return i-d;
}
int get_pre(int i)
{
    i+=d;
    while(i)
    {
        if((i&1)&&a[i^1]) { i^=1;break; }
        i>>=1;
    }
    if(!i) return 0;
    return get_max(i);
}
int get_suf(int i)
{
    i+=d;
    while(i)
    {
        if((!(i&1))&&a[i^1]) { i^=1;break; }
        i>>=1;
    }
    if(!i) return 0;
    return get_min(i);
}

namespace lct
{
    int f[N],c[N][2],sz[N];
    bool root(int x)
    {
        return x!=c[f[x]][0]&&x!=c[f[x]][1];
    }
    void up(int x)
    {
        sz[x]=sz[c[x][0]]+sz[c[x][1]]+1; 
    }
    bool get(int x)
    {
        return x==c[f[x]][1];
    }
    void sc(int y,int x,bool d)
    {
        f[x]=y;
        c[y][d]=x;
    }
    void rot(int x)
    {
        int y=f[x];bool d=get(x);
        if(root(y)) f[x]=f[y];
        else sc(f[y],x,get(y));
        sc(y,c[x][!d],d);sc(x,y,!d);
        up(y);
    }
    void splay(int x)
    {
        while(!root(x))
        {
            int y=f[x];
            if(root(y)) { rot(x);break; }
            rot(get(y)==get(x)?y:x);rot(x);
        }
        up(x);
    }
    void access(int x)
    {
        int y=0;
        while(x)
        {
            splay(x);
            c[x][1]=y;
            y=x;
            x=f[x];
        }
    }
    int deep(int x)
    {
        access(x);
        splay(x);
        return sz[x];
    }
   /* void del(int rt)
    {
        splay(rt);
        f[c[rt][1]]=0;
    }*/
    void clear(int x)
    {
        f[x]=c[x][0]=c[x][1]=0;
    }
};

int root,f[N],c[N][2];
void sc(int y,int x,bool d)
{
    f[x]=y;
    c[y][d]=x;
}
bool get(int x)
{
    return x==c[f[x]][1];
}
void ins(int x)
{
    if(!root) {root=x;return ;}
    int pre=get_pre(x),suf=get_suf(x);
    if(!pre||c[pre][1]) //pre不行,选择插在suf左边 
    { sc(suf,x,0);lct::f[x]=suf; }
    else
    { sc(pre,x,1);lct::f[x]=pre; }
}

int rt;
void change(int x,bool d)
{
    if(c[x][d])
    {
      lct::access(c[x][d]);
      if(x==root) { f[root=c[x][d]]=0;lct::f[root]=0; }
      else 
      {sc(f[x],c[x][d],get(x));
       lct::f[c[x][d]]=0;
       lct::sc(c[x][d],lct::c[x][0],0);
      }
    }
    else 
    {
      if(x==root)root=0;
      else lct::f[lct::c[x][0]]=0;
      c[f[x]][get(x)]=0;
    }
    c[x][d]=f[x]=0;
    lct::clear(x); 
}
void make_root(int x)
{
    if(root)
    {
     sc(x,root,root>x);
     lct::splay(root);
     lct::f[root]=x;
    }
    root=x;
}
void rot_min()
{
    rt=get_min();
    printf("%d\n",lct::deep(rt));
    change(rt,1);
    if(q[i].type==4) clear(rt);
    else make_root(rt); 
}
void rot_max()
{
    rt=get_max();
    printf("%d\n",lct::deep(rt));
    change(rt,0);
    if(q[i].type==5) clear(rt);
    else make_root(rt); 
}

int main()
{
    freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
    scanf("%d",&m);
    for(i=1;i<=m;++i) 
    {
      scanf("%d",&q[i].type);
      if(q[i].type==1) lisan::push(q[i].x);
    }
    lisan::work();
    for(d=1;d<n;d<<=1);d-=1;
    for(i=1;i<=m;++i)
    {
        //cerr<<i<<endl;
        if(i==4)
         int kcz=1;
           if(q[i].type==1) 
           {
               ins(q[i].x);
               mark(q[i].x);
               printf("%d\n",lct::deep(q[i].x));
           }else
           if(q[i].type==2||q[i].type==4) rot_min();
           else rot_max();
    }
}

bzoj1189: [HNOI2007]紧急疏散evacuate

2017-04-15 11:37:50 By kczno1

看到有题解说二分,但其实不用二分(二分多一个log)。

从小到达枚举时间p,对每个时间给每个door新建一个点i,
从i向T连一条容量为1的边。
从它p-1的i向p的i连一条容量为inf的边,表示更早的人也可以选择。
从所有跟该door距离为p的人向i连一条容量为1的边,表示他从此时开始可以选择了。
如果满流就退出,否则就接着枚举,并保留当前的那个网络。
(一开始从S向每个人连一条容量为1的边)

处理跟该door距离为p的点 就是 一开始从每个door为起点bfs一遍,处理出距离,再存到vector里面。

我还加了一个剪枝,就是如果没有人和这个door距离为p,就不新建点,把原来的点连向T的边的容量+=1。

(由于很久调试不出来,代码加了一些注释。。)

#include<bits/stdc++.h>
using std::vector;
using std::min;
using std::cerr;using std::endl;

const int N=30,inf=100000000;
const int fx[4]={0,-1,0,1},fy[4]={-1,0,1,0};//四个方向

int door[N<<2],d;//d=door的个数
int now[N<<2],nowe[N<<2];//now=当前时间建的点i,nowe=i连向T的边
int n,m,i,j,x,y,need;//need是还需要流的量
int dy[N][N];//dy[i][j]就是(i,j)对应的点 
char ch[N][N];
vector<int> st[N<<2][N*N];//st[x][p]表示和door[x]距离为p的点

int q[N*N],head,tail;bool vis[N*N];int g[N*N];//g[x]为x到rt的距离
void bfs(vector<int> *a,int rt)
{
    for(;tail>1;--tail)vis[q[tail]]=0;
    g[q[tail=1]=rt]=0;
    for(head=1;head<=tail;++head)
    {
        x=q[head];
        if(head>1) a[g[x]].push_back(x);
        int X=(x-1)/m+1,Y=(x-1)%m+1;
        for(i=0;i<4;++i)
        {
            y=dy[X+fx[i]][Y+fy[i]];
            if(vis[y])continue;//vis[0],vis[墙],vis[门]都=1
            vis[y]=1;q[++tail]=y;
            g[y]=g[x]+1;
        }
    }
}

namespace kcz
{
    const int N=101000,S=N-1,T=N-2;
    int n;
    int t[N];
    struct edge
    {
        int to,next,f;
    }l[1000000];int e=1;
    void add_e(int x,int y,int f)
    {
        l[++e]=(edge){y,t[x],f};t[x]=e;
        l[++e]=(edge){x,t[y]};t[y]=e;
    }

    int q[N],head,tail,i,x,y,g[N],_t[N];//_t是当前弧
    bool bfs()
    {
        q[tail=1]=T;
        for(i=1;i<=n;++i) g[i]=inf;g[S]=inf;
        for(head=1;head<=tail;++head)
        {
            x=q[head];_t[x]=t[x];
            if(g[x]>=g[S])continue;
            for(i=t[x];y=l[i].to;i=l[i].next)
            if(l[i^1].f&&g[y]==inf) 
            {
                g[y]=g[x]+1;q[++tail]=y;
            }
        }
        return g[S]!=inf; 
    }
    int dfs(int x,int f0)
    {
        if(x==T)return f0;
        int f=f0;
        for(int &i=_t[x];y=l[i].to;i=l[i].next)
        if(l[i].f&&g[y]==g[x]-1)
        {
            int del=dfs(y,min(f,l[i].f));
            l[i].f-=del;l[i^1].f+=del;
            f-=del;
            if(!f)return f0;
        }
        return f0-f;
    }

    void solve(int mx)
    {
        int p;
        for(p=1;need&&p<=mx;++p)
        {
            for(x=1;x<=d;++x)
            {
                if(!st[x][p].size()){ ++l[nowe[x]].f;continue; } 
                ++n; 
               if(now[x]) add_e(now[x],++n,inf);
               add_e(n,T,1);
               now[x]=n;nowe[x]=t[n];
               for(j=0;j<st[x][p].size();++j)
                 add_e(st[x][p][j],n,1);
            }        
            while(bfs()) 
             need-=dfs(S,inf);
        }
        if(need)puts("impossible");
        else printf("%d\n",p-1);
    }
};

int main()
{ freopen("1.in","r",stdin);freopen("1.out","w",stdout);
        scanf("%d%d",&n,&m);
    int i,j,x;
    for(i=1;i<=n;++i)
    for(j=1;j<=m;++j)
     dy[i][j]=++kcz::n;
    for(i=1;i<=n;++i) 
    {scanf("%s",ch[i]+1);
     for(j=1;j<=m;++j)
     {
         x=dy[i][j];
        if(ch[i][j]=='X') {vis[x]=1;continue;}
        if(ch[i][j]=='D') {door[++d]=x;vis[x]=1;}
        else {kcz::add_e(kcz::S,x,1);++need;}
     }
    } 
    vis[0]=1;
    for(x=1;x<=d;++x) 
     bfs(st[x],door[x]);
    kcz::solve(n*m);
}

CF 125E. MST Company

2017-04-15 10:23:43 By kczno1

容易想到二分。
二分一个值w,将与1相连的边全部减去w,之后做mst。
那么w越大,mst选择的与1相连的边的个数(记作num)就越大。
那么二分出使得num=k的w即可。
因为我们保证了结果是原mst-k*w后最小的,
所以也一定保证了原mst最小。

但问题是,对于一个w,num的值是不确定的,因为最小生成树有很多选择。
网上有人说把二分的w改成double即可,我认为这是错的。
double其实没什么意义,由于边权是整数,1.1和1.2做出来的mst一定是一样的。

其实对于一个w,num的值应该是一个区间[l,r]。
我们二分出使得r>=k的最小的w,那么[l,r]一定包含k,因为w-1的r<k,
这时我们再做一遍mst,使得恰好选k条边即可。

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

const int N=5005,M=100000,U=100001;
int n,m,k,i;
struct edge
{
    int x,y,id,w;
    bool is;
    bool operator <(const edge &i)const
    {
        if(w!=i.w) return w<i.w;
        return is; 
    }
}q[M+3];
int st[N],top;
int f[N];
int find(int x)
{
    return x==f[x]?x:f[x]=find(f[x]);
}
int check(int w,bool can)
{
    for(i=1;i<=m;++i) 
    if(q[i].is)q[i].w-=w;

    int ans=0,x,y;
    sort(q+1,q+m+1);
    for(i=1;i<=n;++i) f[i]=i;
    for(top=0,i=1;i<=m;++i)
    {
        x=find(q[i].x);y=find(q[i].y); 
        if(x==y) continue;
        if(!can&&ans+q[i].is>k) continue;
        f[x]=y;
        st[++top]=q[i].id;
        ans+=q[i].is;
    } 

    for(i=1;i<=m;++i)     
    if(q[i].is)q[i].w+=w;

    return ans;
} 

#define mid (l+r>>1)

int main()
{ freopen("1.in","r",stdin);
    scanf("%d%d%d",&n,&m,&k);
    for(i=1;i<=m;++i)
    {
        scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].w);q[i].id=i;
        q[i].is=(q[i].x==1)||(q[i].y==1);
    }
    if(check(U,1)<k||check(-U,1)>k||top<n-1) printf("-1\n");
    else
    {
        int l=-U,r=U;
        while(l+1!=r)
        if(check(mid,1)<k) l=mid;
        else r=mid;
        check(r,0);
        printf("%d\n",n-1);
        for(;top;--top) printf("%d ",st[top]);
    }
}

[Wc2008]游览计划 dp

2017-04-13 16:58:24 By kczno1

总算理解了这个dp。

由于最后的结果一定成一个树形,
我们用dp[s][i]表示包括的关键点集合为s,当前树的根为i的最少花费。
转移有两种,一种是给树扩展一个节点,一种是把两棵树并在一起。
也就是:
1 dp[s][i]=dp[s][j]+a[i] j与i相邻
2 dp[s][i]=dp[x][i]+dp[s-x][i]-a[i] x为s的子集
从小到大枚举s
先做第一个转移 再用当前的s做第二个转移更新更大的s
由于第一个转移是有环的 我们要用spfa来实现

一ac就拿了bzoj rank1。。其实应该还有地方没有卡常。

#include<bits/stdc++.h>

const int S=1<<10,N=101,inf=1<<29;
int n,m,p,k,full;
int a[N],num[N],i,j,dy[12][12];

int fr[S][N],f[S][N];
int t[N];
struct edge
{
    int to,next;
}l[N<<2];int e;
void add_e(int x,int y)
{
    l[++e]=(edge){y,t[x]};t[x]=e;
    l[++e]=(edge){x,t[y]};t[y]=e;
}

int base;
int *g,*from;bool in[N];
int q[100000],head,tail,x,y;
void spfa()
{
    for(i=1;i<=p;++i) q[i]=i;
    tail=p;
    for(head=1;head<=tail;++head)
    {
        x=q[head];in[x]=0;
        for(i=t[x];y=l[i].to;i=l[i].next)
        if(g[y]>g[x]+a[y])
        {
            g[y]=g[x]+a[y];from[y]=base+x;
            if(!in[y]) {in[y]=1;q[++tail]=y;}
        }
    }
}

bool b[N];
void dfs(int s,int x)
{
    if(!s) return ;
    b[x]=1;
    int ns=fr[s][x]/N,y=fr[s][x]%N;
    if(y==x) {dfs(ns,x);dfs(s-ns,x);}
    else dfs(ns,y);
}

int main()
{
    freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    scanf("%d%d",&n,&m);
    int i,j;
    for(i=1;i<=n;++i) 
    for(j=1;j<=m;++j) 
    {
     scanf("%d",a+(++p));
     dy[i][j]=p;
     if(!a[p])num[p]=++k;
    }

    full=(1<<k)-1;
    for(i=1;i<=full;++i)
    for(j=1;j<=p;++j)
     f[i][j]=inf;
    for(k=0,i=1;i<=p;++i)
    if(a[i]) f[0][i]=a[i];
    else f[1<<num[i]-1][i]=0;

    for(i=2;i<=n;++i) 
    for(j=1;j<=m;++j) 
     add_e(dy[i][j],dy[i-1][j]);
    for(j=2;j<=m;++j)
    for(i=1;i<=n;++i)
     add_e(dy[i][j-1],dy[i][j]);

    int s,ns;
    for(s=1;s<=full;++s)
    {
        g=f[s];from=fr[s];
        base=s*N;
        spfa();
        ns=full-s; 
        for(j=ns;j;j=(j-1)&ns)
        if(j<s)
        for(i=1;i<=p;++i)
         if(f[s+j][i]>g[i]+f[j][i]-a[i])
         {
             f[s+j][i]=g[i]+f[j][i]-a[i];
             fr[s+j][i]=base+i;
         }
    }

    int mn=1;
    for(i=2;i<=p;++i)
    if(g[i]<g[mn]) mn=i;
    printf("%d\n",g[mn]);
    dfs(full,mn);

    for(i=1;i<=n;puts(""),++i)
    for(j=1;j<=m;++j) 
    if(num[dy[i][j]]) printf("x"); else
    if(b[dy[i][j]]) printf("o"); else
    printf("_"); 
}

[Wc2010]重建计划 的 长链剖分做法

2017-04-05 19:58:26 By kczno1

首先二分答案ans, 平均数>=ans <=> 所有人-ans后,sum>=0

题目变成求长度在区间内的最长链

树形dp dp[i][j]表示i的子树里以i为top的长度为j的链max权值 枚举儿子即可转移 转移时顺便更新答案

注意到第一个儿子只用区间加上一个边的权值就可以直接转移 长链剖分 每条链开个线段树维护

O(nlog^2)

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

inline void chmax(double &x,const double &y) { if(x<y)x=y; }
const int N=100010;
const double minmin=1e-8;
int n,L,R;
int t[N];
struct edge
{
    int to,next,len;
}l[N<<1];
int i,j,x,y,k;
int f[N],deep[N],mx[N],son[N],top[N],len_to_f[N];
struct seg
{
    double *mx,*ad;    
    int n;
    void init(int _n)
    {
        n=_n;
        mx=new double [n*4+5];
        ad=new double [n*4+5];
    }
#define Mid (l+r>>1)
#define cl (k<<1)
#define cr (cl+1)
    void add(int k,const double &w)
    {
        mx[k]+=w;ad[k]+=w;
    }
    void down(int k)
    {
        if(ad[k]) { add(cl,ad[k]);add(cr,ad[k]); } 
        ad[k]=0;
    }
    void up(int k)
    {
        mx[k]=max(mx[cl],mx[cr]);
    }
    void change(int x,const double &w)
    {
        int k=1,l=1,r=n;
        while(l!=r)
        {
            down(k);
            if(x<=Mid) { k=cl;r=Mid; }
            else { k=cr;l=Mid+1; } 
        }
        mx[k]=w;
        while(k>>=1) up(k);
    }
    double ans;
    int ql,qr;
    void qiu(int k,int l,int r)
    {
        if(ql<=l&&qr>=r)
        {
            chmax(ans,mx[k]);
            return ;
        }
        if(ql>r||qr<l) return ;
        down(k);
        qiu(cl,l,Mid);
        qiu(cr,Mid+1,r);
    }
    double get(int l,int r)
    {
        ql=l;qr=r;ans=-1e12;
        qiu(1,1,n);
        return ans;
    }
}a[N];
void init(int x)
{
    for(y=x;y;y=son[y]) top[y]=x;
    a[x].init(mx[x]);
}
int st[N],tail;
void dfs(int x,int fr=0,int Deep=0)
{
    st[++tail]=x;
    f[x]=fr;deep[x]=++Deep;

    int i=t[x],y,i0,&c=son[x];
    if(l[i].to==fr) { len_to_f[x]=l[i].len;i=t[x]=l[i].next;} 
    for(;y=l[i].to;i0=i,i=l[i].next)
    if(y==fr)  
    {l[i0].next=l[i].next;len_to_f[x]=l[i].len;} 
    else
    {
        dfs(y,x,Deep);
        if(mx[y]>mx[c]) c=y;
    }

    mx[x]=mx[c]+1;
    if(!c) return ;

    i=t[x];
    if(l[i].to==c)  i=t[x]=l[i].next; 
    for(;y=l[i].to;i0=i,i=l[i].next)
    if(y==c) l[i0].next=l[i].next; 
    else init(y);
}

int head,del,rt;
bool ok(const double &base)
{
    for(head=n;head;--head)
    {
        x=st[head];
        seg &now=a[top[x]];
        del=deep[x]-deep[top[x]]+1;
        now.change(del,0);
        for(i=t[x];y=l[i].to;i=l[i].next)
        {
            for(j=1;j<=a[y].n;++j) 
            if(a[y].get(j,j)+now.get(del+max(L-j,1),del+R-j)>=-minmin) 
             return 1;
            for(j=1;j<=a[y].n;++j) 
            if(a[y].get(j,j)>=now.get(del+j,del+j)+minmin)
             now.change(del+j,a[y].get(j,j)); 
        }
        if(now.get(del+L,del+R)>=-minmin) return 1;
         now.add(1,len_to_f[x]-base);
    }
    return 0;
}

double erfen()
{
    double l=0,r=1e6,mid;
    for(int i=0;i<=40;++i)
    if(ok(mid=(l+r)/2)) l=mid;
    else r=mid;
    return l;
}

int main()
{
    freopen("1.in","r",stdin);freopen("1.out","w",stdout); 
    scanf("%d%d%d",&n,&L,&R);
    int e=n;
    for(i=1;i<n;++i,++e)
    {
        scanf("%d%d%d",&x,&y,&k);
        l[i]=(edge){y,t[x],k};t[x]=i;
        l[e]=(edge){x,t[y],k};t[y]=e;
    }
    dfs(1);init(1);
    printf("%.3lf\n",erfen());
}

为什么hack失败

2017-02-11 22:07:10 By kczno1

求助 zjoi2016旅行者 tle

2017-02-06 18:49:24 By kczno1

看了题解打的,但不知道为什么tle了附加点。

就是对于当前矩形,选择较短的中轴做最短路,之后处理一下询问,之后分治两边。

感觉跟2009wc最短路问题类似啊。

我的spfa应该没问题(还加了pairing_heap优化)

点(x,y)标号为(x-1)*m+y

#include<cstdio>

#define r_top 10000000
char ch[r_top],*now_r=ch;
void read(int &x)
{
    while (*now_r<'0') ++now_r;
    for (x=*now_r-'0';*++now_r>='0';)
     x=(x<<1)+(x<<3)+*now_r-'0';
}

#define chmin(x,y) if(x>y)x=y;
#define oo 1000000000
#define N 20100
#define Q 100100
#define add_e(x,y) l[++e]=(edge){y,t[x],len};t[x]=e;
#define add_(x,y) add_e(x,y) add_e(y,x)
int t[N];
struct edge
{
    int to,next,len;
}l[N<<2];int e;

int n,m,i,j,k,x,y,len;
struct Query
{ int x1,y1,x2,y2,id1,id2;
  bool r() 
  { read(x1);read(y1);read(x2);read(y2);
    id1=(x1-1)*m+y1;id2=(x2-1)*m+y2; //两个点的标号
    return id1!=id2;//如果一样就ans=0
  }
}query[Q];
int q[Q],ans[Q];
int g[N];//距离
bool in[N];//in=1表示在当前区间内

int head[N],next[N],*dy[N];
#define fu(x,y) {dy[y]=&x;x=y;}
#define _fu(x,y) {dy[y]=x;*x=y;}
void merge(int &x,int y)
{
    if (g[x]<g[y])
    {
        fu(next[y],head[x]);fu(head[x],y);
    }
    else
    {
        fu(next[x],head[y]);fu(head[y],x);
        x=y;
    }
}
void merges(int &x)
{
    int y=next[x],r;
    while (y)
    {
        r=next[y];
        merge(x,y);
        y=r;
    }
    next[x]=0;
}
void spfa(int x)
{
    int rt=x;g[x]=0;
    do
    {
        x=rt;merges(rt=head[rt]);
        head[x]=next[x]=0;dy[x]=0;
        for (int i=t[x],y;i;i=l[i].next)
        if (in[y=l[i].to]&&g[y]>g[x]+l[i].len)
        {
            g[y]=g[x]+l[i].len;
            if (y==rt) continue;
            if (!rt) {rt=y;continue;}
            if (dy[y]!=0) _fu(dy[y],next[y]);
            merge(rt,y);
        }
    }while (rt);
}

int o,q1[Q],q2[Q];
void solve(int lx,int rx,int ly,int ry,int l,int r)
{
    static int st[N],top;//当前区间内节点 
    if (l>r) return ;
    int numx=rx-lx+1,numy=ry-ly+1;
    x=(lx-1)*m+ly;
    for (i=1;i<=numx;++i,x+=m)
    for (j=0;j<numy;++j) 
     in[st[++top]=x+j]=1;
    if (numx<numy)
    {
        int mid=ly+ry>>1;
        x=(lx-1)*m+mid;//中轴最上面的坐标
        for (o=1;o<=numx;++o,x+=m)//每次往下移
        {
            for (i=1;i<=top;++i) g[st[i]]=oo;
            spfa(x);
           for (j=l;j<=r;++j)
           {
              y=q[j];
              chmin(ans[y],g[query[y].id1]+g[query[y].id2]);
           }
        }
        while (top) in[st[top--]]=0;
        int h1=0,h2=0;
        for (j=l;j<=r;++j) 
        {
            y=q[j];
            if (query[y].y1<mid)
            {
                if (query[y].y2<mid) q1[++h1]=y;
            }
            else
            if (query[y].y1>mid)
            if (query[y].y2>mid) q2[++h2]=y;
        }
        int h=l-1;
        for (i=1;i<=h1;++i) q[++h]=q1[i];
        for (i=1;i<=h2;++i) q[++h]=q2[i];
        solve(lx,rx,ly,mid-1,l,l+h1-1);
        solve(lx,rx,mid+1,ry,l+h1,h);
    }
    else
    {
        int mid=lx+rx>>1;

        x=(mid-1)*m+ly;//中轴最左边的坐标 
        for (o=1;o<=numy;++o,++x)
        {
            for (j=1;j<=top;++j) g[st[j]]=oo;
            spfa(x);
         for (j=l;j<=r;++j)
         {
             y=q[j];
             chmin(ans[y],g[query[y].id1]+g[query[y].id2]);
         }
        }
        while (top) in[st[top--]]=0;
        int h1=0,h2=0;
        for (j=l;j<=r;++j) 
        {
            y=q[j];
            if (query[y].x1<mid)
            {
                if (query[y].x2<mid) q1[++h1]=y;
            }
            else
            if (query[y].x1>mid)
            if (query[y].x2>mid) q2[++h2]=y;
        }
        int h=l-1;
        for (i=1;i<=h1;++i) q[++h]=q1[i];
        for (i=1;i<=h2;++i) q[++h]=q2[i];
        solve(lx,mid-1,ly,ry,l,l+h1-1);
        solve(mid+1,rx,ly,ry,l+h1,h);
    }
}

int main()
{
    freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    fread(ch,1,r_top,stdin);
    read(n);read(m);
    for (i=1;i<=n;++i,++x)
    for (j=1;j<m;++j)
    {
        read(len);
        ++x;
        add_(x,x+1) 
    }
    x=0;
    for (i=1;i<n;++i)
    for (j=1;j<=m;++j) 
    {
        read(len);
        ++x;
        add_(x,x+m) 
    }
    int qnum;
    read(qnum);
    int top=0;
    for (i=1;i<=qnum;++i) 
    if (query[i].r())
    {
      ++top;    
      ans[q[top]=i]=oo;
    }
    solve(1,n,1,m,1,top);
    for (i=1;i<=qnum;++i) printf("%d\n",ans[i]);
}

求助。。关于快乐游戏鸡题解的不懂的地方

2017-01-28 11:13:27 By kczno1

"然后树链剖分以后对于每个点,首先把它的重儿子的线段树拉过来更新一下,然后对于它的每个轻儿子的重链(注意不是轻儿子的子树,否则可能复杂度会多一个log有卡常风险)对应的线段树拉过来更新当前点的线段树就好了。"
为什么可以不把轻儿子的子树拿过来,而是拿轻儿子的重链。。
为什么时间只有一个log。。
upd: 好吧,我应该想通了。
1 按深度的轻重链剖分+选取重儿子,合并轻儿子就是启发式合并
2 两者总时间都是nlogn

证明:

一个点的子树的线段树的元素个数最多只有它子树的深度。

假设所有点的子树的线段树的元素个数=子树的深度,那么实际情况不会 更差.

这样就可以按深度的轻重链剖分+选取重儿子了。

由于每条链只有在top处被拿去合并,所以每条链只被合并了一次。

所以合并总个数O(n),所以总时间O(nlogn)

同时,我们可以发现,启发式合并不会比它更差。

suffix array模板

2017-01-25 21:25:39 By kczno1

$O(nlogn)$版

要注意:
计数排序后面一定要逆序加回去,这样才能保证原来的顺序不被破坏

int sa[N],q1[N],q2[N],*rank=q1,*temp=q2;
//sa[i] suffix array 后缀数组 第i名的起始位置
//rank[i] 第i个起始位置的rank 
char s[N];
int num[N];

int n,i;
void get_sa()
{
    int m=255;
    for (i=1;i<=n;++i) ++num[s[i]];
    for (i=1;i<=m;++i) num[i]+=num[i-1];
    for (i=n;i;--i) sa[num[s[i]]--]=i;
    int last,now;
    m=rank[last=sa[1]]=1;
    for (i=2;i<=n;++i) 
    {
        now=sa[i];
        if (s[now]!=s[last]) ++m;
        rank[last=now]=m;
    }

    for (int l=1;m<n;l<<=1)//rank[i],rank[i+l]作关键字
    {
        //先以rank[i+l]作关键字排序 
        int top=0;
        //i>n-l的 i+l已经超出 直接放进来 
        for (i=n-l+1;i<=n;++i) temp[++top]=i;
        //i<=n-l的 可以利用上一次的sa
        for (i=1;i<=n;++i)
        if (sa[i]>l) temp[++top]=sa[i]-l;

        //在temp的基础上 以rank[i]为关键字 计数排序 
        memset(num,0,m+1<<2);
        for (i=1;i<=n;++i) ++num[rank[i]];
        for (i=2;i<=m;++i) num[i]+=num[i-1];
        for (i=n;i;--i) sa[num[rank[temp[i]]]--]=temp[i];

        std::swap(rank,temp);//temp存储原来的rank,来比较是否一样
        m=rank[last=sa[1]]=1;
        for (i=2;i<=n;++i)
        {
            now=sa[i];
            if (!(temp[now]==temp[last]&&temp[now+l]==temp[last+l])) ++m;
            rank[last=now]=m; 
        }
    } 
}

CF 480 div_1 E Parking Lot

2017-01-19 19:34:16 By kczno1

题意:n*m的矩阵,有一些点不能选。k次操作,每次都让一个点变成不可选,每次都问当前可选的最大正方形。n,m,k<=2000

我想到了一个方法。过掉了。

新加的点造成的新的正方形一定过那个点,也就一定过它所在的列。

用第j列更新答案时,从当前答案+1开始从小到大试。 由于答案最多增加n次,所以总的尝试次数是O(N+K)的。 每次检验,我们从上到下枚举区间,用单调队列维护当前两边的最小值,时间是O(N)的。 所以总时间O(N^2+NK)

(看了cf的官方题解,没看懂,但时间是nklogk的)

但如果是求矩形最大面积怎么做呢?!!(或许不可做?)

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;

void chmax(int &x,int y)
{
    if(x<y)x=y;
}
#define N 2010
int l[N][N],r[N][N];//水平上延伸的极限点
bool ok[N][N];//可用的点 
int now;
bool open;
int n,m,k,len,i,j;
void dp_lr(int i)//dp第i行的l,r 
{
    for (j=1;j<=m;++j)
    if (ok[i][j])
     l[i][j]=l[i][j-1];
    for (j=m;j;--j)
    if (ok[i][j])
     r[i][j]=r[i][j+1]; 
}

struct queue
{
    int q[N],h[N],head,tail;
    void clear()
    {
       head=1;tail=0;
    }
    void push(int x)
    {
        while (tail>=head&&h[tail]>=x) --tail;
        ++tail;
        q[tail]=i;h[tail]=x;
    }
    int len()
    {
        while (i-q[head]>now) ++head;
        if (h[head]<0) return -N;
        return h[head];
    }
}ql,qr;
void dp_ud(int j)//第j列更新当前答案 
{
    for (;;)
    {
        ql.clear();qr.clear();
        for (i=1;i<=now;++i) 
        {
            ql.push(j-l[i][j]);
            qr.push(r[i][j]-j);
        }
        for (;i<=n;++i)
        {
            ql.push(j-l[i][j]);
            qr.push(r[i][j]-j);
            if (ql.len()+qr.len()>=now) break;
        }
        if (i>n) return ;
        ++now;
    }
}

char ch[N]; 

int o;
struct query
{
    int x,y,ans;
    void init()
    {
        scanf("%d%d",&x,&y);
        ok[x][y]=0;l[x][y]=y+1;r[x][y]=y-1;
    }
    void add()
    {
        ans=now;
        ok[x][y]=1;
        dp_lr(x);
        dp_ud(y);
    }
}p[N];

int main()
{ freopen("1.in","r",stdin);freopen("1.out","w",stdout); 
   while (scanf("%d%d%d",&n,&m,&k)!=EOF)
   {
    int i,j;now=0;
    for (i=1;i<=n;++i)
    {
        scanf("%s",ch+1);
        for (j=1;j<=m;++j) 
        if(ch[j]=='.') ok[i][j]=1;
        else { ok[i][j]=0;l[i][j]=j+1;r[i][j]=j-1; }
    }
    for (i=1;i<=k;++i) p[i].init();

    for (i=1;i<=n;++i) {l[i][0]=1;r[i][m+1]=m;dp_lr(i);}
    for (j=1;j<=m;++j) dp_ud(j);

    for (i=k;i;--i) 
     p[i].add();
    for (i=1;i<=k;++i) printf("%d\n",p[i].ans);
   }
}
共 51 篇博客