链接:
题目大意:
给定一个 (n) 个点 (m) 条边的带权有向图,每条边的边权为 (wquad(win{1,2,3})),将所有可能路径按边权排序,输出第 (k) 小路径权值。
正文:
此题为 NOI2020D1T1 的阉割版本,设一个矩阵数组 (F_k),其中每一个 ((u,v)) 表示从 (u) 到 (v) 路径长度为 (2^k) 的路径有多少条,特别
地,((i,0)) 表示以 (i) 为终点的长度为 (2^k) 的路径有多少条。用倍增的方法,转移 (2^k) 次后的矩阵,再从大到小加回来。但显然直接做是不可能的,因为有边权。但是题目给出边权 (win{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;
}