【jzoj 7185】Monito / 管道监控(KMP)(网络流)

91 篇文章 0 订阅
53 篇文章 1 订阅

Monito / 管道监控

题目链接:jzoj 7185

题目大意

有一个图,路径有一个字母。
然后你有一些操作,每个操作有费用和一串字符串。
然后你可以把它从一个点开始,按着字符串的字符一个一个走路径。
(当然要能走才可以放)
然后问你要让所有的边都被走过至少一次最小的费用。

思路

这道题正解是 DP。
但是,我们看看这道题
我们发现这题似乎可以用网络流来做?
对于每个操作字符串我们把它自己 KMP 一下,然后丢进去原图跑,找到原图所有它的字符串,然后想这道题那样连边。

然后由于所有点需求都是一,我们这样搞,如果一个点入读比初度大 x x x,那就留 x x x 到终点,如果少 x x x,就起点留 x x x 到它。
然后跑网络流,最后的答案就有了。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

struct node {
	char x;
	int to, nxt;
}e[501];
struct rd {
	ll x, to, nxt, op, val;
}e_[601 * 601];
int n, m, t, x, stn, le[501], KK, nxt[1000001], find_num;
int fa[501][501], deg[601], asktot, use[601][601], re, up[601];
int le_[601], KK_, S, T, chu[501], bll[601 * 601], le_e[601];
ll dis[601], ask[601 * 601][4], ans;
queue <int> q;
char c, s[1000001];
bool in[1001], cs[501];

int read() {
	re = 0;
	c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re;
}

void addl(int x, int y, char c) {
	e[++KK] = (node){c, y, le[x]}; le[x] = KK;
}

void add(int x, int y, ll z, ll val, int bh) {
	if (use[x][y] && e_[use[x][y]].val <= val) return ;
	if (!use[x][y]) {//丢进去匹配
		e_[++KK_] = (rd){z, y, le_[x], KK_ + 1, val}; le_[x] = KK_;
		e_[++KK_] = (rd){0, x, le_[y], KK_ - 1, -val}; le_[y] = KK_;
		if (bh) {
			asktot++;
			bll[KK_ - 1] = asktot;
			ask[asktot][0] = KK_ - 1;
			ask[asktot][1] = x;
			ask[asktot][2] = z;
			ask[asktot][3] = bh;
		}
		use[x][y] = KK_ - 1;
	}
	else {//同一个区间可能会多次碰到,那我们只要再有更优的时候更新 val 就可以了
		e_[use[x][y]].val = val;
		e_[e_[use[x][y]].op].val = -val;
		if (bll[use[x][y]]) ask[bll[use[x][y]]][3] = bh;
	}
}

void dfs(int now, int j, int bh) {
	if (j == stn) {//匹配到了
		if (!use[now][fa[now][j - 1]])
			up[now] = max(up[now], j);
		add(now, fa[now][j - 1], INF, x, bh);
	}
	
	int tmpj = j;
	for (int i = le[now]; i; i = e[i].nxt) {
		while (j && e[i].x != s[j + 1]) j = nxt[j];
		if (e[i].x == s[j + 1]) j++;
		dfs(e[i].to, j, bh);
		
		j = tmpj;
	}
}

//网络流
bool SPFA() {
	for (int i = 0; i <= T; i++) dis[i] = 1e18;
	memset(deg, 0, sizeof(deg));
	for (int i = 1; i <= T; i++) le_e[i] = le_[i];
	deg[S] = 1;
	dis[S] = 0;
	in[S] = 1;
	q.push(S);
	while (!q.empty()) {
		int now = q.front();
		q.pop();
		
		for (int i = le_[now]; i; i = e_[i].nxt)
			if (dis[e_[i].to] > dis[now] + e_[i].val && e_[i].x) {
				dis[e_[i].to] = dis[now] + e_[i].val;
				deg[e_[i].to] = deg[now] + 1;
				if (!in[e_[i].to]) {
					in[e_[i].to] = 1;
					q.push(e_[i].to);
				}
			}
		
		in[now] = 0;
	}
	return dis[T] != dis[0];
}

ll Dfs(int now, ll sum) {
	if (now == T) return sum;
	
	ll go = 0;
	in[now] = 1;
	for (int &i = le_e[now]; i; i = e_[i].nxt)
		if (e_[i].x && dis[e_[i].to] == dis[now] + e_[i].val && deg[e_[i].to] == deg[now] + 1 && !in[e_[i].to]) {
			ll this_go = Dfs(e_[i].to, min(sum - go, e_[i].x));
			if (this_go) {
				e_[i].x -= this_go;
				e_[e_[i].op].x += this_go;
				go += this_go;
				if (go == sum) {
					in[now] = 0;
					return go;
				}
			}
		}
	
	if (go != sum) dis[now] = -1;
	in[now] = 0;
	return go;
}

void dinic() {
	while (SPFA())
		ans += dis[T] * Dfs(S, INF);
}

int main() {
//	freopen("read.txt", "r", stdin);
	
	n = read(); m = read(); t = read();
	
	S = n + 1;
	T = n + 2;
	
	for (int i = 2; i <= n; i++) {
		x = read();
		c = getchar();
		while (c < 'a' || c > 'z') c = getchar();
		addl(x, i, c);
		fa[i][0] = x;
		add(x, i, INF, 0, 0);
		chu[x]++;
	}
	
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			fa[i][j] = fa[fa[i][j - 1]][0];
	
	for (int i = 1; i <= m; i++) {
		x = read();
		scanf("%s", s + 1);
		stn = strlen(s + 1);
		
		int j = 0;
		for (int i = 2; i <= stn; i++) {//跑 KMP
			while (j != 0 && s[i] != s[j + 1]) j = nxt[j];
			if (s[i] == s[j + 1]) j++;
			nxt[i] = j;
		}
		
		dfs(1, 0, i);
	}
	add(1, T, INF, 0, 0);
	
	for (int i = 1; i <= n; i++)
		if (!chu[i]) add(S, i, 1, 0, 0);
			else if (chu[i] > 1) add(i, T, chu[i] - 1, 0, 0);
	
	dinic();
	
	for (int i = 1; i <= n; i++) {
		int tmp = i;
		for (int j = 1; j <= up[i]; j++) {
			if (!cs[tmp]) {
				cs[tmp] = 1;
				find_num++;
			}
			tmp = fa[tmp][0];
		}
	}
	
	if (find_num != n - 1) printf("-1");
		else {
			printf("%lld\n", ans);
			if (t == 1) {
				int num = 0;
				for (int i = 1; i <= asktot; i++) {//直接一个一个看有没有流
					if (e_[ask[i][0]].x < ask[i][2]) {
						num++;
					}
				}
				printf("%d\n", num);
				for (int i = 1; i <= asktot; i++) {
					if (e_[ask[i][0]].x < ask[i][2]) {
						printf("%lld %lld %lld\n", e_[ask[i][0]].to, ask[i][1], ask[i][3]);
					}
				}
			}
		}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值