@codeforces - 781D@ Axel and Marston in Bitland


@description@

通过以下的方法生成一个序列:
(1)初始时只有一个 "P"。
(2)将当前字符串 s 的 "P" 变成 "B","B" 变成 "P" 得到 s',将 s' 接在 s 之后得到新的序列。
生成的前几个步骤得到字符串为 P,PB,PBBP,PBBPBPPB......

给定一个有向图,每条边上有字符 'P' 或者 'B'。求从点 1 出发走出如上序列的最长路径(即第一步走 s[1], 第二步走 s[2], ...)的最长可能长度。
如果长度 > 10^18,则输出 -1。

Input
第一行两个整数 n, m (1 ≤ n ≤ 500, 0 ≤ m ≤ 2n^2),表示点数与边数。
接下来 m 行,第 i 行三个整数 vi, ui, ti (1 ≤ vi, ui ≤ n, 0 ≤ ti ≤ 1), 表示一条 vi -> ui 的有向边。0 表示 'P',1 表示 'B'。保证无重边,但可能有自环。
Output
如果最长可能长度 > 10^18,输出 -1;否则输出最长可能长度。

@solution@

对于前 2^i 个字符,我们可以看作先走前 2^(i-1) 个字符,再走后 2^(i-1) 个字符(废话)。

同时维护两个东西,一个是题目所说的 PBBPBPPB... 类型的路径 s1,另一个是取反过后的 BPPBPBBP... 类型的路径 s2。
从 2^(i-1) 转移到 2^i,有 s1' = s1 + s2,s2' = s2 + s1。

记 f[0/1][i][x][y],0/1 表示是第一类还是第二类,从 x 出发走 2^i 步是否可以到达 y。
转移时枚举中转点,从 f[0][i-1][x][z] 与 f[1][i-1][z][y] 转移到 f[0][i][x][y],从 f[1][i-1][x][z] 与 f[0][i-1][z][y] 转移到 f[1][i][x][y]。
最后求出 i = 0...60 对应的 f,利用倍增的思想得到最长可能长度(这里的过程类似于倍增求树上 lca 的过程)。
这个复杂度为 O(n^3*logA),其中 A = 10^18。

注意到这个其实就像一个 Floyd 传递闭包,而众所周知这个东西是可以用 bitset 优化的,而且效果还很明显。
那么使用 bitset 过后,复杂度被优化成 O(n^3*logA / w),其中 w = 32 或 64。
然后就可以通过本题了。

@accepted code@

#include <cstdio>
#include <bitset>
using namespace std;
bitset<500>bts[2][61][500], nw, tmp;
int main() {
	int n, m; scanf("%d%d", &n, &m);
	for(int i=1;i<=m;i++) {
		int u, v, p; scanf("%d%d%d", &u, &v, &p), u--, v--;
		bts[p][0][u].set(v, 1);
	}
	for(int p=1;p<=60;p++) {
		for(int k=0;k<n;k++)
			for(int i=0;i<n;i++) {
				if( bts[0][p-1][i][k] ) bts[0][p][i] |= bts[1][p-1][k];
				if( bts[1][p-1][i][k] ) bts[1][p][i] |= bts[0][p-1][k];
			}
	}
	int type = 0; nw.set(0, 1);
	long long ans = 0;
	for(int i=60;i>=0;i--) {
		tmp = 0;
		for(int j=0;j<n;j++)
			if( nw[j] && bts[type][i][j].any() )
				tmp |= bts[type][i][j];
		if( tmp.any() ) {
			ans |= (1LL<<i);
			type ^= 1, nw = tmp;
		}
	}
	if( ans > (long long)(1E18) ) puts("-1");
	else printf("%lld\n", ans);
}

@details@

bitset 真好用.jpg。

posted @ 2019-11-12 22:36  Tiw_Air_OAO  阅读(140)  评论(0编辑  收藏  举报