前段时间,趁着NOIP爆炸后,狂补文化课的空隙,一口气刷了好多网络流的shui ti……
很明显,有两个人,所以是二分图,两两对决可以看作匹配,每个人可以和多个人对决,因此是二分图的多重匹配。我们将s连向byx的人,容量为每个人的寿命。然后手气君的人连向t,容量为寿命。这里提前算好长者续命后的寿命。然后从左向右,当A能打过B时,连一条边,容量为1。最后跑一遍最大流,然后由于只能打m场,要和m取个min。
坑点:两个字符串直接比较是否相等是不行的,我们拿第一个字符出来比才是珂学的。
#include
#include
#include
#include
#include
#include
#define maxn 100010
#define maxm 1000010
#define INF 0x7FFFFFFF
using namespace std;
int n, m, s, t, cur = -1, level[maxn], q[maxn], life1[maxn], life2[maxn];
char byx[maxn][5], sqj[maxn][5];
struct List{
int obj, cap;
List *next, *rev;
}Edg[maxm], *head[maxn], *iter[maxn];
void Addedge(int a, int b, int c){
Edg[++cur].next = head[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
Edg[cur].rev = Edg+(cur^1);
head[a] = Edg+cur;
}
bool bfs(){
int low = 0, high = 0;
q[0] = s;
for(int i = s; i <= t; i++) level[i] = -1;
level[s] = 0;
while(low <= high){
int now = q[low++];
for(List *p = head[now]; p; p = p->next){
int v = p->obj, c = p->cap;
if(c && level[v] == -1){
level[v] = level[now] + 1;
q[++high] = v;
}
}
}
return level[t] != -1;
}
int Dinic(int now, int f){
if(now == t || !f) return f;
int ret = 0;
for(List *&p = iter[now]; p; p = p->next){
int v = p->obj, c = p->cap;
if(c && level[v] > level[now]){
int d = Dinic(v, min(f, c));
f -= d;
p->cap -= d;
ret += d;
p->rev->cap += d;
if(!f) break;
}
}
return ret;
}
int MaxFlow(){
int flow = 0;
while(bfs()){
for(int i = s; i <= t; i++) iter[i] = head[i];
flow += Dinic(s, INF);
}
return flow;
}
int main(){
scanf("%d%d", &n, &m);
int cnt1 = 0, cnt2 = 0;
for(int i = 1; i <= n; i++){
scanf("%s", byx[i]);
if(byx[i][0] == 'Y') cnt1 ++;
}
for(int i = 1; i <= n; i++){
scanf("%s", sqj[i]);
if(sqj[i][0] == 'Y') cnt2 ++;
}
for(int i = 1; i <= n; i++){
scanf("%d", &life1[i]);
if(byx[i][0] == 'J') life1[i] += cnt1;
}
for(int i = 1; i <= n; i++){
scanf("%d", &life2[i]);
if(sqj[i][0] == 'J') life2[i] += cnt2;
}
s = 1; t = s + n + n + 1;
for(int i = s; i <= t; i++) head[i] = NULL;
for(int i = 1; i <= n; i++){
Addedge(s, s+i, life1[i]);
Addedge(s+i, s, 0);
}
for(int i = 1; i <= n; i++){
Addedge(s+n+i, t, life2[i]);
Addedge(t, s+n+i, 0);
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++){
if(byx[i][0] == 'W' && (sqj[j][0] == 'Y' || sqj[j][0] == 'E')){
Addedge(s+i, s+n+j, 1);
Addedge(s+n+j, s+i, 0);
}
if(byx[i][0] == 'J' && (sqj[j][0] == 'W' || sqj[j][0] == 'H')){
Addedge(s+i, s+n+j, 1);
Addedge(s+n+j, s+i, 0);
}
if(byx[i][0] == 'E' && (sqj[j][0] == 'Y' || sqj[j][0] == 'J')){
Addedge(s+i, s+n+j, 1);
Addedge(s+n+j, s+i, 0);
}
if(byx[i][0] == 'Y' && (sqj[j][0] == 'H' || sqj[j][0] == 'J')){
Addedge(s+i, s+n+j, 1);
Addedge(s+n+j, s+i, 0);
}
if(byx[i][0] == 'H' && (sqj[j][0] == 'E' || sqj[j][0] == 'W')){
Addedge(s+i, s+n+j, 1);
Addedge(s+n+j, s+i, 0);
}
}
printf("%d\n", min(MaxFlow(), m));
return 0;
}
无法逃离的最小,不如求可以逃离的最大。我们由s向每个有蜥蜴的格子连边,容量为1,然后每个格子向可以到达的格子连边,容量为INF,但是每个格子有容量限制,于是我们拆点,格子分为入与出,格子间又出向入连,容量依旧INF,每个格子内部入向出连,容量为限制。然后如果一个格子能跳出地图的话,就向t连边,容量为INF。然后跑一遍最大流就是最多能跑掉的蜥蜴了。用总数减之就行了。好简单啊。构图一堆细节,容易敲错。
#include
#include
#include
#include
#include
#include
#define maxn 100010
#define maxm 1000010
#define N 100
#define INF 0x7FFFFFFF
using namespace std;
int R, C, D, s, t, Sum;
int cur = -1, level[maxn], q[maxn];
char m1[N][N], m2[N][N];
struct List{
int obj, cap;
List *next, *rev;
}Edg[maxm], *head[maxn], *iter[maxn];
void Addedge(int a, int b, int c){
Edg[++cur].next = head[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
Edg[cur].rev = Edg+(cur^1);
head[a] = Edg+cur;
}
bool bfs(){
int low = 0, high = 0;
q[0] = s;
for(int i = s; i <= t; i++) level[i] = -1;
level[s] = 0;
while(low <= high){
int now = q[low++];
for(List *p = head[now]; p; p = p->next){
int v = p->obj, c = p->cap;
if(!c || level[v] != -1) continue;
level[v] = level[now] + 1;
q[++high] = v;
}
}
return level[t] != -1;
}
int Dinic(int now, int f){
if(now == t || !f) return f;
int ret = 0;
for(List *&p = iter[now]; p; p = p->next){
int v = p->obj, c = p->cap;
if(c && level[v] > level[now]){
int d = Dinic(v, min(f, c));
f -= d;
p->cap -= d;
p->rev->cap += d;
ret += d;
if(!f) break;
}
}
return ret;
}
int MaxFlow(){
int flow = 0;
while(bfs()){
for(int i = s; i <= t; i++) iter[i] = head[i];
flow += Dinic(s, INF);
}
return flow;
}
int main(){
scanf("%d%d%d", &R, &C, &D);
for(int i = 1; i <= R; i++) scanf("%s", m1[i]);
for(int i = 1; i <= R; i++) scanf("%s", m2[i]);
s = 1; t = s + R * C * 2 + 1;
for(int i = s; i <= t; i++) head[i] = NULL;
for(int i = 1; i <= R; i++)
for(int j = 1; j <= C; j++){
if(m2[i][j-1] == 'L'){
Sum ++;
Addedge(s, s+(i-1)*C+j, 1);
Addedge(s+(i-1)*C+j, s, 0);
}
}
for(int i = 1; i <= R; i++)
for(int j = 1; j <= C; j++){
for(int k = 0; k <= D; k++)
for(int w = 0; w <= D; w++){
if(k + w < 1 || k + w > D) continue;
if(i + k > R || j + w > C || i - k < 1 || j - w < 1){
Addedge(s+R*C+(i-1)*C+j, t, INF);
Addedge(t, s+R*C+(i-1)*C+j, 0);
}
if(i + k <= R && j + w <= C){
Addedge(s+R*C+(i-1)*C+j, s+(i+k-1)*C+j+w, INF);
Addedge(s+(i+k-1)*C+j+w, s+R*C+(i-1)*C+j, 0);
}
if(i + k <= R && j - w > 0){
Addedge(s+R*C+(i-1)*C+j, s+(i+k-1)*C+j-w, INF);
Addedge(s+(i+k-1)*C+j-w, s+R*C+(i-1)*C+j, 0);
}
if(i - k > 0 && j + w <= C){
Addedge(s+R*C+(i-1)*C+j, s+(i-k-1)*C+j+w, INF);
Addedge(s+(i-k-1)*C+j+w, s+R*C+(i-1)*C+j, 0);
}
if(i - k > 0 && j - w > 0){
Addedge(s+R*C+(i-1)*C+j, s+(i-k-1)*C+j-w, INF);
Addedge(s+(i-k-1)*C+j-w, s+R*C+(i-1)*C+j, 0);
}
}
}
for(int i = 1; i <= R; i++)
for(int j = 1; j <= C; j++){
Addedge(s+(i-1)*C+j, s+R*C+(i-1)*C+j, m1[i][j-1]-'0');
Addedge(s+R*C+(i-1)*C+j, s+(i-1)*C+j, 0);
}
printf("%d\n", Sum - MaxFlow());
return 0;
}
这题就是个裸的二分图匹配,之所以是2星难度,是因为转化模型不是特别好想。首先,我们考虑每一行,很容易看出,每一行最终只能保留一列作为最后起作用的那一个。然后我们发现如果某一行啥都没有就肯定是No。我们想到了?匹配。我们再分析一下。如果每一行归属的列确定了,列的任意交换是没有任何关系的,而且每个行选中的列不能相同,这样就一定能满足题目要求。行列一一对应。于是考虑行、列的二分图匹配。
严谨一点,每个二分图匹配的答案必然可以成为答案,因为只要交换行就行了;每个答案也必然满足二分图匹配,若不满足就显然不是答案。我们交换行或列在二分图上没有任何关系,于是成功将问题转化,按题目连边然后直接看看二分图是否满流就行了。
这题其实还算是巧妙的,最后能转换为二分图模型的题目有很多,格子行和列(x,y坐标),格子黑白染色,奇偶性等等都可能迁移过来,GDKOI2017某一道题好像有些类似。。
#include
#include
#include
#include
#include
#include
#define maxn 100010
#define maxm 1000010
#define N 205
#define INF 0x7FFFFFFF
using namespace std;
int T, n, s, t, A[N][N], level[maxn], iter[maxn];
int cur, head_p[maxn], q[maxn];
struct List{
int next, obj, cap;
}Edg[maxm];
void Addedge(int a, int b, int c){
Edg[++cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
head_p[a] = cur;
}
bool bfs(){
q[0] = s;
int head = 0, tail = 0;
for(int i = s; i <= t; i++) level[i] = -1;
level[s] = 0;
while(head <= tail){
int now = q[head++];
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(c && level[v] == -1){
q[++tail] = v;
level[v] = level[now] + 1;
}
}
}
return level[t] != -1;
}
int Dinic(int now, int f){
if(now == t || !f) return f;
int ret = 0;
for(int &i = iter[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(c && level[now] < level[v]){
int d = Dinic(v, min(c, f));
ret += d;
f -= d;
Edg[i].cap -= d;
Edg[i^1].cap += d;
if(!f) break;
}
}
return ret;
}
int MaxFlow(){
int flow = 0;
while(bfs()){
for(int i = s; i <= t; i++) iter[i] = head_p[i];
flow += Dinic(s, INF);
}
return flow;
}
int main(){
scanf("%d", &T);
while(T --){
scanf("%d", &n);
s = 1; t = s + n * 2 + 1;
for(int i = s; i <= t; i++) head_p[i] = -1;
cur = -1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++){
scanf("%d", &A[i][j]);
if(A[i][j]){
Addedge(s+i, s+n+j, INF);
Addedge(s+n+j, s+i, 0);
}
}
for(int i = 1; i <= n; i++){
Addedge(s, s+i, 1);
Addedge(s+i, s, 0);
Addedge(s+n+i, t, 1);
Addedge(t, s+n+i, 0);
}
if(MaxFlow() == n) puts("Yes");
else puts("No");
}
return 0;
}
这题我一开始想着将人拆成T个点,然后跑费用流,但是发现这样有点迷。处理了单独的费用,但是如何处理连边限制好像很难搞。于是我就从另一个角度入手,如果我知道每个人是修到第几辆车不就行了?于是我考虑将一个人拆成n个人,分别代表一个人在n个时间段的状态。一个人可能要先修某一辆车再去修另一辆,这使贡献的等待时间不同。但我们这样连边就解决了一切问题。这样修一辆车时,我们可以算出这个车主的等待时间,而且保证修车同时进行,在修一辆车的时候也不会有人去打扰他(流量限制)。于是答案就是最小的等待时间除以人数,而要求全部修完,就是最小费用最大流了。这题不能贪心能修则修,因为可以等待最快的人修完其他车才轮到你。
本题就是容量为1的费用流。连边就是s向每辆车连容量1,费用0。中间全部连(流出最佳方案),容量INF,费用为对应的时间乘时间段(修时也在等),这是这条流(即这个车主等待)的费用。我们不能算出这辆车对所有人的贡献,因为除了知道车主还在之外不能知道其他人走了没。这也告诉我们费用流要单独考虑一条流的费用。然后每个拆出来的人向t连边,容量1,费用0。跑一遍就得到答案了。
好久没写费用流,但是居然一次就写对了,真感动。(不就是个最短路吗,代码比Dinic短啊)
#include
#include
#include
#include
#include
#include
#define maxn 100010
#define maxm 1000010
#define N 100
#define INF 0x7FFFFFFF
using namespace std;
int m, n, s, t, cur = -1;
int head_p[maxn], q[maxn], preV[maxn], preE[maxn], flow[maxn], dis[maxn], T[N][N];
bool vis[maxn];
struct List{
int next, obj, cost, cap;
}Edg[maxm];
void Addedge(int a, int b, int c, int d){
Edg[++cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
Edg[cur].cost = d;
head_p[a] = cur;
}
bool SPFA(){
for(int i = s; i <= t; i++) dis[i] = INF, vis[i] = false;
q[0] = s;
vis[s] = true;
flow[s] = INF;
dis[s] = 0;
int head = 0, tail = 0;
while(head <= tail){
int now = q[head%maxn];
head ++;
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap, d = Edg[i].cost;
if(!c) continue;
if(dis[now] + d < dis[v]){
dis[v] = dis[now] + d;
flow[v] = min(flow[now], c);
preE[v] = i;
preV[v] = now;
if(!vis[v]){
vis[v] = true;
tail ++;
q[tail%maxn] = v;
if(dis[q[head%maxn]] > dis[q[tail%maxn]]) swap(q[head%maxn], q[tail%maxn]);
}
}
}
vis[now] = false;
}
return dis[t] != INF;
}
int MaxFlowMinCost(){
int res = 0;
while(SPFA()){
res += dis[t] * flow[t];
for(int i = t; i != s; i = preV[i]){
Edg[preE[i]].cap -= flow[t];
Edg[preE[i]^1].cap += flow[t];
}
}
return res;
}
int main(){
scanf("%d%d", &m, &n);
s = 1; t = s + n + m * n + 1;
for(int i = s; i <= t; i++) head_p[i] = -1;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%d", &T[i][j]);
for(int i = 1; i <= n; i++){
Addedge(s, s+i, 1, 0);
Addedge(s+i, s, 0, 0);
}
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++){
Addedge(s+n+(i-1)*n+j, t, 1, 0);
Addedge(t, s+n+(i-1)*n+j, 0, 0);
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
for(int k = 1; k <= n; k++){
Addedge(s+i, s+n+(j-1)*n+k, INF, T[i][j]*k);
Addedge(s+n+(j-1)*n+k, s+i, 0, -T[i][j]*k);
}
printf("%.2lf\n", 1.00 * MaxFlowMinCost() / n);
return 0;
}
终于有了压轴题的感觉。其实这题就是果的最大权闭合子图。APIO时听大神讲过的,当时记得很清楚,但是好像很难写。现在写了一下发现并不难。
首先吃了保护别人的植物才能吃别人,于是就是拿了A就必须拿B的模型,而如果全是正权能拿的拿完就好了,有负权就必须有所取舍的去决策。于是就套模型。将s连向正权植物,容量为权值,如果割掉代表失去正权。负权植物连向t,容量为权值相反数,如果割掉代表获得损失。每个植物向保护它的连边,容量为INF,代表选了这个就要选下一个,最小割割不断它们。
这样s集合代表选,t集合代表不选,中间不能割掉,而且任何一条s-t路径都会导致边的状态不确定,又拿了正权,又不想吃负权,于是这样的路径非法,要被割掉。于是割与方案就对应了。答案就是正权和-最小割。
但是,没有这么简单的哦,植物之间的保护可能成环,这时我们根本就不能获取它们的任何权值,甚至连向这个团的点的权值也不能拿到。我们需要来个拓扑排序搞掉所有的环以及连向环的点。这里不需要Tarjan,假如我们将边反向,拓扑一遍没标记的点就不会在网络里。为什么呢?因为拓扑排序能访问到某点的充要条件是它的入度可能变成0,而一个环无论怎样(除非缩起来),入度是不会通过其它边改成0的,而环搜不到,那环所指向的点的入度也不会变成0了。这些处于“铜墙铁壁”保护的植物就拥有不死之躯了。如此便与图完全对应了。
拓扑时边一定要反向!一开始错在这里。
#include
#include
#include
#include
#include
#include
#define maxn 100100
#define maxm 1000100
#define INF 0x7FFFFFFF
using namespace std;
bool ban[maxn];
int n, m, s, t;
int SUM, in[maxn], head_p[maxn], cnt = -1, cur = -1, level[maxn], iter[maxn], q[maxn], sco[maxn];
struct EDGE{
EDGE *next;
int obj;
}E[maxm], *HEAD[maxn];
struct List{
int next, obj, cap;
}Edg[maxm];
void Insert(int a, int b){
E[++cnt].next = HEAD[a];
E[cnt].obj = b;
HEAD[a] = E+cnt;
}
void Addedge(int a, int b, int c){
Edg[++cur].next = head_p[a];
Edg[cur].obj = b;
Edg[cur].cap = c;
head_p[a] = cur;
}
bool bfs(){
int head = 0, tail = 0;
q[0] = s;
for(int i = s; i <= t; i++) level[i] = -1;
level[s] = 0;
while(head <= tail){
int now = q[head++];
for(int i = head_p[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(!c || level[v] != -1) continue;
level[v] = level[now] + 1;
q[++tail] = v;
}
}
return level[t] != -1;
}
int Dinic(int now, int f){
if(now == t || !f) return f;
int ret = 0;
for(int &i = iter[now]; ~ i; i = Edg[i].next){
int v = Edg[i].obj, c = Edg[i].cap;
if(!c || level[v] <= level[now]) continue;
int d = Dinic(v, min(f, c));
ret += d;
f -= d;
Edg[i].cap -= d;
Edg[i^1].cap += d;
if(!f) break;
}
return ret;
}
int MinCut(){
int flow = 0;
while(bfs()){
for(int i = s; i <= t; i++) iter[i] = head_p[i];
flow += Dinic(s, INF);
}
return flow;
}
void Topo(){
for(int i = 1; i <= n*m; i++) ban[i] = true;
int head = 0, tail = 0;
for(int i = 1; i <= n*m; i++)
if(!in[i]) q[tail++] = i;
tail --;
while(head <= tail){
int now = q[head++];
ban[now] = false;
for(EDGE *p = HEAD[now]; p; p = p->next){
int v = p->obj;
in[v] --;
if(!in[v]) q[++tail] = v;
}
}
}
int main(){
scanf("%d%d", &n, &m);
s = 1; t = s + n * m + 1;
for(int i = s; i <= t; i++) head_p[i] = -1, HEAD[i] = NULL;
int w, x, y;
for(int i = 1; i <= n*m; i++){
scanf("%d", &sco[i]);
scanf("%d", &w);
for(int j = 1; j <= w; j++){
scanf("%d%d", &x, &y);
Insert(i, x*m+y+1);
in[x*m+y+1] ++;
}
if(i % m){
Insert(i+1, i);
in[i] ++;
}
}
Topo();
for(int i = 1; i <= n*m; i++)
for(EDGE *p = HEAD[i]; p; p = p->next){
int v = p->obj;
if(!ban[i] && !ban[v]){
Addedge(s+v, s+i, INF);
Addedge(s+i, s+v, 0);
}
}
for(int i = 1; i <= n*m; i++){
if(ban[i]) continue;
if(sco[i] > 0){
Addedge(s, s+i, sco[i]);
Addedge(s+i, s, 0);
SUM += sco[i];
}
else{
Addedge(s+i, t, -sco[i]);
Addedge(t, s+i, 0);
}
}
printf("%d\n", SUM - MinCut());
return 0;
}
花非花
我心如明镜
但睁眼亦庆幸
唯独我不可以发声
如顽强疾病与命格相衬
——《众生》