Codeforces Round #731 (Div. 3)
Codeforces Round #731 (Div. 3)
A. Shortest Path with Obstacle
题意:给定无限大的网格,A、B、F三个点的坐标,求出A到B的最短路径(只能横向或者竖向走,且不能经过F点)。
分析:如果A、B在矩形网格的对角线,则有两个互不交叉的等距离路线,F不可能阻挡,最短距离为他们的哈曼顿距离。如果A、B是一条直线,F会使路线多走两步。
Code
void solve ()
{
PII x, y, z;
cin >> x.f >> x.s >> y.f >> y.s >> z.f >> z.s;
if (x.f == y.f && x.f == z.f && (z.s < x.s && z.s > y.s || z.s > x.s && z.s < y.s))
cout << abs(x.s - y.s) + 2 << endl;
else if (x.s == y.s && x.s == z.s && (z.f < x.f && z.f > y.f || z.f > x.f && z.f < y.f))
cout << abs(x.f - y.f) + 2 << endl;
else
cout << abs(x.s - y.s) + abs(x.f - y.f) << endl;
}
B. Alphabetical Strings
题意:对于一个空字符串,从'a'开始加入字符,每次只能加字符串最左边或者最右边,问给定字符串是否合法。
分析:从最后一个操作开始向前判断,每次删除最后加入的字符,可以得到相同的子问题,可以使用递归解决。用last保存上一个被删除的字符。
Code
bool check (int l, int r, char last)
{
if (l == r) return s[l] == 'a' && (s[l] == last - 1 || last == 0);
if (s[l] > s[r])
{
if (last != 0 && s[l] != last - 1) return false;
return check(l+1, r, s[l]);
}
if (s[l] == s[r]) return false;
if (last != 0 && s[r] != last - 1) return false;
return check(l, r-1, s[r]);
}
puts(check(0, s.size()-1, 0) ? "YES" : "NO");
C. Pair Programming
题意:给定两个序列和一个初值,每次挑选任意一个序列未选择的第一个数字,0则初值加一,小于初值则不变化,大于初值则非法。输出任意一个合法方案。
分析:双指针同时扫描两个序列,贪心思想,优先加值,其次选小的数字。
Code
int k, n, m;
int t1[N], t2[N], ans[N];
void solve ()
{
cin >> k >> n >> m;
rep(i, 1, n) cin >> t1[i];
rep(i, 1, m) cin >> t2[i];
int p1 = 1, p2 = 1; // 双指针
bool flag = true;
for (int i = 1; i <= n + m; i++)
{
if (p1 <= n && !t1[p1]) p1 ++, ans[i] = 0, k++;
else if (p2 <= m && !t2[p2]) p2 ++, ans[i] = 0, k++;
else if (p1 <= n && p2 <= m)
{
if (k < min(t1[p1], t2[p2])) flag = false;
if (t1[p1] < t2[p2]) ans[i] = t1[p1++];
else ans[i] = t2[p2++];
}
else if (p1 <= n)
{
if (k < t1[p1]) flag = false;
ans[i] = t1[p1++];
}
else
{
if (k < t2[p2]) flag = false;
ans[i] = t2[p2++];
}
}
if (!flag) cout << -1 << endl;
else
{
for (int i = 1; i <= n + m; i++) cout << ans[i] << " ";
cout << endl;
}
}
D. Co-growing Sequence
题意:给定序列a,求序列b,使得任意 \((a_i \ \bigoplus \ b_i) \ \& \ (a_{i+1} \ \bigoplus \ b_{i+1}) \ == \ a_i \ \bigoplus \ b_i\) 成立。
分析:由于只需要求出满足 前&后 = 前,所以\(b_1\)可以是任意的,由于求b的字典需最小,令\(b_1\)为0即可。
在求\(b_{i+1}\) 时,\(a_i \ \bigoplus \ b_i\) 和\(a_{i+1}\)都已知,由于&的性质,\(a_i \ \bigoplus \ b_i\)有1的位置,\(a_{i+1} \ \bigoplus \ b_{i+1}\)也必须有1,只需要枚举每一位确定即可。
Code
void solve ()
{
cin >> n;
rep(i, 1, n) cin >> a[i];
memset(b, 0, sizeof b);
rep(i, 2, n)
{
for (int k = 30; k >= 0; k -- )
{
if ((a[i-1] ^ b[i-1]) >> k & 1)
if (((a[i] ^ b[i]) >> k & 1) == 0)
b[i] += (1 << k);
}
}
rep(i, 1, n) cout << b[i] << " \n" [i == n];
}
E. Air Conditioners
题意:有\(n\)个房间和\(k\)个空调,第\(j\)个空调位于\(a_j\)个房间,温度为\(t_i\),求其他房间的温度值,第\(i\)个房间的温度为 \(min_{1 \leq j \leq k} (ti + |a_j - i|)\)。
分析:每个房间的温度都由左右两方向的空调温度决定,先考虑左边空调的影响,在考虑右边空调的影响,最后求\(min\)即可。
Code
void solve ()
{
memset(le, 0x3f, sizeof le);
memset(ri, 0x3f, sizeof ri);
cin >> n >> k;
rep(i, 1, k) cin >> a[i];
rep(i, 1, k)
{
int t; cin >> t;
le[a[i]] = ri[a[i]] = t;
}
// 分别计算每个格子左右空调的影响
rep(i, 1, n) le[i] = min(le[i], le[i-1] + 1);
per(i, n, 1) ri[i] = min(ri[i], ri[i+1] + 1);
rep(i, 1, n) le[i] = min(le[i], ri[i]);
rep(i, 1, n) cout << le[i] << " \n" [i == n];
}
F. Array Stabilization (GCD version)
题意:给定序列\(a = [a_0,a_1,...a_n-1]\),每次操作可以使\(a_i = gcd(a_i, a_{i+1})\)),特别的,最后一个元素为\(a_{n-1} = gcd(a_{n-1}, a_0)\),即对序列的环形操作。
求经过几次操作后可以使\(a_0=a_1=...=a_{n-1}\)。
分析:可以发现,进行\(x\)次操作后,\(a_i = gcd(a_i, a_{i+1}, ... a_{i+x})\), 即每个元素都为原数组的\(x+1\)位元素的\(gcd\)。
由于无区间修改,可以用\(st\)表预处理每个区间的\(gcd\),最后可以二分\(gcd\)长度,即操作数量。
Code
int n;
int st[N][20], Lg[N];
int get (int l, int r)
{
int t = Lg[r - l + 1];
// 处理环形gcd
if (r > n)
{
r %= n;
return __gcd(get(1, r), get(l, n));
}
else return __gcd(st[l][t], st[r - (1 << t) + 1][t]);
}
bool check (int k)
{
int g = get(1, k+1);
for (int i = 2; i <= n; i++)
if (g != get(i, i + k)) return false;
return true;
}
void solve ()
{
cin >> n;
rep(i, 1, n) cin >> st[i][0];
for (int j = 1; j < 20; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = __gcd(st[i][j-1], st[i+(1<<j-1)][j-1]);
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << r << endl;
}
for (int i = 2; i < N; i++) Lg[i] = Lg[i >> 1] + 1;
G. How Many Paths?
题意:给定一张有向图图\(G\),求从\(1\)号点到达其他点的路径数量,图不存在重边,但可能存在自环。
如果路径数量为\(0\)或\(1\),直接输出,无穷则输出\(-1\),有多个输出\(2\)。
分析:对于一个强连通分量,如果\(1\)号可达,那么到达连通分量中的所有点的路径为INF,对有向图进行Tarjan算法求出强连通分量,缩点得到DAG,再在DAG上DP即可求得到达每一个连通分量的路径数。
\(Tips:\) 对于vector而言,多次clear的时间消耗很大,需要直接重新分配一片空间。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 400010, M = 800010, mod = 1e6 + 7, INF = 0x3f3f3f3f;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp; // 计算强连通分量
int scc_cnt; // 强连通分量的信息
int id[N]; // 每个点所在的强连通分量
int stk[N], top; // Tarjan维护的栈
bool in_stk[N]; // 结点是否在栈中
int din[N]; // 新图的点入度
int dp[N]; // dp表示从1号点到其他点的路径数量
int q[N]; // 拓扑排序要用到的队列
bool is_loop[N], is_loop_scc[N]; // 点、强连通分量中是不是自环
vector<vector<int>> scc; // 每个强连通分量中的点
void init ()
{
memset(h, -1, sizeof h);
idx = 0;
timestamp = 0;
scc_cnt = 0;
memset(din, 0, sizeof din);
memset(dp, 0, sizeof dp);
memset(is_loop_scc, 0, sizeof is_loop_scc);
memset(is_loop, 0, sizeof is_loop);
memset(dfn, 0, sizeof dfn);
scc = vector<vector<int>>(n+5);
}
void add (int a, int b)
{
e[idx] = b; ne[idx] = h[a]; h[a] = idx ++ ;
}
void tarjan (int u)
{
dfn[u] = low[u] = ++timestamp;
stk[++top] = u; in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
++scc_cnt;
int y, num = 0; // 统计强连通分量点的数量
do {
num++;
y = stk[top--];
in_stk[y] = false;
id[y] = scc_cnt;
// 判断这个强连通分量是否包含多个结点,或个有结点有子环,那么到达这个强连通分量就是INF的
if (is_loop[y] || num > 1) is_loop_scc[scc_cnt] = true;
} while (y != u);
}
}
// 在缩点后的新图上作DAG上的DP
void topsort (int n)
{
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++)
if (din[i] == 0) q[++tt] = i;
while (hh <= tt)
{
int u = q[hh++];
for (int v : scc[u])
{
if (-- din[v] == 0) q[++tt] = v;
// 1号结点没有到达u结点,即没有路径
if (dp[u] == 0) continue;
// 否则,判断v的前躯是否路径数量为INF,或者v本身有环
if (dp[u] == -1 || is_loop_scc[v])
dp[v] = -1;
else if (dp[v] != -1)
{
// u有多条路径数量,或者v之前已经走过一次
if (dp[u] == 2 || dp[v]) dp[v] = 2;
else dp[v] = 1;
}
}
}
}
void solve ()
{
cin >> n >> m;
init();
for (int i = 1; i <= m; i++)
{
int a, b; scanf("%d%d", &a, &b);
if (a == b) is_loop[a] = true;
add(a, b);
}
// Tarjan求强连通分量
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
// 缩点求新图点的入度
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
if (id[k] != id[i])
{
scc[id[i]].push_back(id[k]);
din[id[k]] ++ ;
}
}
// 先看1号结点所在的强连通分量的路径数量
dp[id[1]] = (is_loop_scc[id[1]] ? -1 : 1);
topsort(scc_cnt); // DAG上做DP
for (int i = 1; i <= n; i++) printf("%d ", dp[id[i]]);
putchar('\n');
}
/* 随风摇摆是一种优秀的能力,但从一而终也是一份可贵的品质。后者或许会招致破灭,但也可能诞生奇迹。*/
signed main ()
{
int T; scanf("%d", &T); while (T -- )
solve();
return 0;
}