UOJ Logo kczno1的博客

博客

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

Count on a tree 2的点分+暴力bitset做法

2017-04-26 23:15:35 By kczno1

点分,处理每个点到根的bitset,询问时或一下两个bitset。

但是由于可持久化树存储的bitset或运算会很慢,在q*n/32本来就是时间瓶颈的情况下显然是不好的,

所以我想不用可持久化bitset,而是直接每个点都新开个bitset。

注意到实际上bitset只用当前分治处理的这棵树的信息,所以离散化一下,大小就变成O(树的大小)了

于是复杂度就是O(n^2/32+n^2/2/32+..n^2/n/32)=O(n^2/32)。

于是我没有把int全部替换成unsigned short,也没有在分治的树小的时候暴力等等,就拿到了bzoj rank1。

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

#define us unsigned short
#define ui unsigned int
const int N=40010;
struct edge
{
    int to,next;
}l[N<<1];int e;
int a[N],t[N];

int n,i,x,y;
bool xiao(int *x,int *y)
{
    return *x<*y;
}
void lisan()
{
    static int *q[N],top;
    for(i=1;i<=n;++i)q[i]=a+i;
    sort(q+1,q+n+1,xiao);
    int now=-1;
    for(i=1;i<=n;++i)
    {
        if(*q[i]!=now){now=*q[i];++top;}
        *q[i]=top;
    }
}

us t_f[N],deep[N];
ui *b[17][N],**now_b,*X,*Y;
us U[N];

bool vis[N];
us q[N],head,tail,sz[N];
us cnt[N],mx;
us f[N];
void build(us rt,us dep,us fr)
{
    for(;mx;--mx)cnt[mx]=0; 
    f[q[tail=1]=rt]=0;
    for(head=1;head<=tail;++head)
    {
        x=q[head];sz[x]=1;
        cnt[a[x]]=1;if(a[x]>mx)mx=a[x];
        for(i=t[x];y=l[i].to;i=l[i].next)
        if(!vis[y]&&y!=f[x])
            f[q[++tail]=y]=x;
    }

    for(i=2;i<=mx;++i)cnt[i]+=cnt[i-1];
    while(x=q[--head])
    {
        if((sz[x]<<1)>=tail)break;
        sz[f[x]]+=sz[x];
    }
    rt=x;
    t_f[rt]=fr;deep[rt]=dep;
    vis[rt]=1;

    now_b=b[dep];
    f[q[tail=1]=rt]=0;
    us u=(cnt[mx]>>5)+1; U[rt]=u;
    for(head=1;head<=tail;++head)
    {
        x=q[head];a[x]=cnt[a[x]]; 
        now_b[x]=new ui [u];
        if(head>1) for(i=0;i<u;++i)now_b[x][i]=now_b[f[x]][i];
        else for(i=0;i<u;++i)now_b[x][i]=0;
        now_b[x][a[x]>>5]|=1<<(a[x]&31);
        for(i=t[x];y=l[i].to;i=l[i].next)
        if(!vis[y]&&y!=f[x])
        {
            f[y]=x;q[++tail]=y;
        }
    }
    ++dep;
    for(int i=t[rt];y=l[i].to;i=l[i].next)
    if(!vis[y]) build(y,dep,rt);
}

us get_lca(us x,us y)
{
    while(x!=y)
    if(deep[x]>deep[y])x=t_f[x];
    else y=t_f[y];
    return x;
}

namespace kcz
{
    const int U=1<<16;
    us f[U+1];
    void init()
    {
        for(i=1;i<=U;++i)f[i]=f[i-(i&-i)]+1; 
    }
    us count(ui x)
    {
        return f[x&(U-1)]+f[x>>16];
    }
};

const int ch_top=5000000;
char ch[ch_top],*now_r=ch;
void read(int &x)
{
    while(*now_r<'-')++now_r;
    if(*now_r=='-')
    {
      for(x=*++now_r-'0';*++now_r>='0';) x=(x<<1)+(x<<3)+*now_r-'0';
      x=-x;
    }
    else
    {
        for(x=*now_r-'0';*++now_r>='0';) x=(x<<1)+(x<<3)+*now_r-'0';
    }
}

us ans;

int main()
{
    freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    int q;
    fread(ch,1,ch_top,stdin);
    read(n);read(q);
    for(i=1;i<=n;++i)read(a[i]);
    lisan();
    for(i=1;i<n;++i)
    {
        read(x);read(y);
        l[++e]=(edge){y,t[x]};t[x]=e;
        l[++e]=(edge){x,t[y]};t[y]=e;
    }
    build(1,0,0);
    kcz::init();
    while(q--)
    {
        read(x);read(y);//x=x^ans; 
        us lca=get_lca(x,y);
        X=b[deep[lca]][x];Y=b[deep[lca]][y];
        ans=0;
        for(i=0;i<U[lca];++i) ans+=kcz::count(X[i]|Y[i]);
        printf("%d\n",ans);
    }
}

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());
}

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);
   }
}