屠论与网络瘤做题记录

CF1765A Access Levels

解法

考虑两个文件 \(u,v\) 能合并为一组,当且仅当能够访问 \(u\) 的人的集合和能够访问 \(v\) 的人的集合有包含关系。如果存在某两个人 \(x,y\) 满足 \(x\) 只能访问 \(u\)\(y\) 只能访问 \(v\),则 \(c_v>b_{x,s_u}\ge c_u\)\(c_u>b_{y,s_u}\ge c_v\),互相矛盾。

所以可以把所有文件按照能访问其的人数排序,然后如果能访问 \(u\) 的人是能访问 \(v\) 的人的子集(且 \(u\) 排在 \(v\) 的前面),则从 \(u\)\(v\) 连一条边。

此时考虑从前往后考虑每份文件,选择是否在某一组内加文件的过程。设当前组内文件中能被最多人访问的文件为 \(u\),则后面一定需要选择 \(u\) 连向的某份文件 \(v\),显然 \(v\) 满足和该组内其他文件能同在一组内。问题等价于在一张 DAG 上找尽可能少的简单路径覆盖整张图,上最小路径覆盖即可。

构造方案时,由于一条路径上的 \(c\) 需要严格单增,直接在路径上顺次确定每个 \(c\),例如将路径上第 \(i\) 个点的 \(c\) 值设为 \(i+1\) 即可。\(s,b\) 不难构造。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=510;
const int maxd=1010;
const int maxm=260000;
const int INF=1145141919;
int n,m,i,j,k,u,v,t,a,tp,te=1;
int pe[maxn],st[maxn],cnt[maxn];
int num[maxn],rnk[maxn],col[maxn]; 
int h[maxd],d[maxd],c[maxd],to[maxm],nxt[maxm];
char s[maxn]; bool fl[maxm],vis[maxn],ac[maxn][maxn];
queue<int> q;
inline bool cmp(const int &x,const int &y){return cnt[x]<cnt[y];}
inline void cmax(int &x,int y){if(x<y) x=y;}
inline void Add(int x,int y){
    to[++te]=y; fl[te]=1; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==t) return 1;
    int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        if(!dinic(to,1)) continue;
        --f; ++fl; ::fl[lp]=0; ::fl[lp^1]=1;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
int main(){
    scanf("%d%d",&n,&m); t=m<<1|1;
    for(i=1;i<=m;++i) pe[i]=i;
    for(i=1;i<=n;++i){
        scanf("%s",s+1); 
        for(j=1;j<=m;++j){
            u=(s[j]=='0');
            ac[j][i]=u; cnt[j]+=u;
        }
    }
    sort(pe+1,pe+m+1,cmp);
    for(i=1;i<m;++i){
        u=pe[i]; tp=0;
        for(k=1;k<=n;++k) if(ac[u][k]) st[++tp]=k;
        for(j=i+1;j<=m;++j){
            v=pe[j];
            for(k=1;ac[v][st[k]]&&(k<=tp);++k);
            if(k>tp) Add(i,j+m);
        }
    }
    for(i=1;i<=m;++i) Add(0,i),Add(i+m,t); a=m;
    for(d[0]=1;;){
        memset(d+1,0,t<<2);
        memcpy(c,h,(t+1)<<2);
        for(q.push(0);!q.empty();){
            u=q.front(); q.pop();
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=to[i]; if(d[v]) continue;
                d[v]=d[u]+1; q.push(v);
            }
        } 
        if(!d[t]) break;
        a-=dinic(0,INF);
    }
    printf("%d\n",a);
    for(i=1,v=0;i<=m;++i){
        if(col[pe[i]]) continue;
        col[pe[i]]=++v; num[pe[i]]=k=2;
        for(u=i;;){
            for(j=h[u];(fl[j]||!to[j])&&j;j=nxt[j]);
            if(j){
                u=to[j]-m; 
                col[pe[u]]=v;
                num[pe[u]]=++k;
            }
            else break;
        }
    }
    for(i=1;i<=m;++i) printf("%d ",col[i]); putchar('\n');
    for(i=1;i<=m;++i) printf("%d ",num[i]); putchar('\n');
    for(i=1;i<=n;++i){
        for(j=1;j<=a;++j) rnk[j]=1;
        for(j=1;j<=m;++j){
            if(ac[j][i]) continue;
            cmax(rnk[col[j]],num[j]);
        }
        for(j=1;j<=a;++j) printf("%d ",rnk[j]); putchar('\n');
    }
    return 0;
}

CF1761E Make It Connected

解法

显然只有一个连通块时无需操作,存在孤点时只需要操作孤点。

首先考虑存在一个连通块不是团的情况。此时对于其度数最小的点 \(u\)(这一步极其绝),如果 \(u\) 连向的(去掉 \(u\) 所形成的)某个连通块 \(s\) 满足 \(s\) 内的点全部连向 \(u\),则 \(u\) 的度数至少为 \(|s|\),而 \(s\) 内没有点的度数大于 \(|s|\)。如果 \(u\) 的度数和 \(s\) 内每个点的度数均为 \(|s|\),则 \(u\) 才会满足“度数最小”的条件,然而此时整个连通块成为了团。所以对 \(u\) 进行操作后,原连通块内所有点还在同一个连通块中,其他连通块内的点也均和 \(u\) 相连。所以只需要对 \(u\) 进行操作。

考虑整个图是两个团的情况,此时对某个点进行操作时一定会使得该点从这个团到另一个团,所以操作次数为较小团大小。

考虑整个图的连通块数量多于两个的情况。此时从任意两个连通块 \(s_1,s_2\) 内分别选一个点,则 \(s_1,s_2\) 一定会通过第三个连通块联通,其他连通块之间也会连通,故只需要操作两次。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=4010;
int t,n,i,j,u,v,a,c;
int d[maxn],fa[maxn],siz[maxn];
char s[maxn];
int Find(int p){
    if(p==fa[p]) return p;
    return fa[p]=Find(fa[p]);
}
int main(){
    scanf("%d",&t); 
    siz[0]=d[0]=1145141919;
    st:
    while(t--){
        scanf("%d",&n); 
        memset(d+1,0,n<<2);
        for(i=1;i<=n;++i){
            fa[i]=i;
            siz[i]=1;
        }
        for(i=1;i<=n;++i){
            scanf("%s",s+1);
            for(j=i+1;j<=n;++j){
                if(s[j]=='1'){
                    ++d[i]; ++d[j];
                    u=Find(i); v=Find(j);
                    if(u==v) continue;
                    if(siz[u]<siz[v]) swap(u,v);
                    fa[v]=u; siz[u]+=siz[v]; siz[v]=0; 
                }
            }
        }
        for(i=1,c=0;i<=n;++i) if(siz[i]) ++c;
        if(c==1){
            printf("0\n");
            goto st;
        }
        for(i=1,a=j=0;i<=n;++i){
            if(siz[i]){
                if(siz[i]==1){
                    printf("1\n%d\n",i);
                    goto st;
                }
                if(siz[i]<siz[a]) a=i;
            }
            if(d[i]!=siz[Find(i)]-1) if(d[j]>d[i]) j=i;
        }
        if(j) printf("1\n%d\n",j);
        else if(c!=2){
            printf("2\n%d ",a);
            for(i=1;i<=n;++i){
                if(fa[i]!=a){
                    printf("%d\n",i);
                    break;
                }
            }
        }
        else{
            printf("%d\n",siz[a]);
            for(i=1;i<=n;++i) if(fa[i]==a) printf("%d ",i);
            putchar('\n');
        }
    }
    return 0;
}

CF1738F Connectivity Addicts

解法

考虑某个点集内如果同时包括了度数最大的点 \(u\) 和其所有邻点,则该点集一定合法。证明:考虑在只包含 \(u\)\(u\) 的邻点的点集 \(S\) 中不断加入新点的过程。最初该点集大小为 \(d_u+1\),而 \(u\) 的邻点的度数一定不会大于 \(d_u\),故 \(s_S\le d_u(d_u+1)<(d_u+1)^2=n_S^2\)。加入一个新点时,\(n_S^2\) 至少增加 \(2(d_u+1)+1\),而 \(s_S\) 增加不超过 \(d_u\),则加入任何点时仍然满足 \(s_S<n_S^2\)

此时我们可以重复执行这个过程:每次找一个未确定颜色,度数最大的点;然后查询其所有邻点,如果某个邻点已经确定颜色则将该店和之前查询的所有邻点均赋为该点颜色(显然这个颜色的点集仍然会合法);否则将该点和其所有邻点赋为一种新颜色。此时对于上述两种情况,我们都可以在若干次询问之后确定不少于询问次数个点的颜色,故总询问次数不超过 \(n\) 次。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
int t,n,i,j,u,v,w,c,d[maxn],b[maxn],pe[maxn],col[maxn];
inline bool cmp(const int &x,const int &y){return d[x]>d[y];}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n); c=0;
        for(i=1;i<=n;++i){
            scanf("%d",d+i);
            pe[i]=i; col[i]=0;
        }
        sort(pe+1,pe+n+1,cmp);
        for(i=1;i<=n;++i){
            u=pe[i]; 
            if(col[u]) continue; b[1]=u;
            for(j=1,w=0;j<=d[u];++j){
                printf("? %d\n",u);
                fflush(stdout);
                scanf("%d",&v); b[j+1]=v;
                if(w=col[v]) break;
            }
            if(!w) w=++c;
            while(j) col[b[j--]]=w;
        }
        printf("! ");
        for(i=1;i<=n;++i) printf("%d ",col[i]);
        putchar('\n'); fflush(stdout);
    }
    return 0;
}

CF757F Team Rocket Rises Again

解法

什么叫支配树/灭绝树呢哼哼啊啊啊啊啊啊啊啊啊啊啊啊啊呃啊啊啊啊啊啊啊啊啊啊啊啊啊嗯啊啊啊啊啊啊啊啊啊啊啊啊

\(dis_u\)\(s\)\(u\) 的最短路长度,则求出 \(dis\) 之后只需要保留满足 \(dis_v=dis_u+w\)有向边,则整个图变成了一个有向无环图。此时问题变成了删除某个点之后和 \(u\) 不连通的点(原来连通)的数量的最大值。可以使用支配树,但是我不会。

考虑某个入度为 \(1\) 的点 \(u\) 和其前驱 \(v\)。此时选择 \(u\) 能增加的不与 \(s\) 连通的点一定是选择 \(v\) 对应的点的真子集,故我们一定不会选择 \(u\),而选择 \(v\) 可以代替选择 \(u\),此时可以把 \(u\)\(v\) 的合并,同时合并对应的入边和出边。注意需要在新图上边进行拓扑排序边合并,因为合并时可能出现新的可以合并的点;该点对应的所有前驱可能由多个合并成了一个,而这些前驱的拓扑序更小,合并操作一定在遍历到该点之前已经进行。

最后整个图内非 \(s\) 的点中,如果某个点入度为 \(1\)\(s\) 一定连向它。此时如果删去某个非 \(s\) 的点,\(s\) 仍然会和其他点连通。故最终答案是合并成同一个点内的点的数量的最大值。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=200010;
const int maxm=600010;
int n,m,s,i,u,v,w,t=1,f;
int to[maxm],ed[maxm],nxt[maxm],fa[maxn];
int h[maxn],p[maxn],hf[maxn],dg[maxn],siz[maxn];
struct edge{int to,nxt;}F[maxm>>1];
long long d[maxn]; bool vis[maxn];
priority_queue<pair<long long,int> > q;
queue<int> Q; 
int main(){
    scanf("%d%d%d",&n,&m,&s);
    while(m--){
        scanf("%d%d%d",&u,&v,&w);
        to[++t]=v; ed[t]=w; nxt[t]=h[u]; h[u]=t;
        to[++t]=u; ed[t]=w; nxt[t]=h[v]; h[v]=t;
    }
    memset(d+1,0x3f,n<<3); 
    d[s]=0; q.push(make_pair(0,s));
    while(!q.empty()){
        u=q.top().second; q.pop();
        if(vis[u]) continue; vis[u]=1;
        for(i=h[u];i;i=nxt[i]){
            v=to[i]; w=ed[i];
            if(d[v]>d[u]+w){
                d[v]=d[u]+w;
                q.push(make_pair(-d[v],v));
            }
        }
    }
    for(i=2;i<=t;++i){
        u=to[i]; v=to[i^1];
        if(d[v]==d[u]+ed[i]){
            F[++f]={v,hf[u]};
            hf[u]=f; ++dg[v];
        }
    }
    for(Q.push(s),fa[s]=s;!Q.empty();){
        u=Q.front(); Q.pop(); 
        if(fa[u]!=s) ++siz[fa[u]];
        else fa[u]=u; siz[u]=1;
        for(i=hf[u];i;i=F[i].nxt){
            v=F[i].to;
            if(fa[v]&&fa[v]!=fa[u]) fa[v]=s;
            else fa[v]=fa[u];
            if(!(--dg[v])) Q.push(v);
        }
    }
    siz[s]=0;
    printf("%d",*max_element(siz+1,siz+n+1));
    return 0;
}

Gym100212I Trade

解法

直接考虑每条边对两边的点的度数均多了 \(1\) 的贡献,则本题需要跑一遍带下界的网络流。(如果只有一边带下界则可以将其借助最大流的性质转化成普通最大流)不过有更简单的方法:考虑加完所有边之后逐渐删边的过程,每条边被删时会对两边的点的度数造成 \(-1\) 的贡献,则问题变成了一个普通的多重匹配问题。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=610;
const int maxm=181300;
const int INF=1145141919;
int n,m,p,i,u,v,t,a,te=1;
int h[maxn],c[maxn],d[maxn];
int to[maxm],fl[maxm],nxt[maxm];
queue<int> q;
inline void Add(int x,int y,int f){
    to[++te]=y; fl[te]=f; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==t) return f;
    int to,fl=0,nw;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        nw=dinic(to,min(f,::fl[lp]));
        if(!nw) continue; f-=nw; fl+=nw;
        ::fl[lp]-=nw; ::fl[lp^1]+=nw;
        if(!f) break; 
    }
    if(!fl) d[p]=0; return fl;
}
int main(){
    scanf("%d%d%d",&n,&m,&p); 
    a=p; t=n+m+1;
    for(i=1;i<=p;++i){
        scanf("%d%d",&u,&v);
        Add(u,v+n,1); 
        ++d[u]; ++d[v+n];
    }
    for(i=1;i<t;++i){
        if(d[i]<2){
            printf("-1");
            return 0;
        }
    }
    for(i=1;i<=n;++i) Add(0,i,d[i]-2);
    for(i=1;i<=m;++i) Add(i+n,t,d[i+n]-2);
    for(d[0]=1;;){
        memset(d+1,0,t<<2);
        memcpy(c,h,(t+1)<<2);
        for(q.push(0);!q.empty();){
            u=q.front(); q.pop();
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=to[i]; if(d[v]) continue;
                d[v]=d[u]+1; q.push(v);
            }
        }
        if(!d[t]) break; a-=dinic(0,INF); 
    }
    printf("%d\n",a);
    for(i=1;i<=p;++i) if(fl[i<<1]) printf("%d ",i);
    return 0;
}

AGC029F Construction of a tree

解法

考虑有若干个点集 \(S_1,S_2,\cdots,S_k\) 满足 \(|\bigcup_{i=1}^k S_i|\le k\),则无论如何连边都需要在不超过 \(k\) 个点之间连 \(k\) 条边,一定会存在环。所以能构造出一棵树的必要条件是对于每个点集组成的集合 \(P\),一定满足 \(|\bigcup_{S\in P}S|>|P|\)

非常像霍尔定理的形式,考虑先建一张二分图,左部为 \(n-1\) 个点集,右部为 \(n\) 个点,每个点集向所属的点连边;则上述条件成立时一定要有该张二分图最大匹配数为 \(n-1\)。匹配数为 \(n-1\) 对应了霍尔定理,但是霍尔定理允许出现 \(|\bigcup_{S\in P}S|=|P|\) 的情况。

根据最大匹配构造答案时,可以在每个点集中选择对应的匹配点和某个相连的非匹配点。此时可以从唯一失配的右部点 \(r\) 出发,每次找 \(r\) 所连接到的 未被访问的 左部点 \(p\),则边 \(p\) 对应了 \(r\)\(p\) 的匹配点 \(u\) 之间的边;此时再从 \(p\) 出发执行这样的过程,dfs 下去即可。显然 \(r\) 之外的每个右部点只会从匹配的左部点访问到,一定不会建出环。

考虑从 \(r\) 不能遍历到所有点的情况。此时便历到的所有点中右部点一定比左部点多一个,未遍历到的左部点一定只和未遍历到的右部点数量相同,出现了 \(|\bigcup_{S\in P}S|=|P|\) 的情况。此时 \(\forall P,|\bigcup_{S\in P}S|>|P|\) 成立时一定能建出整棵树,故有解的情况一定不会判成无解。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxd=100010;
const int maxn=200010;
const int maxm=800010;
const int INF=1145141919;
int n,i,u,v,t,a,te=1,h[maxn],d[maxn],c[maxn];
int to[maxm],nxt[maxm],ut[maxd],vt[maxd];
bool fl[maxm],vis[maxn]; 
queue<int> q;
inline void Add(int x,int y){
    to[++te]=y; fl[te]=1; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==t) return 1;
    int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        if(!dinic(to,1)) continue;
        ::fl[lp]=0; ::fl[lp^1]=1;
        --f; ++fl; if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
void dfs(int p){
    --a; int lp,to,lt;
    for(lp=h[p];lp;lp=nxt[lp]){
        to=::to[lp];
        if(vis[to]) continue;
        vis[to]=1;
        for(lt=h[to];lt;lt=nxt[lt]){
            if(fl[lt]) continue; to-=n; 
            ut[to]=p; vt[to]=::to[lt];
            dfs(::to[lt]); break;
        }
    }
}
int main(){
    scanf("%d",&n); 
    Add(1,t=n+n);
    for(i=1;i<n;++i){
        scanf("%d",&u);
        Add(0,n+i); Add(i+1,t);
        while(u--){
            scanf("%d",&v);
            Add(n+i,v);
        }
    }
    for(d[0]=1;;){
        memset(d+1,0,t<<2);
        memcpy(c,h,(t+1)<<2);
        for(q.push(0);!q.empty();){
            u=q.front(); q.pop();
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=to[i]; if(d[v]) continue;
                d[v]=d[u]+1; q.push(v);
            }
        }
        if(!d[t]) break; a+=dinic(0,INF);
    }
    if(a!=n-1){
        printf("-1");
        return 0;
    }
    for(i=h[t];i;i=nxt[i]){
        if(fl[i]) continue;
        vis[t]=1; dfs(to[i]);
        if(~a){
            printf("-1");
            return 0;
        }
        break;
    }
    for(i=1;i<n;++i) printf("%d %d\n",ut[i],vt[i]);
    return 0;
}

AGC031E Snuke the Phantom Thief

解法

直接统计坐标再某个范围内的珠宝的数量显然不可行(状态难以维护),考虑用其他的方式表示限制。

考虑按照横坐标从小到大选择所有珠宝,则可以在选择珠宝的同时维护对应的限制。上述的第一种限制可以看成横坐标排名大于 \(b_i\) 的珠宝的横坐标必须大于 \(a_i\)。而在考虑第二种限制的时候,可以事先定好选择的珠宝总数 \(k\),则第二种限制可以看成横坐标排名不大于 \(k-a_i\) 的珠宝的横坐标必须小于 \(a_i\)。综合这两种情况,可以确定横坐标在某个排名的珠宝的横坐标取值范围,可以使用网络流解决对应的内容,注意一定需要在能选满 \(k\) 个珠宝的时候计入答案。(同时不需要加入排名更大的珠宝的横坐标一定要更大的限制)纵坐标也是同样可以确定对应的取值范围,此时每个珠宝只有在横纵坐标均能计入排名时统计答案。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxd=85;
const int maxn=330;
const int maxm=26100;
const int INF=1145141919;
const ll LINF=0xcfcfcfcfcfcfcfcf;
int n,m,i,j,k,u,v,s,t,a,te=1,pe;
int x[maxd],y[maxd],ph[maxn];
int L[maxd],R[maxd],U[maxd],D[maxd];
int h[maxn],c[maxn],to[maxm],nxt[maxm]; 
ll w,ret,ans,ed[maxm],d[maxn]; 
bool fl[maxm],vis[maxn];
queue<int> q; char ch;
inline void cmin(int &x,int y){if(x>y) x=y;}
inline void cmax(int &x,int y){if(x<y) x=y;}
inline void Add(int x,int y,ll c){
    to[++te]=y; fl[te]=1; ed[te]=c; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; fl[te]=0; ed[te]=-c; nxt[te]=h[y]; h[y]=te;
} 
int dinic(int p,int f){
    if(p==t) return 1;
    vis[p]=1; int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(vis[to]) continue;
        if(d[to]!=d[p]+ed[lp]) continue;
        if(!dinic(to,1)) continue;
        --f; ++fl; ret+=ed[lp];
        ::fl[lp]=0; ::fl[lp^1]=1;
        if(!f) break;
    }
    if(!fl) d[p]=LINF; 
    vis[p]=0; return fl;
}
int main(){
    memset(R,0x3f,sizeof(R));
    memset(U,0x3f,sizeof(U));
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d%d%lld",x+i,y+i,&w); 
        u=i<<1; Add(u-1,u,w);
    }
    scanf("%d",&m);
    while(m--){
        scanf(" %c%d%d",&ch,&u,&v); ++v;
        if(ch=='L') cmax(L[v],u+1);
        else if(ch=='R') cmin(R[v],u-1);
        else if(ch=='D') cmax(D[v],u+1);
        else cmin(U[v],u-1); 
    }
    for(i=2;i<=n;++i){
        cmax(L[i],L[i-1]);
        cmin(R[i],R[i-1]);
        cmax(D[i],D[i-1]);
        cmin(U[i],U[i-1]);
    }
    memcpy(ph,h,sizeof(h)); pe=te;
    for(i=1;i<=n;++i){
        memcpy(h,ph,sizeof(h)); 
        for(j=2;j<=pe;j+=2){
            fl[j]=1; 
            fl[j|1]=0;
        }
        te=pe; s=n<<1; 
        t=(n<<1)+(i<<1)+1;
        for(j=1;j<=i;++j){
            Add(0,s+j,0);
            Add(s+i+j,t,0);
        }
        for(j=1;j<=n;++j){
            u=x[j]; v=y[j];
            for(k=1;k<=i;++k){
                if(u>=L[k]&&u<=R[i-k+1]) Add(s+k,(j<<1)-1,0);
                if(v>=D[k]&&v<=U[i-k+1]) Add(j<<1,s+i+k,0);
            }
        }
        for(;;){
            memset(d+1,0xcf,t<<3);
            memcpy(c,h,(t+1)<<2);
            for(q.push(0);!q.empty();){
                u=q.front(); 
                q.pop(); vis[u]=0;
                for(j=h[u];j;j=nxt[j]){
                    if(!fl[j]) continue;
                    v=to[j];
                    if(d[v]<d[u]+ed[j]){
                        d[v]=d[u]+ed[j];
                        if(!vis[v]){
                            vis[v]=1;
                            q.push(v);
                        }
                    }
                }
            }
            if(d[t]==LINF) break; 
            a+=dinic(0,INF);
        }
        if(a==i&&ret>ans) ans=ret; ret=a=0;
    }
    printf("%lld",ans);
    return 0;
}

JAG2014 Autumn Reverse a Road II

解法

考虑每条边容量为 \(1\) 的限制。此时跑出最大流之后反转的边一定需要形成新的增广路,且由最大流最小割定理可得反转某条边最多只会多一条增广路;故最后最需要在残量网络上找某条边权大于 \(0\)原边,判断其的起点是否能到达 \(t\),终点是否能从 \(s\) 到达即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const int maxm=20010;
const int INF=1145141919;
int n,m,s,t,i,u,v,a,te,ans;
int h[maxn],d[maxn],c[maxn],vis[maxn];
int to[maxm],nxt[maxm]; bool fl[maxm];
queue<int> q;
int dinic(int p,int f){
    if(p==t) return 1;
    int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        if(!dinic(to,1)) continue;
        ::fl[lp]=0; ::fl[lp^1]=1;
        --f; ++fl; if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
int main(){
    for(;;){
        scanf("%d%d%d%d",&n,&m,&s,&t);
        if(!n) break; te=1;
        memset(h+1,0,n<<2);
        memset(vis+1,0,n<<2);
        while(m--){
            scanf("%d%d",&u,&v);
            to[++te]=v; fl[te]=1; nxt[te]=h[u]; h[u]=te;
            to[++te]=u; fl[te]=0; nxt[te]=h[v]; h[v]=te;
        }
        for(a=0;;){
            memset(d+1,0,n<<2);
            memcpy(c+1,h+1,n<<2);
            d[s]=1; 
            for(q.push(s);!q.empty();){
                u=q.front(); q.pop();
                for(i=h[u];i;i=nxt[i]){
                    if(!fl[i]) continue;
                    v=to[i]; if(d[v]) continue;
                    d[v]=d[u]+1; q.push(v);
                }
            }
            if(!d[t]) break; a+=dinic(s,INF);
        }
        for(q.push(s),vis[s]=1;!q.empty();){
            u=q.front(); q.pop();
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=to[i]; if(vis[v]) continue;
                vis[v]=1; q.push(v);
            }
        }
        for(q.push(t),vis[t]=-1;!q.empty();){
            u=q.front(); q.pop();
            for(i=h[u];i;i=nxt[i]){
                if(fl[i]) continue;
                v=to[i]; if(vis[v]) continue;
                vis[v]=-1; q.push(v);
            }
        }
        for(i=2,ans=0;i<=te;++i){
            if(!fl[i]) continue;
            if(vis[to[i]]==1&&vis[to[i|1]]==-1) ++ans;
        }
        if(ans) ++a; printf("%d %d\n",a,ans);
    }
    return 0;
}

P4174 [NOI2006]最大获利

解法

裸最大权闭合子图。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=55010;
const int maxm=1000010;
const int INF=1145141919;
int n,m,i,u,v,w,t,a,te=1;
int h[maxn],d[maxn],c[maxn];
int to[maxm],fl[maxm],nxt[maxm];
queue<int> q;
inline void Add(int x,int y,int f){
    to[++te]=y; fl[te]=f; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te; 
} 
int dinic(int p,int f){
    if(p==t) return f;
    int to,fl=0,nw;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        nw=dinic(to,min(f,::fl[lp]));
        if(!nw) continue; f-=nw; fl+=nw; 
        ::fl[lp]-=nw; ::fl[lp^1]+=nw;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
int main(){
    scanf("%d%d",&n,&m); 
    t=n+m+1;
    for(i=1;i<=n;++i){
        scanf("%d",&u);
        Add(i,t,u);
    }
    for(i=1;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        Add(i+n,u,INF); 
        Add(i+n,v,INF);
        Add(0,i+n,w); a+=w;
    }
    for(;;){
        memset(d,0,(t+1)<<2);
        memcpy(c,h,(t+1)<<2);
        for(d[0]=1,q.push(0);!q.empty();){
            u=q.front(); q.pop();
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=to[i]; if(d[v]) continue;
                d[v]=d[u]+1; q.push(v);
            }
        }
        if(!d[t]) break;
        a-=dinic(0,INF);
    }
    printf("%d",a);
    return 0;
}

P4217 [CTSC2010]产品销售

解法

考虑费用流。将每个季度看成一个节点,则需要连的边如下:

  • \(源点\mathop{\longrightarrow}\limits_{费用=P_i}^{容量=U_i}i\)
  • \(i\mathop{\longrightarrow}\limits_{费用=0}^{容量=D_i}汇点\)
  • \(i\mathop{\longrightarrow}\limits_{费用=M_i}^{容量=\infty}i+1\)
  • \(i+1\mathop{\longrightarrow}\limits_{费用=C_i}^{容量=\infty}i\)

此时可以对换源汇点,使得从源点流出的边均满流,方便之后的处理。

由于建出的图内没有负环,则和源点/汇点相连的边不会被反悔,同时每一条增广路一定形如 \(S\rightarrow i\rightarrow p_i\rightarrow T\) 的形式。由于在增广时遍历边的顺序不会对答案造成影响,所以我们从左往右讨论从每个点出发的增广路。此时需要维护每个点到汇点的剩余容量 \(R_i\),从当前点到 右边 满足 \(\boldsymbol{R_i\ne 0}\) 的点 \(i\) 的增广路费用 \(sr_i\),左边的对应费用 \(sl_i\)。注意在向右增广时出现的向左的反悔边由于费用小于 \(0\),在向左增广时一定会代替原来,此时一定会选反悔边。此时需要维护反悔边的对应容量,反悔边容量为 \(0\) 时需要将对应 \(sl\) 进行修改。此时可以使用线段树分别维护 \(sr,sl\) 和反悔边容量。

计算时间复杂度时,考虑每次找一条增广路后,会出现下面三种情况:

  • 某个 \(U_i\) 变为 \(0\)
  • 某个 \(D_i\) 变为 \(0\)
  • 某些反悔边的容量变为 \(0\)/从 \(0\) 变为一个非 \(0\) 值。

显然前两者的发生次数总和不超过 \(O(n)\) 次。至于第三者,考虑某条反悔边在容量变为 \(0\) 之后不会重新变成非 \(0\) 值,故找增广路的次数不会超过 \(O(n)\) 次,时间复杂度为 \(O(n\log n)\)

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define sc second
const int maxn=100010;
const int maxb=maxn<<2;
const int INF=1000000000;
int n,i,j,fl,mf,d[maxn],u[maxn],p[maxn];
int c[maxn],m[maxn],w[maxn],sr[maxn];
pii m1,m2; long long ans;
struct len{int l,r,m;}tr[maxb]; 
int mn[maxb]; bool col[maxb];
struct seg{
    int add;
    pii mn;
}t1[maxb],t2[maxb],t3[maxb];
#define l(p) tr[p].l
#define r(p) tr[p].r
#define m(p) tr[p].m
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mn(p) t[p].mn
#define add(p) t[p].add
void build(int p,int l,int r){
    l(p)=l; r(p)=r;
    m(p)=(l+r)>>1;
    if(l==r) return;
    build(ls(p),l,m(p));
    build(rs(p),m(p)+1,r);
}
void assign(int p,seg *t,int *v){
    if(l(p)==r(p)){
        mn(p)=mp(v[l(p)],l(p));
        return;
    }
    assign(ls(p),t,v); assign(rs(p),t,v);
    mn(p)=min(mn(ls(p)),mn(rs(p)));
}
inline void PushTag(seg &p,int d){
    if(p.mn.fi+d>=0) p.mn.fi+=d;
    p.add+=d;
}
inline void Pushdown(int p,seg *t){
    if(!add(p)) return;
    PushTag(t[ls(p)],add(p));
    PushTag(t[rs(p)],add(p));
    add(p)=0;
}
void Add(int p,int l,int r,int d,seg *t){
    if(l<=l(p)&&r>=r(p)){
        mn(p).fi+=d; add(p)+=d;
        return;
    } 
    Pushdown(p,t);
    if(l<=m(p)) Add(ls(p),l,r,d,t);
    if(r>m(p)) Add(rs(p),l,r,d,t);
    mn(p)=min(mn(ls(p)),mn(rs(p)));
}
void Add3(int p,int l,int r,int f,seg *t=t3){
    if(!col[p]&&f<0) return; 
    if((l(p)==r(p))||(l<=l(p)&&r>=r(p)&&
    ((f>0&&mn[p])||(f<0&&f+mn(p).fi>0)))){
        int d=l(p);
        if(!mn(p).fi) col[p]=1,w[d]=-m[d]; 
        add(p)+=f; mn(p).fi+=f; mn[p]+=f;
        if(!mn(p).fi) col[p]=0,Add(1,1,d,m[d]+c[d],t2);
        return;
    }
    Pushdown(p,t3);
    if(l<=m(p)) Add3(ls(p),l,r,f);
    if(r>m(p)) Add3(rs(p),l,r,f);
    mn(p)=min(col[ls(p)]?mn(ls(p)):mp(INF,0),
              col[rs(p)]?mn(rs(p)):mp(INF,0));
    mn[p]=min(mn[ls(p)],mn[rs(p)]);
    col[p]=col[ls(p)]||col[rs(p)];
}
pii Query(int p,int l,int r,seg *t){
    if(t==t3&&!col[p]) return mp(INF,0);
    if(l<=l(p)&&r>=r(p)) return mn(p);
    Pushdown(p,t); pii ret=mp(INF,0);
    if(l<=m(p)) ret=Query(ls(p),l,r,t);
    if(r>m(p)) ret=min(ret,Query(rs(p),l,r,t));
    return ret;
}
int main(){
    scanf("%d",&n); 
    build(1,1,n); assign(1,t3,d);
    for(i=1;i<=n;++i) scanf("%d",d+i);
    for(i=1;i<=n;++i) scanf("%d",u+i);
    for(i=1;i<=n;++i) scanf("%d",p+i);
    for(i=1;i<n;++i){
        scanf("%d",c+i);
        w[i]=c[i];
    }
    for(i=1;i<n;++i){
        scanf("%d",m+i);
        sr[i+1]=sr[i]+m[i];
        sr[i]+=p[i];
    }
    sr[n]+=p[n]; 
    assign(1,t1,sr); 
    assign(1,t2,p);
    for(i=1;i<=n;++i){
        if(i!=1){
            Add(1,i,n,-m[i-1],t1);
            Add(1,1,i-1,w[i-1],t2);
        }
        while(d[i]){
            m1=Query(1,i,n,t1); 
            if(i!=1) m2=Query(1,1,i-1,t2); 
            else m2=mp(INF,0);
            if(m1<m2){
                j=m1.sc;
                fl=min(d[i],u[j]);
                d[i]-=fl; u[j]-=fl;
                ans+=1LL*fl*m1.fi;
                if(i<j) Add3(1,i,j-1,fl);
            }
            else{
                j=m2.sc;
                if(j<i) mf=Query(1,j,i-1,t3).fi;
                else mf=0; if(!(fl=mf)) fl=INF;
                fl=min(fl,min(d[i],u[j]));
                d[i]-=fl; u[j]-=fl; 
                ans+=1LL*fl*m2.fi; 
                if(mf) Add3(1,j,i-1,-fl);
            }
            if(!u[j]){
                Add(1,j,j,INF,t1);
                Add(1,j,j,INF,t2);
            }
        }
    }
    printf("%lld",ans);
    return 0;
}

TopCoder12543 DeerInZooDivOne

解法

题目要求两个连通块不交,可以暴力枚举一条边断掉,然后在两个连通块内匹配出同构的连通块。

\(dp_{u,v}\) 表示以 \(u\)\(v\) 为根的子树中,分别 包含 \(\boldsymbol{u,v}\) 的最大同构连通块大小。转移时枚举 \(u\)\(v\) 的儿子的对应 dp 值。然而直接枚举两者的儿子不太可行。考虑 \(u\) 的某个儿子最多只能匹配 \(v\) 的一个儿子,对答案产生贡献,可以把问题转化成二分图带权最大匹配问题。时间复杂度为 \(O(n\times n^2\times n^2\times n)=O(n^6)\)(枚举边 + dp + 费用流/KM)但是显然跑不满。注意在实现时 dp 时需要额外维护 \(u,v\) 的父亲,有可能两个同构的连通块需要的根节点不能直接设为断掉的边的两端。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=55;
const int maxm=1400;
const int INF=1145141919;
const int NINF=(signed)0xcfcfcfcf;
int n,i,j,u,v,a,t=1,te,ans;
int he[maxn],to[maxm],ed[maxm],nxt[maxm];
int h[maxn],d[maxn],c[maxn],dp[maxn][maxn][maxn][maxn];
bool fl[maxm],vis[maxn],col[maxn];
struct edge{int to,nxt;}E[maxn<<1]; queue<int> q;
inline void Add(int x,int y,int e){
    to[++te]=y; ed[te]=e; fl[te]=1; nxt[te]=he[x]; he[x]=te;
    to[++te]=x; ed[te]=-e; fl[te]=0; nxt[te]=he[y]; he[y]=te;
}
void dfs(int p,int f){
    for(int to,lp=h[p];lp;lp=E[lp].nxt){
        if((to=E[lp].to)==f) continue;
        col[to]=col[p]; dfs(to,p);
    }
}
int dinic(int p,int f){
    if(p==t) return f;
    vis[p]=1; int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp]; if(vis[to]) continue;
        if(d[to]!=d[p]+ed[lp]) continue;
        if(!dinic(to,1)) continue;
        ::fl[lp]=0; ::fl[lp^1]=1;
        --f; ++fl; a+=ed[lp]; if(!f) break;
    }
    if(!fl) d[p]=NINF; 
    vis[p]=0; return fl;
}
void solve(int x,int fx,int y,int fy){
    if(dp[x][fx][y][fy]) return; 
    int lp,lt; 
    for(lp=h[x];lp;lp=E[lp].nxt){
        if(E[lp].to==fx||col[E[lp].to]) continue;
        for(lt=h[y];lt;lt=E[lt].nxt)
            if(E[lt].to!=fy&&col[E[lt].to])
                solve(E[lp].to,x,E[lt].to,y);
    }
    te=1; memset(he,0,(t+1)<<2);
    for(lt=h[y];lt;lt=E[lt].nxt)
        if(E[lt].to!=fy)
            Add(E[lt].to,t,0);
    for(lp=h[x];lp;lp=E[lp].nxt){
        if(E[lp].to==fx||col[E[lp].to]) continue;
        Add(0,E[lp].to,0);
        for(lt=h[y];lt;lt=E[lt].nxt)
            if(E[lt].to!=fy&&col[E[lt].to])
                Add(E[lp].to,    E[lt].to,
                 dp[E[lp].to][x][E[lt].to][y]);
    }
    for(a=0;;){
        memset(d+1,0xcf,t<<2);
        memcpy(c,he,(t+1)<<2);
        for(q.push(0);!q.empty();){
            u=q.front(); 
            q.pop(); vis[u]=0;
            for(j=he[u];j;j=nxt[j]){
                if(!fl[j]) continue;
                v=to[j];
                if(d[v]<d[u]+ed[j]){
                    d[v]=d[u]+ed[j];
                    if(!vis[v]){
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
        if(d[t]==NINF) break; dinic(0,INF);
    }
    dp[x][fx][y][fy]=a+1; return;
}
class DeerInZooDivOne{
    public:
        int getmax(vector<int> a,vector<int> b){
            n=a.size()+1; 
            for(i=1;i<n;++i){
                u=a[i-1]+1; v=b[i-1]+1;
                E[++t]={v,h[u]}; h[u]=t;
                E[++t]={u,h[v]}; h[v]=t;
            } 
            t=n+1; 
            for(i=1;i<n;++i){
                memset(dp,0,sizeof(dp));
                memset(col+1,0,n);
                u=E[i<<1].to; v=E[i<<1|1].to; 
                col[v]=1; dfs(v,u); 
                for(u=1;u<=n;++u){
                    if(col[u]) continue;
                    for(v=1;v<=n;++v){
                        if(!col[v]) continue;
                        solve(u,0,v,0);
                        ans=max(ans,dp[u][0][v][0]);
                    }
                }
            }
            return ans;
        }
};

TopCoder12729 GearsDiv1

解法

考虑所有点点权相同时,一定不会存在任何边,故其他的点权也是符合条件的。

否则一定有某个颜色的点满足点权和其他的点权不同,意味着另外两种颜色的点的点权一定需要相同,对应删点时一定要删到这两种颜色的点之间不存在边为止,也就是删除这两种颜色的点的导出子图的最小点覆盖。显然这张图一定会是二分图。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=55;
const int maxm=2700;
const int INF=1145141919;
int n,i,j,u,v,t,a,w,c1,c2,te;
int h[maxn],d[maxn],c[maxn];
int to[maxm],fl[maxm],nxt[maxm];
queue<int> q; const char s[4]="RGB";
inline void Add(int x,int y){
    to[++te]=y; fl[te]=1; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; fl[te]=0; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==t) return 1;
    int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        if(!dinic(to,1)) continue; --f; ++fl; 
        ::fl[lp]=0; ::fl[lp^1]=1;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
class GearsDiv1{
    public:
        int getmin(string c,vector<string> g){
            n=c.size(); t=n+1; 
            d[0]=1; a=0x3f3f3f3f;
            for(c1=0;c1<2;++c1){
                for(c2=c1+1;c2<3;++c2){
                    te=1; memset(h,0,(t+1)<<2);
                    for(i=1;i<=n;++i){
                        if(c[i-1]!=s[c1]) continue; Add(0,i); 
                        for(j=1;j<=n;++j){
                            if(c[j-1]!=s[c2]) continue; Add(j,t);
                            if(g[i-1][j-1]=='N') continue; Add(i,j);
                        }
                    }
                    for(w=0;;){
                        memset(d+1,0,t<<2);
                        memcpy(::c,h,(t+1)<<2);
                        for(q.push(0);!q.empty();){
                            u=q.front(); q.pop();
                            for(i=h[u];i;i=nxt[i]){
                                if(!fl[i]) continue;
                                v=to[i]; if(d[v]) continue;
                                d[v]=d[u]+1; q.push(v);
                            }
                        }
                        if(!d[t]) break; w+=dinic(0,INF);
                    }
                    if(w<a) a=w;
                }
            }
            return a;
        }
}; 

TopCoder12500 TheTilesDivOne

解法

考虑在每个黑格处统计对应答案的贡献。每个黑格会限制相邻白格放置 L 形的方案,在四种放置方案中选择一种;而每个白格处只能最多放一个 L 形。拆点限制对应点处的流量即可。

代码(似乎有误)

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxd=50;
const int maxn=5010;
const int maxm=18000;
const int INF=1145141919;
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
int n,m,i,j,k,u,v,t,a,te=1;
int h[maxn],d[maxn],c[maxn];
int it[maxd][maxd],ot[maxd][maxd];
int to[maxm],nxt[maxm]; 
bool fl[maxm]; queue<int> q;
inline void Add(int x,int y){
    to[++te]=y; fl[te]=1; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==1) return 1;
    int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        if(!dinic(to,1)) continue; 
        --f; ++fl; ::fl[lp]=0; ::fl[lp^1]=1;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
class TheTilesDivOne{
    public:
        int find(vector<string> b){
            n=b.size(); m=b[0].size();
            for(i=1;i<=n;++i){
                for(j=(i&1)+1;j<=m;j+=2){
                    if(b[i-1][j-1]=='X') continue;
                    it[i][j]=++t; ot[i][j]=++t;
                    Add(t-1,t); 
                    if(i&1) Add(t,1); 
                    else Add(0,t-1);
                }
            }
            for(i=1;i<=n;++i){
                for(j=((i+1)&1)+1;j<=m;j+=2){
                    if(b[i-1][j-1]=='X') continue;
                    t+=2; u=i; v=j; Add(t-1,t);
                    for(k=0;k<4;i=u,j=v,++k){
                        i+=dx[k]; j+=dy[k];
                        if(!it[i][j]) continue;
                        if(i&1) Add(t,it[i][j]);
                        else Add(ot[i][j],t-1);
                    }
                }
            }
            for(d[0]=1;;){
                memset(d+1,0,t<<2);
                memcpy(c,h,(t+1)<<2);
                for(q.push(0);!q.empty();){
                    u=q.front(); q.pop();
                    for(i=h[u];i;i=nxt[i]){
                        if(!fl[i]) continue;
                        v=to[i]; if(d[v]) continue;
                        d[v]=d[u]+1; q.push(v);
                    }
                }
                if(!d[1]) break; a+=dinic(0,INF); 
            }
            return a;
        }
};

TopCoder12418 BoardPainting

解法

考虑开始时先将每一格单独操作,然后不断合并相邻的两格,需要合并次数尽可能多。但是同一个格子不能被合并多遍。此时考虑把每个操作看成点,将一行内两格合并的操作和这两格连边,将一列内两格和合并它们的操作连边,则需要删去两边的尽量少的操作使得不存在一个格子使两边连通。考虑将源点和合并相邻两列的操作连边,汇点和合并相邻两行的操作连边,跑一遍最小割(边权可以均设为 \(1\),此时割掉操作和源/汇点的边这一唯一合法的操作一定不劣)。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxd=55;
const int maxn=7500;
const int maxm=30000;
const int INF=1145141919;
int n,m,i,j,u,v,t,a,te=1;
int h[maxn],c[maxn],d[maxn];
int to[maxm],nxt[maxm],b[maxd][maxd];
bool fl[maxm]; queue<int> q;
inline void Add(int x,int y){
    to[++te]=y; fl[te]=1; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==1) return 1;
    int to,fl=0;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        if(!dinic(to,1)) continue;
        --f; ++fl; ::fl[lp]=0; ::fl[lp^1]=1;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl; 
}
class BoardPainting{
    public:
        int minimalSteps(vector<string> target){
            n=target.size(); 
            m=target[0].size(); t=1;
            for(i=1;i<=n;++i)
                for(j=1;j<=m;++j)
                    if(target[i-1][j-1]=='#')
                        b[i][j]=++t; 
            a=t-1; 
            for(i=1;i<=n;++i){
                for(j=1;j<=m;++j){
                    if(!(u=b[i][j])) continue;
                    if(v=b[i-1][j]){
                        --a; ++t;
                        Add(0,t); Add(t,u); Add(t,v);
                    }
                    if(v=b[i][j-1]){
                        --a; ++t;
                        Add(t,1); Add(u,t); Add(v,t);
                    }
                }
            }
            for(d[0]=1;;){
                memset(d+1,0,t<<2);
                memcpy(c,h,(t+1)<<2);
                for(q.push(0);!q.empty();){
                    u=q.front(); q.pop();
                    for(i=h[u];i;i=nxt[i]){
                        if(!fl[i]) continue;
                        v=to[i]; if(d[v]) continue;
                        d[v]=d[u]+1; q.push(v);
                    }
                }
                if(!d[1]) break; a+=dinic(0,INF);
            }
            return a;
        }
};

TopCoder12432 CurvyonRails

解法

考虑对整张图黑白染色,然后对每个格子向四联通的格子连边,形成闭合回路的一个充要条件是每个格子均连了两条 不同 的边。

然后考虑特殊点对答案造成的贡献。此时可以对特殊点的两种边分别建点,然后对两个点分别连容量为 \(1\),费用为 \(0/1\) 的边,跑最小费用最大流。此时特殊点如果两条边类型相同则会对答案造成 \(1\) 的贡献,否则不会造成贡献。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxd=30;
const int maxn=1900;
const int maxm=9000;
const int INF=1145141919;
int n,m,i,j,t,u,v,a,s,te=1;
int h[maxn],d[maxn],c[maxn];
int R[maxd][maxd],C[maxd][maxd];
int to[maxm],fl[maxm],ed[maxm],nxt[maxm];
queue<int> q; bool vis[maxn]; char ch;
inline void Add(int x,int y,int f,int e){
    to[++te]=y; fl[te]=f; ed[te]=e; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; ed[te]=-e; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==1) return f;
    vis[p]=1; int to,fl=0,nw;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue; 
        to=::to[lp]; if(vis[to]) continue;
        if(d[to]!=d[p]+ed[lp]) continue;
        nw=dinic(to,min(f,::fl[lp]));
        if(!nw) continue; f-=nw; fl+=nw;
        ::fl[lp]-=nw; ::fl[lp^1]+=nw;
        a+=nw*ed[lp]; if(!f) break;
    }
    if(!fl) d[p]=0x3f3f3f3f; 
    vis[p]=0; return fl;
}
class CurvyonRails{
    public:
        int getmin(vector<string> field){
            n=field.size(); 
            m=field[0].size(); t=1;
            for(i=1;i<=n;++i){
                for(j=1;j<=m;++j){
                    ch=field[i-1][j-1];
                    if(ch=='w') continue;
                    if((i+j)&1) ++s; else --s;
                    if(ch=='.'){
                        R[i][j]=C[i][j]=++t;
                        if((i+j)&1) Add(0,t,2,0);
                        else Add(t,1,2,0);
                    }
                    else{
                        ++t;
                        R[i][j]=++t;
                        C[i][j]=++t;
                        if((i+j)&1){
                            Add(0,t-2,2,0);
                            Add(t-2,t-1,1,0);
                            Add(t-2,t-1,1,1);
                            Add(t-2,t,1,0);
                            Add(t-2,t,1,1);
                        }
                        else{
                            Add(t-2,1,2,0);
                            Add(t-1,t-2,1,0);
                            Add(t-1,t-2,1,1);
                            Add(t,t-2,1,0);
                            Add(t,t-2,1,1);
                        }
                    }
                }
            }
            if(s) return -1;
            for(i=1;i<=n;++i){
                for(j=(i&1)+1;j<=m;j+=2){
                    if(!R[i][j]) continue;
                    u=R[i][j]; s+=2;
                    if(v=R[i-1][j]) Add(u,v,1,0);
                    if(v=R[i+1][j]) Add(u,v,1,0);
                    u=C[i][j];
                    if(v=C[i][j-1]) Add(u,v,1,0);
                    if(v=C[i][j+1]) Add(u,v,1,0);
                }
            }
            for(;;){
                memset(d+1,0x3f,t<<2);
                memcpy(c,h,(t+1)<<2);
                for(q.push(0);!q.empty();){
                    u=q.front(); 
                    q.pop(); vis[u]=0;
                    for(i=h[u];i;i=nxt[i]){
                        if(!fl[i]) continue;
                        v=to[i];
                        if(d[v]>d[u]+ed[i]){
                            d[v]=d[u]+ed[i];
                            if(!vis[v]){
                                vis[v]=1;
                                q.push(v);
                            }
                        }
                    }
                }
                if(d[1]==0x3f3f3f3f) break; 
                s-=dinic(0,INF);
            }
            if(s) a=-1; return a;
        }
};

TopCoder12158 SurroundingGame

解法

一直把题面理解错也太 ** 了

考虑将问题模型转成最小割,此时放弃某个物品的收益当且仅当自己未选,周围位置不全选。此时首先需要黑白染色,分开黑白格对应的代价和收益。此时将每个格子拆成两个点 \(u,v\),每个 \(u\)\(v\) 连容量为对应收益的边;同时源点向黑格的 \(u\) 点,白格的 \(v\) 向汇点连容量为对应代价的边;最后黑格的 \(u,v\) 向相邻白格的 \(u,v\) 连容量为 \(INF\) 的边。此时某个格子的 \(u\)\(v\) 的边不会被割当且仅当该格子对应代价边被割,或者相邻的格子的对应代价的边不被割。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxd=22;
const int maxn=820;
const int maxm=2400;
const int INF=1145141919;
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
int n,m,i,j,k,u,v,w,t=-1,te=1,ans;
int nm[maxd][maxd],h[maxn],d[maxn],c[maxn];
int to[maxm],nxt[maxm],fl[maxm];
queue<int> q;
inline int F(char c){
    if(isdigit(c)) return c^'0';
    if(islower(c)) return c-'a'+10;
    return c-'A'+36;
}
inline void Add(int x,int y,int f){
    to[++te]=y; fl[te]=f; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==t) return f;
    int to,fl=0,nw;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        nw=dinic(to,min(::fl[lp],f));
        if(!nw) continue; f-=nw; fl+=nw;
        ::fl[lp]-=nw; ::fl[lp^1]+=nw;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
class SurroundingGame{
    public:
        int maxScore(vector<string> c,vector<string> b){
            n=c.size(); m=c[0].size();
            for(i=1;i<=n;++i)
                for(j=1;j<=m;++j)
                    nm[i][j]=(t+=2);
            t+=2;
            for(i=1;i<=n;++i){
                for(j=1;j<=m;++j){
                    w=F(b[i-1][j-1]); 
                    v=F(c[i-1][j-1]);
                    u=nm[i][j]; Add(u,u+1,w); ans+=w;
                    if((i+j)&1){
                        Add(0,u,v);
                        for(k=0;k<4;++k){
                            v=nm[i+dx[k]][j+dy[k]];
                            if(!v) continue;
                            Add(u,v,INF); Add(u+1,v+1,INF);
                        }
                    }
                    else Add(u+1,t,v);
                }
            }
            for(d[0]=1;;){
                memset(d+1,0,t<<2);
                memcpy(::c,h,(t+1)<<2);
                for(q.push(0);!q.empty();){
                    u=q.front(); q.pop();
                    for(i=h[u];i;i=nxt[i]){
                        if(!fl[i]) continue;
                        v=to[i]; if(d[v]) continue;
                        d[v]=d[u]+1; q.push(v);
                    }
                }
                if(!d[t]) break; ans-=dinic(0,INF);
            }
            return ans;
        }
};

TopCoder14719 RatingProgressAward

解法

考虑在对操作定序之后,只有最大子段内的操作会对答案造成贡献。此时可以将所有操作所在的位置按照在最大子段前/内/后分部分,而每组约束关系 \((u,v)\) 即对应了 \(u\) 所在部分会影响 \(v\) 所在部分;每种操作只有在某个部分内才会造成贡献。此时这个题变成了 P3227 [HNOI2013]切糕

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
const int maxm=2220;
const int INF=1145141919;
int n,i,u,v,te=1,ans;
int h[maxn],d[maxn],c[maxn];
int to[maxm],fl[maxm],nxt[maxm];
queue<int> q;
inline void Add(int x,int y,int f){
    to[++te]=y; fl[te]=f; nxt[te]=h[x]; h[x]=te;
    to[++te]=x; nxt[te]=h[y]; h[y]=te;
}
int dinic(int p,int f){
    if(p==1) return f;
    int to,fl=0,nw;
    for(int &lp=c[p];lp;lp=nxt[lp]){
        if(!::fl[lp]) continue;
        to=::to[lp];
        if(d[to]!=d[p]+1) continue;
        nw=dinic(to,min(f,::fl[lp]));
        if(!nw) continue; f-=nw; fl+=nw;
        ::fl[lp]-=nw; ::fl[lp^1]+=nw;
        if(!f) break;
    }
    if(!fl) d[p]=0; return fl;
}
class RatingProgressAward{
    public:
        int maximalProgress(vector<int> c,vector<int> a,vector<int> b){
            n=c.size(); 
            for(i=1;i<=n;++i){
                v=c[i-1]; u+=2;
                if(v>0){
                    ans+=v;
                    Add(0,u,v);
                    Add(u+1,1,v);
                }
                else Add(u,u+1,-v);
            }
            for(i=a.size()-1;~i;--i){
                u=(a[i]+1)<<1; v=(b[i]+1)<<1;
                Add(u,v,INF); Add(u+1,v+1,INF);
            }
            for(d[0]=1;;){
                memset(d+1,0,(n+1)<<3);
                memcpy(::c,h,(n+2)<<3);
                for(q.push(0);!q.empty();){
                    u=q.front(); q.pop();
                    for(i=h[u];i;i=nxt[i]){
                        if(!fl[i]) continue;
                        v=to[i]; if(d[v]) continue;
                        d[v]=d[u]+1; q.push(v);
                    }
                }
                if(!d[1]) break; ans-=dinic(0,INF);
            }
            return ans;
        }
};

CodeChef PARADE - Annual Parade

解法

考虑有向图最小路径覆盖。单点组成的路径/简单路径会在拆点二分图中造成一对失配点的贡献,然而如果有向图的路径覆盖中出现了大小大于 \(1\) 的环,则对应拆点二分图中环不会对失配点数量造成贡献。

问题原为求有向有环带权图中经过某个数量的点的最小路径数,此时可以直接建一张新图,其中每对点 \(u,v\) 在新图上的边权为原图上 \(u\)\(v\) 的最短路。原图上的路径覆盖在新图上对应的一定满足经过的点不重。同时新图上的路径覆盖每多一条边就会对代价中 \(C\) 的系数减一。所以直接在新图上跑一遍最小路径覆盖即可。

代码

点此查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=260;
const int maxd=510;
const int maxm=127000;
int n,m,i,j,k,u,v,w,t,Q,te=1;
int to[maxm],ed[maxm],nxt[maxm];
int pre[maxd],wt[maxn],wp[maxn];
int dis[maxn][maxn],h[maxd],d[maxd]; 
bool fl[maxm],vis[maxd];
queue<int> q;
inline void Add(int x,int y,int e){
    to[++te]=y; ed[te]=e; nxt[te]=h[x]; fl[te]=1; h[x]=te;
    to[++te]=x; ed[te]=-e; nxt[te]=h[y]; h[y]=te;
} 
inline void cmin(int &x,int y){if(y<x) x=y;} 
int main(){
    scanf("%d%d%d",&n,&m,&Q); t=(n<<1)+1;
    memset(dis,0x3f,sizeof(dis));
    for(i=1;i<=n;++i) dis[i][i]=0;
    while(m--){
        scanf("%d%d%d",&u,&v,&w);
        cmin(dis[u][v],w);
    }
    for(k=1;k<=n;++k)
        for(i=1;i<=n;++i)
            for(j=1;j<=n;++j)
                cmin(dis[i][j],dis[i][k]+dis[k][j]);
    for(i=1;i<=n;++i) Add(0,i,0),Add(i+n,t,0);
    for(i=1;i<n;++i){
        for(j=i+1;j<=n;++j){
            Add(i,j+n,dis[i][j]);
            Add(j,i+n,dis[j][i]);
        }
    }
    for(j=n;;--j){
        memset(d+1,0x3f,t<<2);
        for(q.push(0);!q.empty();){
            u=q.front(); q.pop(); vis[u]=0;
            for(i=h[u];i;i=nxt[i]){
                if(!fl[i]) continue;
                v=to[i];
                if(d[v]>d[u]+ed[i]){
                    d[v]=d[u]+ed[i];
                    pre[v]=i;
                    if(!vis[v]){
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
        if(d[t]==0x3f3f3f3f) break;
        wt[j-1]=wt[j]+(wp[j-1]=d[t]);
        for(u=t;i=pre[u],u;u=to[i^1])
            fl[i]=0,fl[i^1]=1;
    }
    for(;j--;wt[j]=wp[j]=0x3f3f3f3f);
    while(Q--){
        scanf("%d",&w); i=upper_bound(wp,wp+n+1,w,greater<int>())-wp;
        printf("%d\n",wt[i]+i*w);
    }
    return 0;
}
posted @ 2022-12-09 19:32  Fran-Cen  阅读(63)  评论(0编辑  收藏  举报