[補題] 第 45 屆國際大學生程式設計競賽(ICPC)亞洲區域賽(上海)

kesisour發表於2020-12-19

前往獨立部落格獲取更好閱讀體驗:http://kesisour.work/index.php/archives/90/

題源: https://ac.nowcoder.com/acm/contest/9925

G Fibonacci

簽到題,斐波那契數列中偶數的個數為長度的三分之一,貢獻值從 n 向下遞減

/// ~~AK~~ Cu
#include <bits/stdc++.h>

#define int ll
typedef long long ll;
using namespace std;

int n;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    int p = n / 3;
    int sum = (2 * n - p - 1) * p / 2;
    cout << sum << endl;
    return 0;
}

M Gitignore

樹上DFS,根據檔案目錄建樹,不能被忽略的節點向上回溯至根節點的所有節點全都不能被忽略,標記完 tag 後從根節點搜尋,遇到可以忽略的就返回,否則繼續深搜,統計需要忽略的節點個數。

/// ~~AK~~ Cu
#include <bits/stdc++.h>

#define int ll
typedef long long ll;
using namespace std;

const int M = 100005;

int t, n, m;

map<string, int> s;
string st, newst;
int oldcnt = 0, newcnt = 0;
int flag[100005] = {0};
int C = 0, head[100005];

struct edge {
    int to, nxt;
} edge[M];

inline void add_adge(int u, int v) {
    edge[C].to = v;
    edge[C].nxt = head[u];
    head[u] = C;
    C++;
}

int dfs(int x) {
    if (flag[x]) {
        int tmp = 0;
        for (int i = head[x]; i != -1; i = edge[i].nxt) {
            tmp += dfs(edge[i].to);
        }
        return tmp;
    } else return 1;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    for (int ii = 0; ii < t; ii++) {
        int cnt = 0;
        C = 0;
        memset(head, -1, sizeof(head));
        memset(flag, 0, sizeof(flag));
        flag[0] = 1;
        oldcnt = newcnt = 0;
        s.clear();
        cin >> n >> m;
        for (int i = 0; i < n; i++) {
            cin >> st;
            st = st + '/';
            oldcnt = newcnt = 0;
            for (int j = 0; j < st.length(); j++) {
                if (st[j] == '/') {
                    oldcnt = newcnt;
                    newst = st.substr(0, j);
                    if (!s.count(newst)) {
                        s[newst] = ++cnt;
                        newcnt = s[newst];
                        add_adge(oldcnt, newcnt);
                    }
                    newcnt = s[newst];
                }
            }
        }
        for (int i = 0; i < m; i++) {
            cin >> st;
            st = st + '/';
            for (int j = st.length() - 1; j > 0; j--) {
                if (st[j] == '/') {
                    newst = st.substr(0, j);
                    if (!s.count(newst)) {
                        continue;
                    }
                    newcnt = s[newst];
                    flag[newcnt] = 1;
                }
            }
        }
        int ans = dfs(0);
        cout << ans << endl;
    }
    return 0;
}

B Mine Sweeper II

思維題,考慮數字和就等於相鄰(雷格子,非雷格子)二元組的個數,於是把整個地圖全反過來這個二元組個數不變,然後 B 與 A 之間以及 B 與 inv(A) ,即把 A 所有格子全部取反的掃雷地圖之間總有一個偏差不超過一半的,所以就選其中偏差不超過一半的然後變過去就行。

/// ~~AK~~ Cu
#include <bits/stdc++.h>

#define int ll
typedef long long ll;
using namespace std;

int n, m;

string a[1005], b[1005];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for (int i = 0; i < n; i++) {

        cin >> a[i];
    }
    int ans = 0;
    for (int i = 0; i < n; i++)
        cin >> b[i];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (a[i][j] != b[i][j]) ans++;
        }
    }
    if (ans <= (n * m) / 2) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                cout << a[i][j];
            }
            cout << endl;
        }
    } else {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (a[i][j] == '.')a[i][j] = 'X';
                else a[i][j] = '.';
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                cout << a[i][j];
            }
            cout << endl;
        }
    }
    return 0;
}

D Walker

分類討論兩個點的貢獻情況:

  1. 一個點走完所有。
  2. 兩個點對著向對方的邊界走。
  3. 兩個點共同走中間的部分。中間的界限在兩邊同時貢獻完的情況下總時間最短,因此可以二分查詢。
    賽場AK程式碼分類的有點複雜,不過也能正確通過。
/// ~~AK~~ Cu
#include <bits/stdc++.h>

#define int ll
typedef long long ll;
using namespace std;

int t;
double n, p1, p2, v1, v2, tmp;

double fun(double l, double r, double p, double v) {
    double l1 = p - l, l2 = r - p;
    return (min(l1, l2) * 2 + max(l1, l2)) / v;
}

double mid, t1, t2;

double erfen(double l, double r) {
    mid = (l + r) / 2;
    t1 = fun(0, mid, p1, v1);
    t2 = fun(mid, n, p2, v2);
    if (fabs(t1 - t2) <= 1e-6) {
        return t1;
    } else if (t1 < t2) {
        return erfen(mid, r);
    } else {
        return erfen(l, mid);
    }
}

signed main() {
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d", &t);
    for (int i = 0; i < t; i++) {
        //cin >> n >> p1 >> v1 >> p2 >> v2;
        scanf("%lf %lf %lf %lf %lf", &n, &p1, &v1, &p2, &v2);
        double ans = 0.0;
        if (p1 - p2 >= 0.00000001) {
            tmp = p1;
            p1 = p2;
            p2 = tmp;
            tmp = v1;
            v1 = v2;
            v2 = tmp;
        }
        if (v1 - v2 >= 0.00000001) {
            tmp = p1;
            p1 = n - p2;
            p2 = n - tmp;
            tmp = v1;
            v1 = v2;
            v2 = tmp;
        }
        if (p1 == p2) {
            ans = max(p1 / v1, (n - p2) / v2);
        }
        else if ((n - p2) / v2 - fun(0, p2, p1, v1) >= 0.00000001) {
            ans = (n - p2) / v2;
        }
        else if (p1 / v1 - fun(0, n, p2, v2) >= 0.00000001) {
            ans = fun(0, n, p2, v2);
        } else if (p1 / v1 - fun(p1, n, p2, v2) >= 0.00000001) {
            ans = p1 / v1;
        } else {
            /// 二分 x
            ans = erfen(p1, p2);
        }
        double ans2 = max((n - p1) / v1, p2 / v2);
        ans = min(ans, ans2);
        printf("%.10lf\n", ans);
    }
    return 0;
}

C Sum of Log

根據性質可以得出 log2(i + j) == log2(max(i, j)),即最大為1的二進位制位不會改變,考慮按二進位制位進行數位DP。
dp[i][j][k][l]表示當前第i位,j&k 都是 0/1 變數,表示當前狀態是否抵住了X&Y的上界,l表示當前狀態的最高有效位。
可能實現方法略微複雜,甚至在交題的時候有一定概率會T…,抽空補一下遞推的數位DP版本。

#include <bits/stdc++.h>

#define numm ch - 48
#define pd putchar(' ')
#define pn putchar('\n')
#define int ll
typedef long long ll;
using namespace std;

template<typename T>
void read(T &res) {
    bool flag = false;
    char ch;
    while (!isdigit(ch = getchar())) (ch == '-') && (flag = true);
    for (res = numm; isdigit(ch = getchar()); res = (res << 1) + (res << 3) + numm);
    flag && (res = -res);
}

template<typename T>
void write(T x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

///

const int mod = 1e9 + 7;

int t, x, y, tmp, lx, ly;
int digx[35], digy[35];
int dp[35][2][2][35];

int dfs(int dig, int limx, int limy, int maxdig) {
    if (!dig) return maxdig;
    if (dp[dig][limx][limy][maxdig]) return dp[dig][limx][limy][maxdig];
    int ans = 0;
    if ((limx and digx[dig]) or !limx) ans += dfs(dig - 1, limx, limy and (!digy[dig]), (maxdig ? maxdig : dig));
    if ((limy and digy[dig]) or !limy) ans += dfs(dig - 1, limx and (!digx[dig]), limy, (maxdig ? maxdig : dig));
    ans += dfs(dig - 1, limx and (!digx[dig]), limy and (!digy[dig]), maxdig);
    dp[dig][limx][limy][maxdig] = ans % mod;
    return dp[dig][limx][limy][maxdig];
}

signed main() {
    read(t);
    while (t--) {
        memset(dp, 0, sizeof(dp));
        memset(digx, 0, sizeof(digx));
        memset(digy, 0, sizeof(digy));
        read(x), read(y);
        tmp = x, lx = 0, ly = 0;
        while (tmp) {
            digx[++lx] = tmp % 2;
            tmp = tmp >> 1;
        }
        tmp = y;
        while (tmp) {
            digy[++ly] = tmp % 2;
            tmp = tmp >> 1;
        }
        write(dfs(max(lx, ly), 1, 1, 0)), pn;
    }
    return 0;
}

I Sky Garden

思維題,挺簡單的,注意 m 為 1 的時候圓心是沒有端點的。

#include <bits/stdc++.h>

#define numm ch - 48
#define pd putchar(' ')
#define pn putchar('\n')
#define int ll
typedef long long ll;
using namespace std;

template<typename T>
void read(T &res) {
    bool flag = false;
    char ch;
    while (!isdigit(ch = getchar())) (ch == '-') && (flag = true);
    for (res = numm; isdigit(ch = getchar()); res = (res << 1) + (res << 3) + numm);
    flag && (res = -res);
}

template<typename T>
void write(T x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}



///

const double pi = 3.1415926535;

int n, m;
double ans;

signed main() {
    read(n), read(m);
    if (m != 1) ans = n * m * (n + 1);
    for (int i = 1; i <= n; i++) {
        double tmp = 0;
        for (int j = 1; j <= m; j++) {
            tmp += min((double)2 * i, pi * i * j / m);
        }
        tmp *= 2.0 * m;
        tmp -= 2.0 * m * i;
        ans += tmp;
    }
    for (int i = 1; i < n; i++) {
        double tmp = 0;
        for (int j = 1; j <= m; j++) {
            tmp += min((double)2 * i, pi * i * j / m);
        }
        tmp *= 2.0;
        tmp -= 2.0 * i;
        tmp *= 2.0 * m;
        tmp *= n - i;
        ans += tmp;
        ans += (double) m * (n - i + 1) * (n - i) * 2 * m;
    }
    printf("%.8f", ans);
    return 0;
}

L Traveling in the Grid World

思維題

若 gcd(m - a, n - b) == 1,則 (m, n) 能否從點 (a, b) 直接到達。

第一個結論:不能直接到達的點最多有一個轉折點就可以到達。
證明:gcd(1, n - 1) == 1 and gcd(m - 1, 1) == 1一定成立。

第二個結論:若某轉移點使得總距離最小,則途中兩條路一定不會經過點。
證明:若從 A 到 B 經過轉移點 C,此時有點 D 位於 AC 路徑中,那麼 AD + BD < AC + BC

第三個結論:最優解一定只需要一個轉移點。
證明:若從 A 到 B 經過兩個轉移點 C 和 D,那麼總路徑為 AC + CD + BD,此時BC > BD + CD。若 BC 可以直接到達,則不需要點 D 答案更優,若 BC 不能直接到達,則參見第二結論, BC 中一定存在點 E 使得 AE + BE < AC + BC,答案更優。

綜上,若不能直接到達,暴力直線附近的點作為轉移點即可。

#include <bits/stdc++.h>

#define numm ch - 48
#define pd putchar(' ')
#define pn putchar('\n')
#define int ll
typedef long long ll;
using namespace std;

template<typename T>
void read(T &res) {
    bool flag = false;
    char ch;
    while (!isdigit(ch = getchar())) (ch == '-') && (flag = true);
    for (res = numm; isdigit(ch = getchar()); res = (res << 1) + (res << 3) + numm);
    flag && (res = -res);
}

template<typename T>
void write(T x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}



///

int t, n, m;
int f, c;

signed main() {
    read(t);
    while (t--) {
        read(n), read(m);
        if (__gcd(m, n) == 1) {
            printf("%.9lf\n", sqrt(m * m + n * n));
            continue;
        }
        double k = (double) m / n;
        double ans = 200000005.0;
        f = 1, c = 0;
        while (f) {
            for (int i = 1; i <= n; i++) {
                int y = k * i - c;
                if (fabs((double)y / i - k) < 1e-10) continue;
                if (__gcd(i, y) == 1 and __gcd(n - i, m - y) == 1) {
                    f = 0;
                    ans = min(ans, sqrt(y * y + i * i) + sqrt((n - i) * (n - i) + (m - y) * (m - y)));
                }
            }
            c++;
        }
        printf("%.9lf\n", ans);
    }
    return 0;
}

相關文章