专栏文章

题解:CF2046D For the Emperor!

CF2046D题解参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@miqvm58y
此快照首次捕获于
2025/12/04 11:28
3 个月前
此快照最后确认于
2025/12/04 11:28
3 个月前
查看原文

Statement

有一张 nn 个点 mm 条边的有向图,第 ii 个点上有 aia_i 个人。
你可以选择一些结点发放一份计划,接下来每个人可以沿着边自由移动,如果一个人从一个收到过计划的点移动到一个没有收到计划的点,那么可以令其收到计划。
你需要让所有的点都收到计划,求最少需要在多少个结点发放计划,或者报告无解。
数据范围:数据组数 T100T \leq 1002n2002 \leq n \leq 2001m8001 \leq m \leq 8000ain0 \leq a_i \leq n

Solution

考虑建立费用流模型,核心思想是让最大流量总是 ai\sum a_i,而经过一个点的费用是 B-BBB 是一个极大的数),在一个点发放计划的费用是 11,这样求出最小费用之后,可以根据其除以 BB 的整数部分计算其最多能让多少个点收到计划,根据余数计算出该前提下最小发放计划的数量。
记源点为 SS,汇点为 TT。考虑按照下述方案连边:
  • 为了限制从一个点 uu 出发的人的数量为 aua_u,先建立一个约束点 FuF_u,连一条流量为 aua_u 的边 SFuS \to F_u
  • 考虑为一个点 uu 建立一个入点 PuP_u 和一个出点 QuQ_u,流过 PuQuP_u \to Q_u 这条边就认为一个人走过的路径经过了点 uu。要让第一次经过产生 B-B 的费用,之后不产生费用,只需要连两条流量分别为 11++\infty、费用分别为 B-B00 的边即可。
  • 为了让一开始发放计划的点产生 11 的费用,而当点被经过之后从该点出发不产生 11 的费用,只需连边 FuPuF_u \to P_u,流量和费用均为 11,再连边 FuQuF_u \to Q_u,流量为 ++\infty,费用为 00。如果 FuQuF_u \to Q_u 被经过,那么 PuQuP_u \to Q_u 的费用为 B-B 的边一定被经过,此时点 uu 已经被发放计划,所以不产生贡献。
  • 对于原图的有向边 uvu \to v,需要连边 QuPvQ_u \to P_v
  • 最后当人结束移动时流向汇点,即连边 QuTQ_u \to T
如果原图存在环,那么需要先进行强联通分量缩点,aa 的值为 SCC 内所有点的 aa 的值的和。
按照上述方案连边,总点数是 Θ(n)\Theta(n) 的,总边数是 Θ(n+m)\Theta(n + m) 的,使用最小费用最大流解决可以接受。

Solution (English)

Consider constructing a cost-flow model. The core idea is to ensure that the maximum flow is always ai\sum a_i, and the cost of passing through a point is B-B (where BB is a very large number). The cost of issuing a plan at a point is 11. After calculating the minimum cost, its integer part divided by BB represents the maximum number of points that can receive the plan, while the remainder determines the minimum number of plans issued under this condition.
Let the source be SS and the sink be TT. Edges are constructed as follows:
  • To limit the number of people starting from a point uu to aua_u, introduce a constraint point FuF_u and connect an edge with capacity aua_u from SFuS \to F_u.
  • For a point uu, create an entry node PuP_u and an exit node QuQ_u. If a path passes through the edge PuQuP_u \to Q_u, it means that someone has traversed point uu. To ensure that the first traversal incurs a cost of B-B while subsequent traversals incur no cost, simply add two edges with capacities 11 and ++\infty, and costs B-B and 00, respectively.
  • To ensure that issuing a plan at a point initially incurs a cost of 11, and subsequent departures from the point incur no cost, connect edges FuPuF_u \to P_u with both capacity and cost of 11, and FuQuF_u \to Q_u with capacity ++\infty and cost 00. If the edge FuQuF_u \to Q_u is used, then the edge PuQuP_u \to Q_u with cost B-B must have been traversed, indicating that point uu has already been issued a plan and thus contributes no additional cost.
  • For a directed edge uvu \to v in the original graph, connect an edge QuPvQ_u \to P_v.
  • Finally, to allow people to end their movement at the sink, connect edges QuTQ_u \to T.
If the original graph contains cycles, first perform a strongly connected components (SCC) contraction. The value of aa for each SCC is the sum of the aa values of all points within the SCC.
Following the above scheme for edge construction, the total number of nodes is Θ(n)\Theta(n), and the total number of edges is Θ(n+m)\Theta(n + m). Solving this using minimum-cost maximum-flow is accepted.

Code

CPP
#include<bits/stdc++.h>
using namespace std;
const int inf=1e5;

struct MinCostFlow{
    static const int N=602,M=2000;
    int n,cntEdge,head[N+1];
    struct Edge{
        int to,nxt,lim,cost;
    }edge[(M+1)*2];
    inline void addEdge(int u,int v,int lim,int cost){
        edge[++cntEdge]={v,head[u],lim,cost},head[u]=cntEdge;
        edge[++cntEdge]={u,head[v],0,-cost},head[v]=cntEdge;
    }
    inline void init(int _n){
        n=_n,cntEdge=1;
        memset(head,0,sizeof(int)*(n+1));
    }
    int dis[N+1],minn[N+1],pre[N+1],preEdge[N+1];
    bool inQueue[N+1];
    inline bool SPFA(int S,int T){
        memset(dis,0x7f,sizeof(int)*(n+1));
        memset(minn,0x7f,sizeof(int)*(n+1));
        memset(inQueue,false,sizeof(bool)*(n+1));
        queue<int> Q;
        Q.push(S),dis[S]=0,inQueue[S]=true,pre[T]=-1;
        while(!Q.empty()){
            int u=Q.front(); Q.pop();
            inQueue[u]=false;
            for(int i=head[u];i;i=edge[i].nxt){
                int v=edge[i].to;
                if(edge[i].lim&&dis[u]+edge[i].cost<dis[v]){
                    dis[v]=dis[u]+edge[i].cost;
                    pre[v]=u,preEdge[v]=i,minn[v]=min(minn[u],edge[i].lim);
                    if(!inQueue[v])Q.push(v),inQueue[v]=true;
                }
            }
        }
        return pre[T]!=-1;
    }
    inline pair<int,int> flow(int S,int T){
        int maxflow=0,mincost=0;
        while(SPFA(S,T)){
            maxflow+=minn[T],mincost+=minn[T]*dis[T];
            int u=T;
            while(u!=S){
                edge[preEdge[u]].lim-=minn[T];
                edge[preEdge[u]^1].lim+=minn[T];
                u=pre[u];
            }
        }
        return {maxflow,mincost};
    }
}G;

int n,m,a[201];
vector<int> E[201];

int dfn[201],low[201],cntdfn;
int cntscc,scc[201],siz[201];
bool vis[201];
stack<int> sta;
void tarjan(int u){
    dfn[u]=low[u]=++cntdfn,vis[u]=true,sta.push(u);
    for(int v :E[u]){
        if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
        else if(vis[v])low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u]){
        scc[u]=++cntscc,siz[cntscc]=0;
        int v;
        do v=sta.top(),sta.pop(),siz[cntscc]+=a[v],scc[v]=cntscc,vis[v]=false;
        while(v!=u);
    }
}

int main(){
    int T; scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)E[i].clear(),dfn[i]=low[i]=vis[i]=0;
        cntdfn=cntscc=0;
        for(int i=1;i<=n;i++)scanf("%d",a+i);
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            E[u].push_back(v);
        }
        for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
        G.init(cntscc*3+2);
        auto id=[&](int t,int u){
            return t*cntscc+u;
        };
        int S=id(3,1),T=id(3,2);
        for(int i=1;i<=cntscc;i++){
            if(siz[i]){
                G.addEdge(S,id(0,i),siz[i],0);
                G.addEdge(id(0,i),id(1,i),1,1);
                G.addEdge(id(0,i),id(2,i),inf,0);
            }
            G.addEdge(id(1,i),id(2,i),1,-inf);
            G.addEdge(id(1,i),id(2,i),inf,0);
            G.addEdge(id(2,i),T,inf,0);
        }
        for(int u=1;u<=n;u++)
            for(int v :E[u])if(scc[u]!=scc[v])
                G.addEdge(id(2,scc[u]),id(1,scc[v]),inf,0);
        int cost=-G.flow(S,T).second;
        int used=(inf-cost%inf)%inf,covered=(cost+used)/inf;
        printf("%d\n",covered==cntscc?used:-1);
    }
    return 0;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...