图论专项测试2

T1 收藏家

转化模型,每个人手上有一个物品,并且第 \(i\) 个人一次至多持有 \(a_i\) 个物品,每次操作一个人可以把一个物品给另一个人,问最后第1个人至多有多少个物品。

存在时间问题,可以建分层图跑网络流。源点向每个人初始的点连 \(1\) 的边, \(1\) 最终的边向汇点连 \(a_1\) 的边,中间每一次操作就在两人对应时间的点上双向连 \(1\) 边。

这样很多点都是没用的,只保留有用的点即可。

\(code:\)

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

namespace IO{
    typedef long long LL;
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
} using namespace IO;

const int NN=10010,MM=1000010;
int t,n,m,tot,a[NN],x[NN],y[NN],pre[NN];

namespace Network_Flows{
    int S,T,ql,qr,flow,q[NN],idx=1;
    int c[MM],to[MM],nex[MM],thd[NN],dis[NN],head[NN];
    void add(int a,int b,int x){
        to[++idx]=b; nex[idx]=head[a]; head[a]=idx; c[idx]=x;
        to[++idx]=a; nex[idx]=head[b]; head[b]=idx; c[idx]=0;
    }
    bool bfs(){
        memset(dis,0x3f,sizeof(dis));
        memcpy(head,thd,sizeof(thd));
        dis[S]=0; q[ql=qr=1]=S;
        while(ql<=qr){
            int u=q[ql++];
            for(int v,i=head[u];i;i=nex[i]) if(c[i])
                if(dis[v=to[i]]>dis[u]+1){
                    dis[v]=dis[u]+1;
                    q[++qr]=v;
                }
            if(u==T) return 1;
        }
        return 0;
    }
    int dfs(int u,int in){
        if(u==T) return in;
        int rest=in,go;
        for(int v,i=head[u];i;head[u]=i=nex[i]) if(c[i]){
            if(dis[v=to[i]]==dis[u]+1){
                go=dfs(v,min(rest,c[i]));
                if(go) c[i]-=go, c[i^1]+=go, rest-=go;
                else dis[v]=0;
            }
            if(!rest) break;
        }
        return in-rest;
    }
    void dinic(){
        memcpy(thd,head,sizeof(head));
        while(bfs()) flow+=dfs(S,INT_MAX);
    }
} using namespace Network_Flows;

signed main(){
    t=read();
    while(t--){
        tot=flow=0; idx=1;
        memset(pre,0,sizeof(pre));
        memset(head,0,sizeof(head));
        n=read(); m=read();
        for(int i=1;i<=n;i++) a[i]=read();
        for(int i=1;i<=m;i++) x[i]=read(), y[i]=read();
        T=++tot; S=++tot;
        for(int i=1;i<=m;i++){
            if(pre[x[i]]) add(pre[x[i]],tot+1,a[x[i]]);
            else add(S,tot+1,1);
            pre[x[i]]=++tot;
            if(pre[y[i]]) add(pre[y[i]],tot+1,a[y[i]]);
            else add(S,tot+1,1);
            pre[y[i]]=++tot;
            add(pre[x[i]],pre[y[i]],1);
            add(pre[y[i]],pre[x[i]],1);
        }
        add(pre[1],T,a[1]);
        dinic();
        write(flow,'\n');
    }
    return 0;
}

T2 旅行

可以认为每个点的决策是独立的,只有当一个点的所有出点都采取最优决策时,这个点才能采取最优决策。于是考虑单独一点如何求出最优解。设 \(u\) 点最优期望为 \(f_u\)

显然的转移方程:

\[f_u=\frac{\sum_{u\to v}f_v}{deg_u} \]

这里 \(v\) 可能因为重边被算多次。

删去一条边,会同时影响分子分母,不好直接算,可以分数规划,二分来算。

\[\begin{align*}f_u>mid\\\frac{\sum_{u\to v}f_v}{deg_u}>mid\\ \frac{\sum_{u\to v}f_v}{deg_u}-mid>0\\ \sum_{u\to v}f_v-mid\times deg_u>0 \end{align*} \]

考虑到限制,不能直接贪心,可以网络流最大权闭合子图做。

\(code:\)

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

namespace IO{
    typedef long long LL;
    typedef double DB;
    #define x first
    #define y second
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
} using namespace IO;

const int NN=1010,NM=2010,MM=20010;
int n,m,k;
vector<pair<int,int>>lim[NN];
DB f[NN];

namespace Net{
    int S,T,ql,qr,q[NM],idx=1;
    int to[MM],nex[MM],dis[NM],thd[NM],head[NM];
    DB c[MM];
    void clear(){
        idx=1; S=m+1; T=S+1;
        memset(head,0,sizeof(head));
    }
    void add(int a,int b,DB d){
        to[++idx]=b; nex[idx]=head[a]; head[a]=idx; c[idx]=d;
        to[++idx]=a; nex[idx]=head[b]; head[b]=idx; c[idx]=0;
    }
    bool bfs(){
        memset(dis,0x3f,sizeof(dis));
        memcpy(head,thd,sizeof(thd));
        dis[S]=0; q[ql=qr=1]=S;
        while(ql<=qr){
            int u=q[ql++];
            for(int v,i=head[u];i;i=nex[i]) if(c[i])
                if(dis[v=to[i]]>dis[u]+1){
                    dis[v]=dis[u]+1;
                    q[++qr]=v;
                }
            if(u==T) return 1;
        }
        return 0;
    }
    DB dfs(int u,DB in){
        if(u==T) return in;
        DB rest=in,go;
        for(int v,i=head[u];i;head[u]=i=nex[i]) if(c[i]){
            if(dis[v=to[i]]==dis[u]+1){
                go=dfs(v,min(rest,c[i]));
                if(go) c[i]-=go, c[i^1]+=go, rest-=go;
                else dis[v]=0;
            }
            if(!rest) break;
        }
        return in-rest;
    }
    DB flow(DB res=0){
        memcpy(thd,head,sizeof(head));
        while(bfs()) res+=dfs(S,INT_MAX);
        return res;
    }
} using Net::S; using Net::T;

namespace Graph{
    int idx,st[MM],to[MM],nex[MM],head[NN];
    bool vis[NN];
    void add(int a,int b,int i){
        to[++idx]=b; nex[idx]=head[a];
        head[a]=idx; st[i]=a;
    }
    bool check(int u,DB mid){
        Net::clear();
        DB sum=0;
        for(int i=head[u];i;i=nex[i])
            if(f[to[i]]-mid>1e-5) Net::add(S,i,f[to[i]]-mid) ,sum+=f[to[i]]-mid;
            else if(f[to[i]]-mid<1e-5) Net::add(i,T,mid-f[to[i]]);
        for(auto x:lim[u])
            Net::add(x.first,x.second,INT_MAX);
        return sum-Net::flow()>1e-5;
    }
    void calc(int u){
        DB l=0,r=m;
        while(r-l>1e-5){
            DB mid=(r+l)/2.0;
            if(check(u,mid)) f[u]=mid,l=mid;
            else r=mid;
        }
        f[u]+=1;
    }
    void dfs(int u){
        vis[u]=1;
        for(int i=head[u];i;i=nex[i])
            if(!vis[to[i]]) dfs(to[i]);
        if(head[u]) calc(u);
    }
} using Graph::st;

signed main(){ 
    n=read(); m=read(); k=read();
    for(int u,v,i=1;i<=m;i++)
        u=read(),v=read(), Graph::add(u,v,i);
    for(int u,v,i=1;i<=k;i++)
        u=read(),v=read(),lim[st[u]].push_back({u,v});
    Graph::dfs(1);
    printf("%.5lf\n",f[1]);
    return 0;
}

T3 字符串

\(SAM\) + \(LCT\) + 主席树。

一种暴力的做法是动态建 \(SAM\) ,维护每个节点 \(endpos\) 中最大的位置,记为 \(lst\) 。每次插入字符时用 \(min(len,pos-l+1)\) 更新链上代表的区间答案,之后暴跳 \(link\) 修改祖先链。

对于 \(SAM\) 中的 \(parent\;tree\) ,实际上有三种操作:

  • 给一个点加一个叶子节点
  • 在一对父子节点间插入一个点
  • 修改一条直链

第三个操作很像 \(access\) 。实际上,这三种操作都能通过 \(LCT\) 实现。

对第一种操作,直接连虚边即可。第二种操作需分类讨论父亲与儿子是否在同一重链( \(splay\) ) 上。具体地,先 \(splay(fa)\)\(splay(son)\) 判断所属关系,如果不在同一重链上,就让克隆节点与父亲连虚边,儿子节点成为克隆节点的右儿子。如果在同一重链上,就直接在中间加个节点。如果不清楚可以看土哥NB带图讲解。第三种操作可以在 \(access\) 后打标记修改。

对于查询答案,取 \(max\) 需分类讨论拆开 \(min\) ,需要支持常数取 \(max\) 和等差数列取 \(max\) ,时空都不太优(主要是不会),因此可以换一种思路,考虑二分答案。

\(access\) 时,直接在主席树上这条重链的 \(lst\) 处(一条重链 \(lst\) 一定相同)与链上最长 \(len\)\(max\) ,查询时二分答案,若第 \(r\)棵主席树上 \([l=mid-1,r]\) 区间内最大值不小于 \(mid\) ,则说明最终答案大于等于 \(mid\)

\(code:\)

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

namespace IO{
    typedef long long LL;
    typedef double DB;
    int read(){
        int x=0,f=0; char ch=getchar();
        while(ch>'9'||ch<'0'){ f|=(ch=='-'); ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return f?-x:x;
    } char output[50];
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'), x=-x;
        do{ output[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
    }
    void ckmax(int& x,int y){ x=x>y?x:y; }
} using namespace IO;

const int NN=800010;
int n,m,ans;
char c,s[NN];

namespace Pers_SegmentTree{
    const int TN=NN<<5;
    int tot,ext,root[NN],lc[TN],rc[TN],mx[TN];
    int clone(int v){
        int u=++tot;
        lc[u]=lc[v]; rc[u]=rc[v]; mx[u]=mx[v];
        return u;
    }
    void update(int& rt,int pos,int val,int l=1,int r=ext){
        rt=clone(rt); ckmax(mx[rt],val);
        if(l==r) return;
        int mid=l+r>>1;
        if(pos<=mid) update(lc[rt],pos,val,l,mid);
        else update(rc[rt],pos,val,mid+1,r);
    }
    int query(int rt,int opl,int opr,int l=1,int r=ext){
        if(opl>opr) return 0;
        if(l>=opl&&r<=opr) return mx[rt];
        int mid=l+r>>1,res=0;
        if(opl<=mid) ckmax(res,query(lc[rt],opl,opr,l,mid));
        if(opr>mid) ckmax(res,query(rc[rt],opl,opr,mid+1,r));
        return res;
    }
} using namespace Pers_SegmentTree;

namespace LinkCut_Tree{
    int fa[NN],val[NN],mxv[NN],lst[NN],tag[NN],son[NN][2];
    bool get(int x){ return x==son[fa[x]][1]; }
    bool isroot(int x){ return x!=son[fa[x]][0]&&x!=son[fa[x]][1]; }
    void pushup(int x){ mxv[x]=max({mxv[son[x][0]],mxv[son[x][1]],val[x]}); }
    void cov(int x,int v){ lst[x]=tag[x]=v; }
    void pushdown(int x){ if(tag[x]) cov(son[x][0],tag[x]), cov(son[x][1],tag[x]); tag[x]=0; }
    void pushall(int x){ if(!isroot(x)) pushall(fa[x]); pushdown(x); }
    void rotate(int x){
        int y=fa[x],z=fa[y],xpos=get(x),ypos=get(y);
        son[y][xpos]=son[x][xpos^1];
        if(son[x][xpos^1]) fa[son[x][xpos^1]]=y;
        if(!isroot(y)) son[z][ypos]=x; fa[x]=z;
        son[x][xpos^1]=y; fa[y]=x;
        pushup(y); pushup(x);
    }
    void splay(int x){
        pushall(x);
        while(!isroot(x)){
            int y=fa[x];
            if(!isroot(y))
                get(x)^get(y)?rotate(x):rotate(y);
            rotate(x);
        }
    }
    void insert(int f,int s,int id,int len){
        splay(f); splay(s);
        val[id]=len; lst[id]=lst[s];
        if(!isroot(f)){
            son[s][0]=id; son[id][0]=f;
            fa[id]=s; fa[f]=id;
            pushup(id); pushup(s);
        } else{
            fa[id]=f; son[id][1]=s;
            fa[s]=id;
            pushup(s); pushup(id);
        }
    }
    void access(int x,int id){
        root[id]=root[id-1];
        for(int y=0;x;x=fa[y=x]){
            splay(x);
            son[x][1]=0; pushup(x);
            update(root[id],lst[x],mxv[x]);
            son[x][1]=y; pushup(x);
        }
        splay(1); cov(1,id);
    }
} using namespace LinkCut_Tree;

namespace Suffix_Automaton{
    int oto,pre,len[NN],link[NN],to[NN][26];
    int clone(int v,int l){
        int u=++oto;
        memcpy(to[u],to[v],sizeof(to[v]));
        link[u]=link[v]; val[u]=len[u]=l;
        return u;
    }
    void extend(int c,int l){
        int p=pre,now=++oto;
        val[now]=len[now]=len[pre]+1;
        while(p&&!to[p][c])
            to[p][c]=now, p=link[p];
        if(!p) link[now]=fa[now]=1;
        else{
            int q=to[p][c];
            if(len[q]==len[p]+1) link[now]=fa[now]=q;
            else{
                int cln=clone(q,len[p]+1);
                insert(link[q],q,cln,len[p]+1);
                while(p&&to[p][c]==q)
                    to[p][c]=cln, p=link[p];
                link[q]=link[now]=fa[now]=cln;
            }
        }
        pre=now; access(now,l);
    }
} using namespace Suffix_Automaton;

int getans(int lp,int rp){
    int l=0,r=rp-lp+1,res;
    while(l<=r){
        int mid=l+r>>1;
        if(query(root[rp],lp+mid-1,rp)>=mid) res=mid,l=mid+1;
        else r=mid-1;
    }
    return res;
}

signed main(){
    scanf("%s",s+1); oto=pre=1;
    n=strlen(s+1); m=read(); ext=n+m;
    for(int i=1;i<=n;i++) extend(s[i]-'a',i);
    while(m--){
        int op=read();
        if(op==1){ cin>>c; extend((c-'a'+ans)%26,++n); }
        else{
            int l=(read()+ans-1)%n+1,r=(read()+ans-1)%n+1;
            if(l>r) swap(l,r);
            write(ans=getans(l,r),'\n');
        }
    }
    return 0;
}
posted @ 2021-12-26 21:37  keen_z  阅读(36)  评论(0编辑  收藏  举报