• 【BZOJ】2879: [Noi2012]美食节


    题意

    (m)个厨师,(n)种菜,每种菜需要做(p_i)份,每个厨师做第(i)种菜用时(t_{i, j})。一个厨师做完一道菜才能做下一道。每份菜的时间是这个厨师做完这道菜的用时加上之前做过的菜的用时。问做完所有的菜的最小用时是多少。($ n le 40, m le 100, sum p_i le 800, t_{i, j} le 1000 $)

    分析

    可以考虑每个厨师做的每一道菜。最后做的菜对时间贡献了一次,倒数第二的菜对时间贡献了两次。于是我们考虑每个厨师倒着做的菜品即可。

    题解

    将每个厨师拆成(sum p_i)个点,表示这个菜是倒数第(i)次做的。但是这样一次性把图建出来会tle。所以我们要优化。
    考虑到每个厨师倒数第一个菜一定在倒数第二个菜先做,所以我们按照这个顺序来建图即可,即增广一次建一次。
    具体做法是:
    首先加入(m)个点,表示每个厨师倒数第一次做的菜。源(S)向这些点连边,容量(1),费用为(0)。再加入(n)个点,表示菜类,每个点向汇连边,容量为(p_i),费用为(0)。然后在厨师的点集中向菜类点集连边,容量为(1),费用为(t_{i, j})
    然后增广一条路径。此时找出被增广的厨师,再新建一个点,表示这个厨师第二次做的菜。源(S)向这个点连边,容量为(1),费用为(0)。然后向菜类连边,容量为(1),费用为(2 t_{i, j})。依次类推。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=45, M=105, nN=N+M+1000, nE=N*(M+800)*8, oo=0x3f3f3f3f;
    int ihead[nN], cnt=1;
    struct E {
    	int next, from, to, cap, w;
    }e[nE];
    void add(int x, int y, int cap, int w) {
    	e[++cnt]=(E){ihead[x], x, y, cap, w}; ihead[x]=cnt;
    	e[++cnt]=(E){ihead[y], y, x, 0,  -w}; ihead[y]=cnt;
    }
    bool spfa(int s, int t, int n, int &ans) {
    	static int d[nN], q[nN], p[nN], fr, ta;
    	static bool vis[nN];
    	memset(d, 0x3f, sizeof(int)*(n+1));
    	fr=ta=0;
    	d[s]=0;
    	q[ta++]=s;
    	while(fr!=ta) {
    		int x=q[fr++];
    		fr=fr==nN?0:fr;
    		vis[x]=0;
    		for(int i=ihead[x]; i; i=e[i].next) {
    			if(!e[i].cap) {
    				continue;
    			}
    			int y=e[i].to;
    			if(d[y]>d[x]+e[i].w) {
    				d[y]=d[x]+e[i].w;
    				p[y]=i;
    				if(!vis[y]) {
    					vis[y]=1;
    					q[ta++]=y;
    					ta=ta==nN?0:ta;
    				}
    			}
    		}
    	}
    	if(d[t]==oo) {
    		return 0;
    	}
    	for(int x=t; x!=s; x=e[p[x]].from) e[p[x]].cap--, e[p[x]^1].cap++;
    	ans+=d[t];
    	return 1;
    }
    int n, m, p[N], sum, pos[M], num[M], t[N][M], nu[N];
    int main() {
    	int ans=0;
    	scanf("%d%d", &n, &m);
    	for(int i=1; i<=n; ++i) {
    		scanf("%d", &p[i]);
    		sum+=p[i];
    	}
    	int S=n+m+sum+1, T=S+1;
    	for(int i=1; i<=n; ++i) {
    		add(S, i, p[i], 0);
    		for(int j=1; j<=m; ++j) {
    			scanf("%d", &t[i][j]);
    		}
    	}
    	for(int j=1; j<=m; ++j) {
    		int id=n+j;
    		add(id, T, 1, 0);
    		num[j]=1;
    		pos[j]=cnt;
    		for(int i=1; i<=n; ++i) {
    			add(i, id, 1, t[i][j]);
    		}
    	}
    	int tot=n+m;
    	while(spfa(S, T, T, ans)) {
    		int j=0;
    		for(j=1; j<=m && !e[pos[j]].cap; ++j);
    		++num[j];
    		++tot;
    		add(tot, T, 1, 0);
    		pos[j]=cnt;
    		for(int i=1; i<=n; ++i) {
    			add(i, tot, 1, num[j]*t[i][j]);
    		}
    	}
    	printf("%d
    ", ans);
    	return 0;
    }
  • 相关阅读:
    Java数据类型转换(自动转换和强制转换)
    Java数据类型以及变量的定义
    Java8新特性_日期时间新类 LocalDate、LocalTime、LocalDateTime
    Java8新特性_接口中的默认方法
    java8 新特性 Optional容器类
    Java8新特性 并行流与串行流 Fork Join
    Java8新特性_stream API 练习
    IDEA导入JUnit4
    Reduce:规约;Collector:收集、判断性终止函数、组函数、分组、分区
    【Linux】排序命令sort
  • 原文地址:https://www.cnblogs.com/iwtwiioi/p/4985796.html
Copyright © 2020-2023  润新知