ABC375
E
题目大意:
\(n\)个人,每个人初始在第\(a_i\)组,有一个力量值\(b_i\)。需要调整某些人到其他组去,使得每个组的力量值相等。若能,求出最少调整的人数。若不能,输出\(-1\)。\(n\leq 100,1\leq b_i,\sum b_i \leq 1500,a_i\in\{1,2,3\}\)。
分析:
暴力dfs显然不行,还要求最少调整的人数,只能是dp了呀!至于dp的状态,题目里面就只有\(a_i\)和\(b_i\),显然就用\(b_i\)来设状态。设\(dp[i][x][y]\)表示考虑到第\(i\)个人,前\(i-1\)个人的第一组的力量值之和为\(x\),第二组的力量值之和为\(y\)。此处特别注意:\(i\)这一维是有必要的,不可以省略!因为转移过程为:前\(i-1\)个的和分别是\(x\)和\(y\),然后考虑当前的\(i\)放在那一组后进行转移。如果没有\(i\)这一维,意味着无法体现一个一个考虑的过程了,即自己可以贡献自己,这是不行的!用滚动数组优化,复杂度\(O(n(\sum b_i) ^ 2)\)。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, s[4], a[105], b[105], dp[2][1505][1505];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d%d", &a[i], &b[i]), s[a[i]] += b[i], s[0] += b[i];
if (s[0] % 3) {puts("-1"); return 0;}
memset(dp, 0x3f, sizeof(dp)); dp[0][s[1]][s[2]] = dp[1][s[1]][s[2]] = 0;
for (int k = 1; k <= n; k ++ )
{
int o = (k & 1);
for (int i = 0; i <= 1500; i ++ )
{
for (int j = 0; j <= 1500; j ++ )
{
dp[o][i][j] = min(dp[o][i][j], dp[o ^ 1][i][j]);
if (a[k] == 1)
{
if (i >= b[k] && j + b[k] <= 1500) dp[o][i - b[k]][j + b[k]] = min(dp[o][i - b[k]][j + b[k]], dp[o ^ 1][i][j] + 1);
if (i >= b[k]) dp[o][i - b[k]][j] = min(dp[o][i - b[k]][j], dp[o ^ 1][i][j] + 1);
}
if (a[k] == 2)
{
if (i + b[k] <= 1500 && j >= b[k]) dp[o][i + b[k]][j - b[k]] = min(dp[o][i + b[k]][j - b[k]], dp[o ^ 1][i][j] + 1);
if (j >= b[k]) dp[o][i][j - b[k]] = min(dp[o][i][j - b[k]], dp[o ^ 1][i][j] + 1);
}
if (a[k] == 3)
{
if (i + b[k] <= 1500) dp[o][i + b[k]][j] = min(dp[o][i + b[k]][j], dp[o ^ 1][i][j] + 1);
if (j + b[k] <= 1500) dp[o][i][j + b[k]] = min(dp[o][i][j + b[k]], dp[o ^ 1][i][j] + 1);
}
}
}
}
printf("%d\n", dp[n & 1][s[0] / 3][s[0] / 3] > n ? -1 : dp[n & 1][s[0] / 3][s[0] / 3]);
return 0;
}
F
题目大意:
\(n\)座城市,\(m\)条无向的道路,第\(i\)条连接\(a_i\)和\(b_i\),长度为\(c_i\)。\(q\)次询问,分为两类。第一类:删除第\(i\)条边(永久)。第二类:求出从\(x\)到\(y\)的最短路,不存在输出\(-1\)。\(2\leq n \leq 300,0\leq m \leq \frac{n(n-1)}{2},1\leq c_i \leq 10 ^ {9},1\leq q \leq 2 \times 10 ^ 5\)。保证无自环重边。
分析:
经典的一个套路呀!删边不好维护,改成加边呗。要求任意两点最短路,首先floyd预处理,\(f[x][y]\)表示\(x\)到\(y\)的最短路。从后往前处理每次询问。第一类,加上这条边,然后计算新的\(f\)数组。加上某条边\((a,b)\)后,\(x\)到\(y\)的最短路分成三种情况:
- 不经过这条边
- 经过这条边,路径为\(x->a->b->y\)
- 经过这条边,路径为\(x->b->a->y\)
特别注意:第二和三种情况都要考虑到,因为是无向边!而且这两个的结果显然不一定相等!
这三种取\(min\)即可。第二类,直接输出答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n, m, q, cnt, v[50005], a[50005], b[50005], c[50005], p[200005][3];
ll ans[200005], f[305][305], nf[305][305];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= m; i ++ ) scanf("%d%d%d", &a[i], &b[i], &c[i]);
for (int i = 1; i <= q; i ++ )
{
scanf("%d", &p[i][0]);
if (p[i][0] == 1) scanf("%d", &p[i][1]), v[p[i][1]] = 1;
else scanf("%d%d", &p[i][1], &p[i][2]);
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i != j)
f[i][j] = 1e16;
for (int i = 1; i <= m; i ++ )
if (v[i] != 1)
f[a[i]][b[i]] = f[b[i]][a[i]] = c[i];
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
for (int i = q; i >= 1; i -- )
{
if (p[i][0] == 1)
{
for (int x = 1; x <= n; x ++ )
for (int y = 1; y <= n; y ++ )
nf[x][y] = min(f[x][y], min(f[x][a[p[i][1]]] + c[p[i][1]] + f[b[p[i][1]]][y], f[x][b[p[i][1]]] + c[p[i][1]] + f[a[p[i][1]]][y]));
for (int x = 1; x <= n; x ++ )
for (int y = 1; y <= n; y ++ )
f[x][y] = nf[x][y];
}
else ans[ ++ cnt] = (f[p[i][1]][p[i][2]] > 1e13) ? -1 : f[p[i][1]][p[i][2]];
}
for (int i = cnt; i >= 1; i -- ) printf("%lld\n", ans[i]);
return 0;
}
G
题目大意:
\(n\)座城市,\(m\)条无向的道路,第\(i\)条连接\(a_i\)和\(b_i\),长度为\(c_i\)。\(\forall i \in [1,m]\),判断下面两个值是否相等。
- \(1\)到\(n\)的最短路的长度
- \(1\)到\(n\)不经过第\(i\)条边的最短路的长度
若是,输出\(Yes\),否则输出\(No\)。\(2 \leq n \leq 2 \times 10 ^ 5,1\leq m \leq 2 \times 10 ^ 5,1\leq c_i \leq 10 ^ 9\)。保证无自环重边。所有边都可经过时,从\(1\)可以到\(n\)。
分析:
首先dijkstra预处理从城市\(1\)到每座城市的最短路(记为\(d1[]\))和最短路的条数(记为\(num1[]\)),以及从城市\(n\)到每座城市的最短路(记为\(d2[]\))和最短路的条数(记为\(num2[]\))。题目即要求我们判断每条边\((a,b,c)\)是否一定在任意的最短路上。这需要同时满足两个条件:
- 这条边在某一条最短路上,即:\(d1[a]+d2[b]+c=d1[n]\)
- 从\(1\)到\(a\)的最短路的条数乘上从\(b\)到\(n\)的最短路的条数,等于从\(1\)到\(n\)的最短路的条数。
最短路的条数,在求最短路的过程中顺便求即可。若\(d1[y] > d1[x] + z\),那么更新\(d1[y]=d1[x]+z\)的同时有\(num1[y]=num1[x]\),因为\(y\)由\(x\)唯一继承而来。否则若\(d1[y] = d1[x] + z\),那么有\(num1[y]+=num1[x]\),因为到\(x\)的最短路都可以贡献到\(y\)。
特别注意:因为是无向边,所以需要把\(a,b\)互换再判断一次,两次中有一次满足即输出\(Yes\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int mod = 998244853;
priority_queue < pair < int, int > > q;
int n, m, s, tot, num[200005], nm[200005][2], d[200005][2], hd[200005], dis[200005], vis[200005], to[400005], nxt[400005], w[400005];
int read()
{
int x = 0, fl = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') fl = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
return x * fl;
}
void add(int x, int y, int z)
{
tot ++ ;
to[tot] = y;
nxt[tot] = hd[x];
w[tot] = z;
hd[x] = tot;
return;
}
void Dij(int s)
{
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
for (int i = 1; i <= n; i ++ )
dis[i] = 1e18;
dis[s] = 0; num[s] = 1;
q.push(make_pair(0, s));
while (!q.empty())
{
pair < int, int > h = q.top(); q.pop();
int x = h.second;
if (vis[x]) continue; vis[x] = 1;
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i], z = w[i];
if (dis[y] > dis[x] + z)
{
dis[y] = dis[x] + z;
q.push(make_pair(-dis[y], y));
num[y] = num[x];
}
else if (dis[y] == dis[x] + z)
{
num[y] = (num[y] + num[x]) % mod;
}
}
}
return;
}
signed main()
{
n = read(); m = read();
s = 1;
for (int i = 1; i <= m; i ++ )
{
int x = read(), y = read(), z = read();
add(x, y, z); add(y, x, z);
}
Dij(s);
for (int i = 1; i <= n; i ++ ) d[i][0] = dis[i], nm[i][0] = num[i];
s = n;
Dij(s);
for (int i = 1; i <= n; i ++ ) d[i][1] = dis[i], nm[i][1] = num[i];
for (int i = 1; i <= 2 * m; i += 2)
{
int y = to[i], x = to[i + 1], z = w[i];
if ((d[x][0] + d[y][1] + z == d[n][0]) && (1ll * nm[x][0] * nm[y][1] % mod == nm[n][0])) puts("Yes");
else if ((d[y][0] + d[x][1] + z == d[n][0]) && (1ll * nm[y][0] * nm[x][1] % mod == nm[n][0])) puts("Yes");
else puts("No");
}
return 0;
}