codeforces #366 704B 704C 704D 704E

704B DP

题意:有n个点,第i个点在位置xi,保证对于任意 i<j xi<xj 。从i点跳到左边的点j的花费为|xi - xj| + ci + bj ,跳到右边点j的花费为|xi - xj| + di + aj。
求从s点跳到T点,经过其他点恰好一次的最小花费。

化简一下,向左跳的花费为(xi+ci)+(bj-xj),向右跳的花费为(di-xi)+(xj+aj)。

可以发现每个点的花费和到达点以及来源点无关,只和跳的方向有关。并且除S和T之外的点都只有一个进入的方向和一个出去的方向。
两头点进入出去方向固定,其余点不定。可以dp。

设进入的方向为下,出去的方向为上。
在不经过S,T前,左下和右上的个数相等,经过S后左下-右上的差+1,经过T后左下-右上的差-1。
设f[i][j]表示现在到第i个点,左下有j个的最小花费。同时记录一下左下右上的差就可以转移了。

注意dp时需保证左下右上个数不同时为0,否则会出现多个环。

#include 
using namespace std;
#define N 5100
#define ll long long
int n,s,e,type;
int X[N],a[N],b[N],c[N],d[N];
ll f[N][N];
int main()
{
    scanf("%d%d%d",&n,&s,&e);
    for(int i=1;i<=n;i++)
        scanf("%d",&X[i]);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),a[i]+=X[i];
    for(int i=1;i<=n;i++)
        scanf("%d",&b[i]),b[i]-=X[i];
    for(int i=1;i<=n;i++)
        scanf("%d",&c[i]),c[i]+=X[i];
    for(int i=1;i<=n;i++)
        scanf("%d",&d[i]),d[i]-=X[i];
    memset(f,0x3f,sizeof(f));
    if(s==1)f[1][1]=d[1],type=-1;
    else if(e==1)f[1][0]=b[1],type=1;
    else f[1][1]=b[1]+d[1];
    for(int i=1;ifor(int j=0;j<=i;j++)
            if(j>0||j+type>0)
            {
                if(s==i+1)
                {
                    f[i+1][j+1]=min(f[i+1][j+1],f[i][j]+d[i+1]);
                    f[i+1][j]=min(f[i+1][j],f[i][j]+c[i+1]);    
                }
                else if(e==i+1)
                {
                    if(j)f[i+1][j-1]=min(f[i+1][j-1],f[i][j]+a[i+1]);
                    f[i+1][j]=min(f[i+1][j],f[i][j]+b[i+1]);
                }
                else
                {
                    f[i+1][j+1]=min(f[i+1][j+1],f[i][j]+d[i+1]+b[i+1]);
                    if(j)f[i+1][j]=min(f[i+1][j],f[i][j]+d[i+1]+a[i+1]);
                    if(j+type)f[i+1][j]=min(f[i+1][j],f[i][j]+c[i+1]+b[i+1]);
                    if(j&&j+type)f[i+1][j-1]=min(f[i+1][j-1],f[i][j]+c[i+1]+a[i+1]);
                }
            }
            if(s==i+1)type--;
            if(e==i+1)type++;
    }   
    printf("%I64d\n",f[n][0]);
    return 0;   
}

704C dp

题意:有m个布尔值,n个集合。每个集合元素个数 <=2 ,每个值或该值取反的出现次数和 <=2 。将每一个值赋值,要求使存在为1的值的集合个数为奇数,求方案数。

对于有两个元素的集合在两个元素之间连一条边。由于每个点度数最多为2,那么这个图一定是一堆链和一堆环。对于链和环分别dp,最后再放在一起dp一下,这题就做完了。。。。。。

么?啊,为什么这么多特判呀。。。。。活活写出3K+。。。。好吧,是我写得太挫。

#include 
using namespace std;
#define mod 1000000007 
#define ll long long 
#define N 110000 
int n,m,tot,neg;
int head[N],nex[N<<1],to[N<<1],val[N<<1];
int vis[N],cnt[N],a[N],b[N],use[N],u1[N<<1];
int st[N][2],top,ans,du[N],f[N][2][2],g[N][2][2][2],d[N][2];
int e[N],sum;
void add(int x,int y,int z)
{
    tot++;
    nex[tot]=head[x];head[x]=tot;
    to[tot]=y;val[tot]=z;
}
void dfs(int x)
{
    if(use[x])return;
    use[x]=1;
    for(int i=head[x];i;i=nex[i])
        if(!u1[i])
        {
            e[++sum]=i;
            u1[i]=u1[i^1]=1;
            dfs(to[i]);
        }
}
int get(int v,int x,int y)
{
    if(to[v]!=abs(b[val[v]]))swap(x,y);
    int t=val[v];
    if(a[t]<0)x^=1;
    if(b[t]<0)y^=1;
    return x|y;
}
int main()
{
//  freopen("tt.in","r",stdin);
    scanf("%d%d",&n,&m);tot=1;
    int ans=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&cnt[i]);
        if(cnt[i]==1)
        {
            scanf("%d",&a[i]);
            if(vis[abs(a[i])])
            {
                st[top][0]=st[top][1]=0;
                top--;(ans*=2)%=mod;
                continue;
            }
            vis[abs(a[i])]=a[i];
            if(!du[abs(a[i])])st[++top][0]=st[top][1]=1;
        }
        if(cnt[i]==2)
        {
            scanf("%d%d",&a[i],&b[i]);
            if(a[i]==-b[i])
            {   
                neg++,vis[abs(a[i])]=1;
                (ans*=2)%=mod;
            }
            else
            {   
                int a1=abs(a[i]),b1=abs(b[i]);
                if(a1==b1)vis[a1]=1,st[++top][0]=st[top][1]=1;
                else
                {
                    add(a1,b1,i);add(b1,a1,i);
                    du[a1]++;du[b1]++;
                    if(vis[a1])st[top][0]=st[top][1]=0,top--;
                    if(vis[b1])st[top][0]=st[top][1]=0,top--;
                }
            }
        }
    }
    for(int now=1;now<=m;now++)
        if(!use[now]&&du[now]!=2)
        {
            if(!du[now]&&!vis[now])(ans*=2)%=mod;
            else if(!du[now])continue;
            else
            {
                sum=0;dfs(now);
                for(int i=1;i<=sum;i++)
                    memset(f[i],0,sizeof(f[i]));
                if(vis[now])
                {
                    if(vis[now]<0)
                    {
                        f[1][0][get(e[1],0,0)^1]++;
                        f[1][1][get(e[1],0,1)^1]++;
                        f[1][0][get(e[1],1,0)]++;
                        f[1][1][get(e[1],1,1)]++;
                    }
                    else
                    {
                        f[1][0][get(e[1],0,0)]++;
                        f[1][1][get(e[1],0,1)]++;
                        f[1][0][get(e[1],1,0)^1]++;
                        f[1][1][get(e[1],1,1)^1]++;
                    }
                }
                else
                {
                    f[1][0][get(e[1],0,0)]++;
                    f[1][1][get(e[1],0,1)]++;
                    f[1][0][get(e[1],1,0)]++;
                    f[1][1][get(e[1],1,1)]++;
                }
                for(int i=1;ifor(int j=0;j<=1;j++)
                        for(int k=0;k<=1;k++)
                            for(int t=0;t<=1;t++)
                            {
                                (f[i+1][j][t^get(e[i+1],k,j)]+=f[i][k][t])%=mod;
                            }
                top++;
                for(int i=0;i<=1;i++)
                {
                    for(int j=0,t;j<=1;j++)
                    {
                        if(t=vis[to[e[sum]]])
                            (st[top][j^i^(t<0)]+=f[sum][i][j])%=mod;
                        else (st[top][j]+=f[sum][i][j])%=mod;
                    }
                }
            }
        }
    for(int now=1;now<=m;now++)
        if(!use[now]&&du[now]==2)
        {
            sum=0;dfs(now);
            for(int i=1;i<=sum;i++)
                memset(g[i],0,sizeof(g[i]));
            g[1][0][0][get(e[1],0,0)]++;
            g[1][0][1][get(e[1],0,1)]++;
            g[1][1][0][get(e[1],1,0)]++;
            g[1][1][1][get(e[1],1,1)]++;
            for(int i=1;i1;i++)
                for(int j=0;j<=1;j++)
                    for(int k=0;k<=1;k++)
                        for(int t=0;t<=1;t++)
                            for(int w=0;w<=1;w++)
                            {
                                (g[i+1][j][t][w^get(e[i+1],k,t)]+=g[i][j][k][w])%=mod;
                            }
            top++;
            for(int i=0;i<=1;i++)
                for(int j=0;j<=1;j++)
                    for(int k=0;k<=1;k++)
                    {
                        (st[top][get(e[sum],j,i)^k]+=g[sum-1][i][j][k])%=mod;
                    }

        }
    d[0][0]=ans;
    for(int i=1;i<=top;i++)
        for(int j=0;j<=1;j++)
            for(int k=0;k<=1;k++)
                (d[i][j^k]+=(ll)d[i-1][j]*st[i][k]%mod)%=mod;

    if(neg&1)printf("%d\n",d[top][0]);
    else printf("%d\n",d[top][1]);
    return 0;
}

704D 上下界网络流

题意:网格图上n个点,每个点有x,y坐标。给每个点红蓝染色,染成红色或蓝色有不同花费。给出m个限制,有两种限制:所有x坐标为l的点中红色点与蓝色点的差 <=d ,所有y坐标为l的点中红色点与蓝色点的差 <=d 。求染色的最小花费,并输出方案,无解输-1。

《论信仰的重要性》
10^5个点,10^6条边的网络流能跑出来我也是很佩服呀。。。。
upd:这玩意其实是一个类似二分图的东西,所以复杂度大概n根号。

网络流,左面对于每一个横坐标建一个点,左面对于每一个纵坐标建一个点。
对于每个点把相应的横坐标和纵坐标之间连流量上界1下界0的边。

对于每个限制,计算出对应横纵坐标点流量的上下界。
跑有上下界最大流。跑出来的答案为价格较小的颜色的个数。

#include 
using namespace std;
#define N 110000
#define M 2100000
#define ll long long 
const int inf=1e9;
int n,m,r,b;
int S,T,SS,TT,num,tot,ans,sum1;
int a[2][N],cnt[2],X[N],Y[N],sum[2][N];
int head[N<<1],nex[M],to[M],val[M];
int mx[2][N],mn[2][N],du[N<<1];
int deep[N<<1],pos[N];
queue<int>que;
int get(int x,int y)
{
    return lower_bound(a[y]+1,a[y]+1+cnt[y],x)-a[y];
}
void add(int x,int y,int z)
{
    tot++;
    nex[tot]=head[x];head[x]=tot;
    to[tot]=y;val[tot]=z;
}
int bfs(int S,int T)
{
    memset(deep,-1,sizeof(deep));
    while(!que.empty())que.pop();
    deep[S]=0;que.push(S);
    while(!que.empty())
    {
        int tmp=que.front();
        que.pop();
        for(int i=head[tmp];i;i=nex[i])
            if(deep[to[i]]==-1&&val[i])
            {
                deep[to[i]]=deep[tmp]+1;
                if(to[i]==T)return 1;
                que.push(to[i]);
            }
    }
    return 0;
}
int dfs(int x,int mv,int T)
{
    if(x==T)return mv;
    int tmp=0;
    for(int i=head[x];i;i=nex[i])
        if(deep[to[i]]==deep[x]+1&&val[i])
        {
            int t=dfs(to[i],min(val[i],mv-tmp),T);
            if(!t)deep[to[i]]=-1;
            tmp+=t;val[i]-=t;
            val[i^1]+=t;
            if(tmp==mv)break;
        }
    return tmp;
}
int main()
{
    //freopen("tt.in","r",stdin);
    scanf("%d%d",&n,&m);
    scanf("%d%d",&r,&b);tot=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&X[i],&Y[i]);
        a[0][++cnt[0]]=X[i];
        a[1][++cnt[1]]=Y[i];
    }
    for(int i=0;i<=1;i++)
    {   
        sort(a[i]+1,a[i]+1+cnt[i]);
        cnt[i]=unique(a[i]+1,a[i]+1+cnt[i])-a[i]-1;
    }
    num=cnt[0]+cnt[1];
    S=++num;T=++num;SS=++num;TT=++num;
    for(int i=1;i<=n;i++)
    {
        X[i]=get(X[i],0);sum[0][X[i]]++;
        Y[i]=get(Y[i],1);sum[1][Y[i]]++;
        add(X[i],Y[i]+cnt[0],1);pos[i]=tot;
        add(Y[i]+cnt[0],X[i],0);
    }
    for(int i=0;i<=1;i++)
        for(int j=1;j<=cnt[i];j++)
            mx[i][j]=sum[i][j];
    for(int i=1,t,l,d;i<=m;i++)
    {
        scanf("%d%d%d",&t,&l,&d);t--;
        int t1=get(l,t);
        if(a[t][t1]!=l)continue;
        mx[t][t1]=min(mx[t][t1],(d+sum[t][t1])/2);
        mn[t][t1]=max(mn[t][t1],(sum[t][t1]-d+1)/2);
        if(mx[t][t1]puts("-1");return 0;}
    }
    for(int i=1;i<=cnt[0];i++)
    {
        if(mx[0][i]!=mn[0][i])
        {   
            add(S,i,mx[0][i]-mn[0][i]);
            add(i,S,0);
        }
        du[i]+=mn[0][i];
        du[S]-=mn[0][i];
    }
    for(int i=1;i<=cnt[1];i++)
    {
        int t=i+cnt[0];
        if(mx[1][i]!=mn[1][i])
        {           
            add(t,T,mx[1][i]-mn[1][i]);
            add(T,t,0);
        }
        du[T]+=mn[1][i];
        du[t]-=mn[1][i];
    }
    for(int i=1;i<=T;i++)
        if(du[i])
        {
            if(du[i]>0)add(SS,i,du[i]),add(i,SS,0),sum1+=du[i];
            else add(i,TT,-du[i]),add(TT,i,0);
        }
    add(T,S,inf);add(S,T,0);
    while(bfs(SS,TT))
        ans+=dfs(SS,inf,TT);
    if(ans!=sum1)
        {puts("-1");return 0;}
    ans=0;
    while(bfs(S,T))
        ans+=dfs(S,inf,T);
    printf("%I64d\n",(ll)ans*min(r,b)+(ll)(n-ans)*max(r,b));
    for(int i=1;i<=n;i++)
    {
        if(!val[pos[i]])putchar(r'r':'b');
        else putchar(r>b ? 'r':'b');
    }
    return 0;
}

704E 树链剖分 splay

题意:在一个n个点的树上,有m个钢铁侠。第i个钢铁侠在ti时刻出现,从vi走到ui,速度为ci条边每秒,经过点不需要时间。求两个钢铁侠第一次相撞的时间,没有输-1。

先考虑在链上的情况。维护ans代表目前最早相撞时间。将插入和删除事件按时间排序。

对于插入,用该点的前驱和后继与插入点的相撞时间更新ans,
对于删除,用该点的前驱后继相撞时间更新ans

用两个点更新一个点的条件是这两个点的相撞时间大于等于当前时间且小于等于两点删除时间的最小值。
假设在这段时间中两点间插入了另一个点,那么插入的点与两点中任意一点的相撞时间都小于两点的相撞时间。因此可以用两点的相撞时间更新答案。
由于在最终答案中相撞的点一定在某时刻位置相邻,因此这样可以统计到所有答案。

如果当前时间大于ans,退出循环。
因为ans为出现过的点中最早碰撞时间,在当前时间小于ans时两点之间的位置关系不会发生改变。时间 >ans 时位置关系会发生改变。因此可以用平衡树维护位置关系。

对于树上的情况,将一个钢铁侠拆成log个,树链剖分对于每一段分别计算ans。

时间复杂度O(nlog^2n),空间复杂度O(nlogn)
感觉卡空间,可能是错觉。
顺便一提,在splay上找前驱后继时需要注意权值相等的点。
手写平衡树就是快,而且并不长,码长和rk1差不到0.1k。好像其他人都写了auto,并不会用。

#include 
using namespace std;
#define N 110000
#define M 5000000
#define inf 1e9
#define eps 1e-9
#define which(x) ch[fa[x]][1]==x
int n,m,tot,root;
int head[N],nex[N<<1],to[N<<1];
int fa[N],son[N],top[N],size[N],deep[N],used[N];
int p[N],ft[N],ch[N][2];
double ans,beg[N],tim,c[N],ove[N],cv[N];
int tt[N],tv[N],tu[N],tlc[N],cnt[N],en[N];
int l1,r1;
void dfs1(int x,int y)
{
    ft[x]=y;size[x]=1;
    deep[x]=deep[y]+1;
    for(int i=head[x];i;i=nex[i])
        if(to[i]!=y)
        {
            dfs1(to[i],x);
            size[x]+=size[to[i]];
            son[x]=size[to[i]]>size[son[x]] ? to[i]:son[x];
        }
}
void dfs2(int x,int y,int tp)
{
    top[x]=tp;
    if(son[x])dfs2(son[x],x,tp);
    for(int i=head[x];i;i=nex[i])
        if(to[i]!=y&&to[i]!=son[x])
            dfs2(to[i],x,to[i]);
}
void add(int x,int y)
{
    tot++;
    nex[tot]=head[x];head[x]=tot;
    to[tot]=y;
}
struct node
{
    int pos,bel;
    double v;
    bool type,c;
    friend bool operator < (const node &r1,const node &r2)
    {
        if(fabs(r1.v-r2.v)return r1.type>r2.type;
        return r1.vM];
double get(int x,int y,int lc,int type,double c)
{
    if(type)
        return (deep[x]+deep[y]-deep[lc]*2)/c;
    return (deep[x]-deep[y])/c;
}
double cal(int x)
{return p[x]+c[x]*(tim-beg[x]);}
void cal(int x,int y)
{
    double ntim=min(ove[x],ove[y]);
    double t=(cal(y)-cal(x))/(c[x]-c[y]);
    if(fabs(cal(y)-cal(x))0;
    if(t>-eps&&tif(!x)return;
    if(y==x)pre(ch[x][0],y);
    else if(cal(x)<=cal(y))
    {
        l1=x;
        pre(ch[x][1],y);
    }
    else pre(ch[x][0],y);
}
void rotate(int x)
{
    int y=fa[x],k=which(x);
    ch[y][k]=ch[x][k^1];
    ch[x][k^1]=y;
    ch[fa[y]][which(y)]=x;

    fa[x]=fa[y];fa[y]=x;
    fa[ch[y][k]]=y;
}
void splay(int x,int tar)
{
    while(fa[x]!=tar)
    {
        int y=fa[x];
        if(fa[y]==tar)rotate(x);
        else
        {
            if(which(x)^which(y))rotate(x);
            else rotate(y);
            rotate(x);
        }
    }
    if(!tar)root=x;
}
int nxt1(int x)
{
    splay(x,0);
    x=ch[x][1];
    while(ch[x][0])x=ch[x][0];
    return x;
}
int pre1(int x)
{
    splay(x,0);
    x=ch[x][0];
    while(ch[x][1])x=ch[x][1];
    return x;
}
int cl(int x)
{ch[x][0]=ch[x][1]=fa[x]=0;}
int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i"%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs1(1,0);
    dfs2(1,0,1);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%lf%d%d",&tt[i],&cv[i],&tv[i],&tu[i]);
        int v=tv[i],u=tu[i];
        while(top[v]!=top[u])
        {
            if(deep[top[v]]2;
            v=ft[top[v]];
        }
        tlc[i]=deep[v]v:u;   
        cnt[top[v]]+=2;
    }
    for(int i=1;i<=n;i++)cnt[i]+=cnt[i-1],en[i]=cnt[i-1];
    for(int i=1,t,v,u;i<=m;i++)
    {
        t=tt[i],v=tv[i];u=tu[i];
        int lc=tlc[i],v1=v,type=0,st,ed;
        bool c=0;
        while(top[v]!=top[u])
        {
            if(deep[top[v]]1,c^=1;
            st=v,ed=ft[top[v]];
            if(type)swap(st,ed);
            a[++en[top[v]]]=(node){st,i,t+get(v1,st,lc,type,cv[i]),1,c};
            a[++en[top[v]]]=(node){ed,i,t+get(v1,ed,lc,type,cv[i]),0,c};
            v=ft[top[v]];
        }
        if(deep[v]1,c^=1;
        st=v;ed=u;
        if(type)swap(st,ed);
        a[++en[top[v]]]=(node){st,i,t+get(v1,st,lc,type,cv[i]),1,c};
        a[++en[top[v]]]=(node){ed,i,t+get(v1,ed,lc,type,cv[i]),0,c};
    }
    c[m+1]=-inf;c[m+2]=inf;beg[m+1]=beg[m+2]=-inf;
    ans=inf;
    for(int i=1,t;i<=n;i++)
        if(!used[t=top[i]])
        {
            used[t]=1;
            p[m+1]=p[m+2]=deep[i];
            sort(a+cnt[t-1]+1,a+cnt[t]+1);
            cl(m+2);cl(m+1);
            fa[m+1]=m+2;ch[m+2][0]=m+1;root=m+2;
            for(int j=cnt[t-1]+1;j<=cnt[t];j++)
                if(a[j].type==0)
                    ove[a[j].bel]=a[j].v;
            int tot=0;
            for(int j=cnt[t-1]+1,pos;j<=cnt[t];j++)
            {
                tim=a[j].v;int bel=a[j].bel;
                if(tim>ans)break;
                if(a[j].type==1)
                {
                    p[bel]=deep[a[j].pos];
                    c[bel]=a[j].c ? cv[bel]:-cv[bel];
                    beg[bel]=tim;
                    l1=m+1;cl(bel);
                    pre(root,bel);r1=nxt1(l1);
                    cal(bel,l1);cal(bel,r1);
                    splay(l1,0);splay(r1,l1);
                    ch[r1][0]=bel;fa[bel]=r1;
                    tot++;
                }
                else
                {
                    l1=pre1(bel),r1=nxt1(bel);
                    cal(l1,r1);
                    splay(l1,0);splay(r1,l1);
                    ch[r1][0]=fa[bel]=0;
                    tot--;
                }
            }   
        }
    if(fabs(ans-inf)<1e3)puts("-1");
    else printf("%.6lf\n",ans);
    return 0;
}

这场cf大概填完了。觉得题还是不错的。

你可能感兴趣的:(dp,树链剖分,网络流,平衡树)