【Luogu P3597】 [POI2015]WYC

链接:

题目

博客园

题目大意:

给定一个 \(n\) 个点 \(m\) 条边的带权有向图,每条边的边权为 \(w\quad(w\in\{1,2,3\})\),将所有可能路径按边权排序,输出第 \(k\) 小路径权值。

正文:

此题为 NOI2020D1T1 的阉割版本,设一个矩阵数组 \(F_k\),其中每一个 \((u,v)\) 表示从 \(u\)\(v\) 路径长度为 \(2^k\) 的路径有多少条,特别
地,\((i,0)\) 表示以 \(i\) 为终点的长度为 \(2^k\) 的路径有多少条。用倍增的方法,转移 \(2^k\) 次后的矩阵,再从大到小加回来。但显然直接做是不可能的,因为有边权。但是题目给出边权 \(w\in\{1,2,3\}\),考虑拆点。

还有一件事,如果倍增到 \(2^65\) 还没发现大于 \(k\) 的直接输 \(-1\)

代码:

代码没卡常,开 O2 过了,这个题需要稍微卡卡常。

struct matrix
{
	ll mat[N * 3][N * 3];
	inline ll* operator [] (int b) { return mat[b];}
	matrix(){ memset(mat, 0, sizeof mat);}
}F[logT + 5], tmp, now;
int n, m;
ll k, ans;

inline matrix operator * (matrix &a, matrix &b)
{
	matrix c; 
	for (int k = 0; k <= 3 * n; k++)
		for (int i = 0; i <= 3 * n; i++)
			for (int j = 0; j <= 3 * n; j++)
				c[i][j] += a[i][k] * b[k][j];
	return c;
}


bool check (matrix a)
{
	ll sum = 0;
	for (int i = 1; i <= n; i++)
		if(k <= (sum += a[i][0] - 1)) return 1;
	return 0;
}

signed main()
{
	scanf ("%d%d%lld", &n, &m, &k);
	for (int i = 1; i <= n; i++)
	{
		F[0][i][0] = F[0][i][i + n] = F[0][i + n][i + 2 * n] = 1;
	}
	for (int i = 1; i <= m; i++)
	{
		int u, v, w;
		scanf ("%d%d%d", &u, &v, &w);
		++F[0][u + n * (w - 1)][v];
	}
	F[0][0][0] = 1; 
	int logt = 1;
	for (; logt < logT + 5; logt++)
	{
		if(logt > 65)
		{
			puts("-1");
			return 0;
		}
		F[logt] = F[logt - 1] * F[logt - 1];
		if(check(F[logt])) break;
	}
	for (int i = 0; i <= 3 * n; i++)
		now[i][i] = 1;
	for (; --logt >= 0; )
	{
		tmp = now * F[logt];
		if(!check(tmp)) now = tmp, ans += (1ll << logt);
	}
	printf("%lld", ans);
	return 0;
}

posted @ 2020-08-24 08:08  Jayun  阅读(133)  评论(0编辑  收藏  举报