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;
}
posted @ 2021-08-11 20:02  Horb7  阅读(31)  评论(0编辑  收藏  举报