目录
传送门
题意:题意迷得很,最后也是看网友的才看懂。
每台计算机有p(<=10)个零件,一个公司有n(<=50)个工厂。每个工厂加工一些半成品,输入(得到)一些指定的零件,最多能够输出指定套(指定的是总输出上限,最多不能输出输入套)指定的文件。
n行,第 i 行第一个值是第 i 个工厂的总输出的上限 ,然后是p个数表示输入的零件,0表示一定不能有该零件,1表示一定要有该零件,2表示无所谓,接着是输出的零件,0表示有该零件,1表示没有。
问每多能造出多少台电脑(当所有输出都有,即都为1的时候表示可以造一台电脑),最开始什么也没有(无中生有)。
题解:建图(点权转化为边权)+最大流Dinic
1).有一个点权,我们转化为边权。
点权转化为边权
2)建图注意,超级源点s=0,超级汇点t=2*n+1。超级源点连接所有输入要求全为0的工厂,所有输出要求全为1的工厂连接超级汇点。
除此之外:任意两个工厂之间满足要求则可以互相连接。
说明:1)感觉真的难建图,甚至有时候觉得题目条件没给玩(这个可能只是这个题的错觉)
2)最开始什么也没有;输出全为1表示可以造一台电脑。
代码:
(ps:等刷了一些题之后要总结一个更简单,更正确的模板——理解的更透彻)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#include <vector>
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
using namespace std;
const int N = 100 + 10; //节点数2*n
const int M = 1e3 + 10; //边最多n*n+2*n=2500+100
const int INF = 1e9 + 10;
int p, n; // p个零件,n个工厂
int a[N][23]; //数量,以及入,出
int s, t; //源点与汇点
int head[N], now[N], cnt; //注意最后节点编号为 0~2*n+1
struct Edge {
int x, to, w, next;
Edge(int x = 0, int to = 0, int w = 0, int next = 0)
: x(x), to(to), w(w), next(next) {}
} e[M];
vector<int> dp; //记录下特定的边—— out_x -> in_y
struct node {
int x, y, w;
node(int x = 0, int y = 0, int w = 0) : x(x), y(y), w(w) {}
};
vector<node> ans;
void add(int u, int v, int w) {
e[cnt] = Edge(u, v, w, head[u]), head[u] = cnt++;
e[cnt] = Edge(v, u, 0, head[v]), head[v] = cnt++;
}
//点权——拆点:分为入点(1~n)和出点(n+1~n+n),同时将入点和出点连接,权值为点权
void build() {
for (int i = 1; i <= n; i++) {
add(i, n + i, a[i][1]);
bool in = true, out = true;
for (int k = 1; k <= p; k++)
if (a[i][k + 1] == 1) in = false;
for (int k = 1; k <= p; k++)
if (a[i][p + 1 + k] == 0) out = false;
if (in) add(s, i, INF); //超级源点
if (out) add(i + n, t, INF); //超级汇点
for (int j = 1; j <= n; j++) {
if (i == j) continue;
bool ok = true;
for (int k = 1; k <= p; k++) {
if (a[j][1 + k] == 2) continue;
//注意下标,注意是p还是n
if (a[i][1 + p + k] == 1 && a[j][1 + k] == 0) ok = false;
if (a[i][1 + p + k] == 0 && a[j][1 + k] == 1) ok = false;
}
if (ok) dp.pb(cnt), add(i + n, j, INF); //答案只取这里的点
}
}
}
int dep[N];
bool bfs() {
for (int i = 0; i <= 2 * n + 1; i++) dep[i] = INF; //还是INF好用
queue<int> q;
q.push(s), dep[s] = 0;
now[s] = head[s];
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = now[x]; ~i; i = e[i].next) {
int v = e[i].to;
now[v] = head[v];
if (dep[v] == INF && e[i].w) {
dep[v] = dep[x] + 1;
if (v == t) return true;
q.push(v);
}
}
}
// for (int i = s; i <= t; i++) cout << i << " " << dep[i] << endl;
return false;
}
int dfs(int x, int flow) {
if (x == t) return flow;
int ans = 0;
for (int i = now[x]; ~i && flow; i = e[i].next) {
int v = e[i].to;
now[x] = i;
//满足基本条件再能继续。dep[v]=-1无解,dep[x]=-1呢,除了dep[s],其他不可能为0。所以另dep[v]=-1是没有大问题的
//一个算法,想要运用好,需要注意的东西是很多的!所以acm不是能纯抄模板就能打好的,必须要掌握很多算法思维。
//还是建议弄成INF吧(虽然都可以)
if (e[i].w && dep[v] == dep[x] + 1) {
int tmp = dfs(v, min(flow, e[i].w));
if (tmp == 0) dep[v] = INF; //剪枝,结束的就不搞了
e[i].w -= tmp;
e[i ^ 1].w += tmp;
ans += tmp;
flow -= tmp;
}
}
return ans;
}
int Dinic() {
int res = 0;
while (bfs()) {
// for (int i = 0; i <= 2 * n + 1; i++) now[i] = head[i];
//就不放在的bfs了。费用流需要每次都更新就放在循环里面
//最终证明只能够放在bfs或者循环当中,不能放在外面
res += dfs(s, INF);
}
return res;
}
void init() {
memset(head, -1, sizeof(head));
cnt = 0, ans.clear(), dp.clear();
s = 0, t = 2 * n + 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= 2 * p + 1; j++) read(a[i][j]);
build(); //建图也是一个难点
}
signed main() {
while (cin >> p >> n) {
init();
int maxflow = Dinic();
print(maxflow, ' ');
for (int i = 0; i < dp.size(); i++) {
//反向边有值,dp[i]为正向边
if (e[dp[i] ^ 1].w)
ans.pb(node(e[dp[i]].x - n, e[dp[i]].to, e[dp[i] ^ 1].w));
//注意出边是+n了的,现在就要减回来
}
print(ans.size(), '\n');
for (int i = 0; i < ans.size(); i++)
printf("%d %d %d\n", ans[i].x, ans[i].y, ans[i].w);
}
return 0;
}