目录
题意:
一间房间有n个插座,有m个电器要使用一些插座(有的插座没在n个里面),有k个转换器,每个转换器可以插在相应的插座或者转换器上(插上之后相当于插座变成另一种插座),每个转换器上可以插相应的插头或者转换器(转换器可以当作插头看待)。要求尽可能多的插头能充电,问最少有多少电器充不上点。
- n,m,k<=100
- 插座,插头都有不超过24位的字符串表示
- n个插座各不一样(题目好像没有明显说)
- 转换器的种类无穷多
- 输入:n->n个插座->m->m个电器名称以及插头种类(只能插相应种类的插座)->k->k个转换器插的(插座/转换器。可以视作插在插座上面就变成了另一种插座,同时视转换器本身为插头)的种类以及可以插在它上面的(插头/转换器)的种类
- 输出:一个整数,最少的充不上电的电器的数量
关于读题:
- 题目阅读真的只一间难事,尽量多习惯英文阅读罢,翻译出来的东西太TM感人了(主要是区域赛都是这类型emm)。
- 不要浮躁,急于求出,急功近利。就慢慢读题,慢慢写题,你感觉自己太慢了,才会更觉得轻松,更觉得自己学有余力。——慢才能保证效率,觉得自己学有余力才会更有动力。
题解:
- 无他,读题+建图。
- 图:源点->插头->插座->汇点(到达汇点就表示匹配到了一对)
关于建图标点:
一个一个标,就不必特地的计算拿个点表示的是什么(大概只是一些情况用得到,但实测相当好用)
代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <string>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const int N = 2e3 + 10; // 100个点?
const int M = 2e3 + 10;
const int INF = 1e9 + 10;
int n, m, k;
int s, t;
struct Edge {
int v, w, next;
Edge(int v = 0, int w = 0, int next = 0) : v(v), w(w), next(next) {}
} e[M];
int head[N], now[N], cnt;
void add(int u, int v, int w) {
e[cnt] = Edge(v, w, head[u]), head[u] = cnt++;
e[cnt] = Edge(u, 0, head[v]), head[v] = cnt++;
}
int dep[N];
// bfs使dfs按照dep来增广,得到优化(具体证明就看不太懂了)。
bool bfs() {
memset(dep, -1, sizeof(dep));
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].v;
if (dep[v] == -1 && e[i].w > 0) {
dep[v] = dep[x] + 1;
now[v] = head[v]; //当前弧优化(初始化)。
q.push(v);
if (v == t) return true;
//遇到v==t即退出,因为<dep[v]的已经遍历完了,dfs的增广路不可能经过深度>=dep[v]的点
}
}
}
return false;
}
//一次dfs可能有几条增广路:见图
int dfs(int x, int flow) {
if (x == t) return flow;
int ans = 0;
// flow表示x点还能操作的流量,ans表示增广的这几路后流出x总最大流
// flow不剩下了,就没必要增广了
for (int i = now[x]; ~i && flow; i = e[i].next) {
int v = e[i].v;
now[x] = i;
//当前弧优化(这里才能得到优化,bfs中的只是初始化:另now[x]=head[x])
if (dep[v] == dep[x] + 1 && e[i].w > 0) {
int tmp = dfs(v, min(flow, e[i].w));
// tmp为流过点v的最大流量
if (tmp == 0) dep[v] = -1; //剪枝,去掉增广完毕的点
e[i].w -= tmp;
e[i ^ 1].w += tmp;
ans += tmp;
flow -= tmp; //流过当前点(x)的流量累加(回溯)
}
}
return ans; //最后返回的是这几路增广的流量和
}
int Dinic() {
int maxflow = 0;
// bfs为真就一直dfs多路增广
while (bfs()) maxflow += dfs(s, INF);
return maxflow;
}
map<string, int> mp;
string s0, s1;
int id;
void init() {
memset(head, -1, sizeof(head));
read(n);
s = ++id, t = ++id; //这也许,是一种极好的设置下标的方式
for (int i = 1; i <= n; i++) {
cin >> s0;
mp[s0] = ++id;
add(id, t, 1); //每种插座只有一个,还有插头需要的插座可能没有出现
//插座与汇点相连
}
read(m);
for (int i = 1; i <= m; i++) {
cin >> s0 >> s1;
add(s, ++id, 1); //源点与插头相连。插头设点
//需要但是没有的插座(就看后面转换器adapter有没有转机)
if (!mp[s1]) {
mp[s1] = ++id;
//, add(id, t, 1); //插座全与汇点t相连
//注意了,并不是所有插座都与汇点相连,这种只配使用转换器,不能直接出结果(到t就相当关于直接完成匹配)
add(id - 1, id, 1); //插头插在插座上
} else
add(id, mp[s1], 1); //插头插在插座上
}
read(k);
for (int i = 1; i <= k; i++) {
cin >> s0 >> s1;
if (!mp[s0]) mp[s0] = ++id;
if (!mp[s1]) mp[s1] = ++id;
add(mp[s0], mp[s1], INF); //无成本将s0插座转换为s1(不需要用到s1)
}
}
signed main() {
init();
int maxflow = Dinic();
print(m - maxflow, '\n');
return 0;
}
/*
1 1
1
1
2 2
1 0
1 0
1 1
*/