2 条题解

  • 0
    @ 2026-5-10 3:03:15

    古希腊哲学家赫拉克利曾说:“人不能两次踏进同一条河流。”这句话承认了辩证唯物主义中绝对运动与相对静止的统一的观点,是正确的。

    人不能两次踏进同一条河流,因为你第一次踏进的河流和第二次踏进的河流已经不是同一个河流了(水流走了)。同样地,对于一个修车工人而言,修第一辆车的他和修第二辆车的他不是同一个人。

    所以本题的错误建图方式是,建一个二分图,左边是车,右边是工人,连边跑最小费用流。这样建图体现的是形而上学的不变论,是错误的。

    ~~说人话:~~对于每个工人而言,他在修第kk辆车的时候,之前已经修了k1k-1辆车,所以对于不同的kk,对应的客人等待的时间是不同的,因此我们需要将每个工人拆成nn个点,分别表示修第几辆车的他。

    但是你会发现这样做的话连边时的边权比较困难。所以我们需要做一个推导。假设某个工人一共修了KK辆车,花的时间分别为T1,T2,,TKT_1,T_2,\cdots,T_K,那么当他修第一辆车时,后面的K1K-1个人必须等着,同时第11个人也要等他修好,所以总共等待时间为K×T1K\times T_1;接下来修第二辆车时同理,有K1K-1个人在等他修,所以时间是(K1)×T2(K-1)\times T_2,以此类推,总等待时间即为

    i=1KTi(Ki+1)\sum\limits_{i=1}^K T_i(K-i+1)

    不难发现,他倒数第ii个修的车对应的客人总共要贡献Ti×iT_i\times i的等待时间。于是有了这一步转化,我们可以给出最终的建图方案了:将每个工人拆成nn个点,分别表示修倒数第几辆车的他;如果第jj个工人修第ii辆车要花T(i,j)T(i,j)的时间,我们枚举k=1,,nk=1,\cdots,n,从第ii辆车向第jj个工人的第kk个点连边,容量为11,费用为k×T(i,j)k\times T(i,j)。然后跑最小费用最大流,最后总费用除以nn即可。

    #include <cctype>
    #include <cstdio>
    #include <climits>
    #include <algorithm>
    
    template <typename T> inline void read(T& x) {
        int f = 0, c = getchar(); x = 0;
        while (!isdigit(c)) f |= c == '-', c = getchar();
        while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
        if (f) x = -x;
    }
    template <typename T, typename... Args>
    inline void read(T& x, Args&... args) {
        read(x); read(args...); 
    }
    
    const int maxv = 3000, maxe = 1e5, inf = INT_MAX;
    int dist[maxv], head[maxv], q[maxv];
    bool vis[maxv];
    int v[maxe << 1], cap[maxe << 1], cost[maxe << 1], flow[maxe << 1], next[maxe << 1];
    int n, m, s, t, V, tot = -1;
    
    inline void ae(int x, int y, int ca, int co) {
        v[++tot] = y; cap[tot] = ca; cost[tot] = co; next[tot] = head[x]; head[x] = tot;
        v[++tot] = x; cap[tot] = 0; cost[tot] = -co; next[tot] = head[y]; head[y] = tot;
    }
    inline bool bfs() {
        for (int i = 1; i <= V; ++i)
            dist[i] = inf, vis[i] = 0;
        int l = 0, r = 1;
        dist[t] = 0; vis[q[1] = t] = 1;
        while (l < r) {
            int x = q[++l]; vis[x] = 0;
            for (int i = head[x]; ~i; i = next[i])
                if (cap[i ^ 1] > flow[i ^ 1] && dist[v[i]] > dist[x] - cost[i]) {
                    dist[v[i]] = dist[x] - cost[i];
                    if (!vis[v[i]]) vis[q[++r] = v[i]] = 1;
                }
        }
        return dist[s] < inf;
    }
    int dfs(int x, int cf, int &mc) {
        vis[x] = 1;
        if (x == t || !cf) return cf;
        int getf = 0;
        for (int i = head[x]; ~i; i = next[i])
            if (!vis[v[i]] && cap[i] > flow[i] && dist[v[i]] == dist[x] - cost[i]) {
                int nf = dfs(v[i], std::min(cf, cap[i] - flow[i]), mc);
                if (nf) {
                    flow[i] += nf; flow[i ^ 1] -= nf; getf += nf; cf -= nf;
                    mc += nf * cost[i];
                    if (!cf) break;
                }
            }
        return getf;
    }
    inline void mcmf(int &mc, int &mf) {
        mc = mf = 0;
        while (bfs()) {
            vis[t] = 1;
            while (vis[t]) {
                for (int i = 1; i <= V; ++i)
                    vis[i] = 0;
                mf += dfs(s, inf, mc);
            }
        }
    }
    
    int main() {
        read(m, n);
        s = n + n * m + 1;
        t = V = s + 1;
        for (int i = 1; i <= V; ++i) head[i] = -1;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) {
                int x; read(x);
                for (int k = 1; k <= n; ++k)
                    ae(i, j * n + k, 1, x * k);
            }
        for (int i = 1; i <= n; ++i) ae(s, i, 1, 0);
        for (int i = 1; i <= n * m; ++i) ae(i + n, t, 1, 0);
        int mc, mf; mcmf(mc, mf);
        printf("%.2lf\n", (double)mc / n);
        return 0;
    }
    

    本题有数据加强板:NOI2012的美食节。那道题需要动态加边。不过那题似乎对我这种zkw费用流使用者不太友好,反正我是用EK-spfa过的那题。

    最后说一句,如果认为“人甚至一次也不能踏进同一条河流”,那就是相对主义诡辩论,是错误的。

    • 0
      @ 2025-10-8 17:02:36
      //本题要求最小平均时间,实际上就是转化为求最小时间
      //可以用费用流来解决这个问题 
      //所以可以将每个工人拆成N个点,倒数第K点对应最后修第K辆车的工人。
      //不难发现,倒数第K个顾客需要的等待时间就为(前K辆车的需求时间的总和)*i
      //依照这样建图,再建个超级源点和汇点,跑最小费用最大流 
      #include<bits/stdc++.h>
      using namespace std;
      const int N=1e4+20,inf=0x3f3f3f3f;
      struct edge{int x,y,cost,flow,pre;}a[N<<3];
      int alen,last[N<<1];
      void ins(int x,int y,int flow,int cost){
          a[++alen]={x,y,cost,flow,last[x]};last[x]=alen; 
          a[++alen]={y,x,-cost,0,last[y]};last[y]=alen; 
      }
      int read(){
          int x=0,f=1;char ch=getchar();
          for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
          for(;ch<='9'&&ch>='0';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
          return x*f;
      }
      int d[N],st,ed;
      int cur[N];
      bool v[N]; 
      //费用流板子 
      bool spfa(){
          queue<int> q;
          memset(d,0x3f,sizeof(d));d[st]=0;
          memset(v,0,sizeof(v));
          q.push(st);
          while(q.size()){
              int x=q.front();q.pop();v[x]=0;
              for(int k=last[x];k;k=a[k].pre){
                  int y=a[k].y;
                  if(d[y]>d[x]+a[k].cost&&a[k].flow>0){
                      d[y]=d[x]+a[k].cost;
                      if(!v[y]) v[y]=1,q.push(y);
                  }
              }
          }
          return d[ed]!=d[0];
      }
      int ans;
      int findflow(int x,int f){
          v[x]=1;
          if(x==ed) return ans+=f*d[ed],f;
          int sx=0;
          for(int k=cur[x];k;k=a[k].pre){
              cur[x]=k;
              int y=a[k].y;
              if(a[k].flow>0&&d[y]==a[k].cost+d[x]&&!v[y]){
                  int sy=findflow(y,min(f-sx,a[k].flow));
                  sx+=sy;
                  a[k].flow-=sy,a[k^1].flow+=sy;
                  if(sx==f) return sx;
              }
          }
          v[x]=0;
          return sx;
      }
      int main(){
          alen=1;m=read(),n=read();
          for(int i=1;i<=n;i++){
              for(int j=1;j<=m;j++){
                  int x=read();
                  for(int k=1;k<=n;k++) ins(i,j*n+k,1,x*k);
              }
          }
          st=m*n+n+1;ed=st+1;
          for(int i=1;i<=n;i++) ins(st,i,1,0);
          for(int i=1;i<=m*n;i++) ins(i+n,ed,1,0);
          int t=0;
          while(spfa()){
              v[ed]=1;
              while(v[ed]){
                  memset(v,0,sizeof(v));
                  memcpy(cur,last,sizeof(cur));
                  t+=findflow(st,inf);
              }
          }
          printf("%.2lf",double(ans)/double(n));
      }
      
      • 1

      信息

      ID
      2723
      时间
      1000ms
      内存
      125MiB
      难度
      8
      标签
      递交数
      13
      已通过
      6
      上传者