【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;
}