1. 程式人生 > 其它 >[考試總結]ZROI-21-NOIP衝刺-TEST10 總結

[考試總結]ZROI-21-NOIP衝刺-TEST10 總結

ZROI-21-NOIP衝刺-TEST10 總結

...too young...too simple,sometimes naive!

#T1 不知道高到哪裡去了

Time Limits: 3s | Memroy Limit: 256MiB

#題意簡述

比你們不知道高到哪裡去了!

一個 \(n(n\leq10^4)\) 個點 \(m(5\times10^5)\) 條邊的帶權無向圖(可能有重邊、自環),你處在 \(C\) 點,刺客開始時在 \(I\) 點,你要到 \(T\) 點去,你不知道刺客向哪裡走,刺客時刻知道你的位置,問你的速度至少是刺客的多少倍(實數)才能安全到達 \(T\) 點,如果無法到達,則輸出 -1

#大體思路

考慮二分答案,考慮如何判定。顯然我們可以得到每次到達任意一點的最小時間,刺客的最小時間可以提前用最短路得到,當我們發現當前邊的終點的最小時間比刺客晚,那麼我們就不前往,看最終是否能夠到達 \(T\)

點即可。時間複雜度為 \(O((m+n)\log^2 n)\).

#Code

#define dd double
#define ll long long
#define pdi pair <double, int>
#define mp(a, b) make_pair(a, b)

const int N = 100010;
const int M = 500010;
const int INF = 0x3f3f3f3f;
const dd eps = 1e-7;

template<typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (;!isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (;isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

struct edge {int u, v, nxt, w;} e[M << 1];

int head[N], ecnt(1);
int C, I, T, n, m;

dd d[N], TimeLimit[N];
bool vis[N];

inline void add_edge(int u, int v, int w) {
    e[ecnt].u = u, e[ecnt].v = v, e[ecnt].w = w;
    e[ecnt].nxt = head[u], head[u] = ecnt ++;
}

inline void init() {
    read(n); read(m);
    for (int i = 1; i <= m; ++ i) {
        int u, v, w; read(u), read(v), read(w);
        add_edge(u, v, w); add_edge(v, u, w);
    }
    read(C); read(I); read(T);
    fill(TimeLimit + 1, TimeLimit + n + 1, INF);
}

dd l = 0, r = 10000001;

priority_queue <pdi > q;

inline void dijkstra(int s, dd v) {
    fill(d + 1, d + n + 1, INF);
    fill(vis + 1, vis + n + 1, 0);
    if (TimeLimit[s] <= 0) return;
    q.push(mp(0, s)); d[s] = 0;
    while (q.size()) {
        int now = q.top().second; q.pop();
        if (vis[now]) continue; vis[now] = 1;
        for (int i = head[now]; i; i = e[i].nxt) {
            dd w = (dd)e[i].w / v;
            if (d[e[i].v] <= d[now] + w) continue;
            if (TimeLimit[e[i].v] <= d[now] + w) continue;
            d[e[i].v] = d[now] + w; 
            q.push(mp(-d[e[i].v], e[i].v));
        }
    }
}

inline bool Check(dd mid) {
    dijkstra(C, mid); return d[T] != INF;
}

int main() {
    init(); dijkstra(I, 1);
    for (int i = 1; i <= n; ++ i) TimeLimit[i] = d[i];
    while (r - l > eps) {
        dd mid = (l + r) / 2;
        if (Check(mid)) r = mid;
        else l = mid;
    }
    if (l >= 10000000) {return puts("-1"), 0;}
    else printf("%lf", l);
    return 0;
}

#T2 身經百戰

Time Limits: 1s | Memroy Limit: 256MiB

#題意簡述

我是身經百戰了,見得多了!

身經百戰的你要和怪物戰鬥。有 \(n(n\leq10^6)\) 個怪,每個怪的血量 \(v_i(v_i\leq10^9)\) 都是一個非負整數,當血量變為負數後怪就死了。有 \(m(m\leq10^5)\) 種魔法,第 \(i\) 種用一個三元組 \((a_i,b_i,c_i)\) 表示,魔法可以重複使用。

你有兩種操作可以做:

  • 花費 \(1\) 點能量,給一個怪的血量減掉 \(1\)
  • 如果一個怪的血量是 \(a_i\),你可以花費 \(c_i\) 的能量把它的血量變為 \(b_i\)

你希望削弱這些怪,但你不想殺生。請問最少要花費多少能量才能把所有怪的血量都變成 \(1\)。注意:一個血量為 \(0\) 的怪仍然是存活的。

資料保證有解。

#大體思路

不難發現每個怪物都是獨立的問題,所以其實可以看作多次詢問,同樣因為這個原因,我們嘗試進行 DP。

\(f_{i,j}\) 表示將第 \(i\) 個怪物的血量變為 \(j\) 所需要的最小代價。注意到這個 DP 的轉移具有後效性,所以考慮將所有轉移變為邊,找最短路,我們發現上面的狀態設計有許多的冗餘狀態,我們只需要保留給出資料中出現的數,將原本 DP 的轉移變為邊則是 \(a_i\to b_i\) (邊權為 \(c_i\))以及 \(x_i\) 向所有比它小的數連邊(邊權為差值),但是這樣的邊的數量仍舊是 \(O(m^2)\) 級別的,不能接受,注意到第二部分的邊可以被簡化,也就是 \(x_i\) 只需要向比自己小的第一個數連邊即可,這樣得到的轉移圖與之前是等價的,邊的數量為 \(O(n+m)\),現在我們只需要建出反向邊,以 \(1\) 為源點最短路即可。

時間複雜度為 \(O((n+m)\log(n+m))\).

#Code

#define ll long long
#define plli pair <long long, int>
#define mp(a, b) make_pair(a, b)
#define mset(l, x) memset(l, x, sizeof(l))

const int N = 5000010;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

struct Magic {int a, b; ll c;} p[N];
struct Edge {int u, v, nxt; ll w;} e[N];

int n, m, v[N], head[N], ecnt(1), a[N];
ll d[N]; int vis[N];

inline void add_edge(int u, int v, int w) {
    e[ecnt].u = u, e[ecnt].v = v, e[ecnt].w = w;
    e[ecnt].nxt = head[u], head[u] = ecnt ++;
}

#define lb(l, len, x) lower_bound(l + 1, l + len + 1, x)

priority_queue <plli > q;

void solve(int s) {
    mset(d, 0x3f); mset(vis, 0);
    d[s] = 0; q.push(mp(0, s));
    while (q.size()) {
        int now = q.top().second; q.pop();
        if (vis[now]) continue; vis[now] = 1;
        for (int i = head[now]; i; i = e[i].nxt)
          if (d[e[i].v] > d[now] + e[i].w) {
              d[e[i].v] = d[now] + e[i].w;
              q.push(mp(-d[e[i].v], e[i].v));
          }
    }
}

int main() {
    read(n), read(m);
    for (int i = 1; i <= n; ++ i) read(v[i]);
    for (int i = 1; i <= m; ++ i) {
        read(p[i].a), read(p[i].b), read(p[i].c);
        a[i * 2 - 1] = p[i].a, a[i * 2] = p[i].b;
    }
    for (int i = 1; i <= n; ++ i) a[2 * m + i] = v[i];
    a[2 * m + n + 1] = 1; sort(a + 1, a + 2 * m + n + 2);
    int _m = unique(a + 1, a + 2 * m + n + 2) - a - 1;
    for (int i = 1; i <= m; ++ i) {
        int u = lb(a, _m, p[i].a) - a;
        int v = lb(a, _m, p[i].b) - a;
        add_edge(v, u, p[i].c);
    }
    for (int i = 1; i < _m; ++ i)
      add_edge(i, i + 1, a[i + 1] - a[i]);
    solve((lb(a, _m, 1) - a)); ll ans = 0;
    for (int i = 1; i <= n; ++ i) {
        int pos = lb(a, _m, v[i]) - a;
        ans += d[pos];
    }
    printf("%lld ", ans);
    return 0;
}

#T3 跑得比誰都快

Time Limits: 3s | Memroy Limit: 256MiB

#題意簡述

你們有一個好,全世界跑到什麼地方,你們比其他的西方記者啊,跑得還快。

這條路上有 \(n(n\leq2\times10^5)\) 個紅綠燈,把路分成了 \(n+1\) 個部分。紅綠燈的顏色是迴圈的,一次迴圈內,在 \([0,g)\) 的時間裡它是綠色的,在 \([g,g+r)\) 的時間裡它是紅色的。一開始,每個紅綠燈都處於迴圈的開始,也就是說都會先綠 \(g(g+r\leq10^9)\) 的時間。

記者團裡有 \(q(q\leq2\times10^5)\) 個香港記者。每個記者要從路的開頭跑到末端,遇到紅燈的話不能穿過,必須等紅燈變綠。記者的最大速度是 \(1\),記者可以瞬間改變自己的速度。告訴你記者出發的時間,問你她什麼時候跑到。

本題強制線上,假如輸入的第 \(i(1<i\leq q)\) 個記者的出發時間為 \(time_i\),那麼她實際出發的時間是 \(time_i\text{ xor }(ans_{i−1} \bmod 2147483647)\),其中 \(ans_{i−1}\) 表示第 \(i−1\) 個人的到達時間。

#大體思路

考慮在任意紅綠燈出發遇到的第一個紅燈的位置,由於 \(m=g+r\) 是週期,所以不妨將時間通過 \(\bmod m\) 將所有時間分為 \(m\) 種,設 \(t_i\) 為到達 \(i\) 號紅綠燈前沒有任何一個紅綠燈阻擋到達 \(i\) 的時間,顯然是 \(s_i\bmod m\)\(s_i\)\(len\) 的字首和),設 \(j\) 為從 \(i\) 出發後遇到的第一個紅燈,由於兩者之間沒有任何紅燈阻攔,考慮到從任意一個紅綠燈出發必然是在該紅綠燈進行了停頓,但顯然不會對到 \(j\) 的時間和到 \(i\) 的時間之間的差值造成影響,且出發時間必然是 \(m\) 的倍數,在 \(i\) 於是兩者之間經過的時間必然是 \(t_j-t_i=km+b\)\(k\in Z,b\in[g,r)\),於是我們找 \([t_i\bmod m+g,t_i\bmod m+r)\) 區間最小值即可,然後採用 \(j\) 到終點的時間更新 \(i\) 到終點的時間,最後將 \(i\) 插入到 \(t_i\bmod m\) 的位置即可。

再來看查詢時,同樣考慮第一個遇到的紅綠燈,不過上面維護出的都是開始時間為 \(0\) 的情況(迴圈從頭開始),所以我們開始時需要將查詢區間進行矯正。

時間複雜度為 \(O(n\log n)\).

#Code

#define ll long long

const int N = 10000010;
const ll MOD = 2147483647;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

struct Node {int ls, rs, val;} p[N];

ll n, m, G, R, q, len[N], s[N], sp[N], lst, pcnt, f[N]; int rt;

void insert(int &k, int l, int r, int x, int c) {
    if (!k) k = ++ pcnt; p[k].val = c;
    if (l == r) return; int mid = l + r >> 1;
    if (x <= mid) insert(p[k].ls, l, mid, x, c);
    else insert(p[k].rs, mid + 1, r, x, c);
}

int query(int k, int l, int r, int x, int y) {
    if (!k) return n + 1; if (x <= l && r <= y) return p[k].val;
    int mid = l + r >> 1, res = INF;
    if (x <= mid) res = min(res, query(p[k].ls, l, mid, x, y));
    if (mid < y) res = min(res, query(p[k].rs, mid + 1, r, x, y));
    return res;
}

ll dist(int x, int y) {return y == n + 1 ? s[y] - s[x] : (s[y] - s[x] + m - 1) / m * m;}

int main() {
    read(n), read(G), read(R); m = G + R;
    for (int i = 1; i <= n + 1; ++ i) read(len[i]);
    for (int i = 1; i <= n + 1; ++ i)
      s[i] = s[i - 1] + len[i], sp[i] = s[i] % m; 
    for (int i = n; i; -- i) {
        int l = (sp[i] + G) % m, r = (l + R - 1) % m, pos = 0;
        if (l > r) pos = min(query(rt, 0, m - 1, 0, r), query(rt, 0, m - 1, l, m - 1));
        else pos = query(rt, 0, m - 1, l, r);
        f[i] = dist(i, pos) + f[pos]; insert(rt, 0, m - 1, sp[i], i);
    }
    read(q);
    while (q --) {
        ll x; read(x); x ^= (lst % MOD); s[0] = -x; int pos = 0;
        int l = (0ll + m - x % m + G) % m, r = (l + R - 1) % m;
        if (l > r) pos = min(query(rt, 0, m - 1, 0, r), query(rt, 0, m - 1, l, m - 1));
        else pos = query(rt, 0, m - 1, l, r);
        printf("%lld\n", lst = dist(0, pos) + f[pos]);
    }
    return 0;
}

#T4 人生經驗

Time Limits: 1s | Memroy Limit: 512MiB

#題意簡述

我作為一個長者,來告訴你們一些人生的經驗…

人生經驗以 01 字串的形式存在。

一個長度為奇數的 01 字串是好的,當且僅當它可以被用下面的辦法轉化為 1

  • 選一個奇數 \(i(3\leq i\leq|S|)\)
  • \(S\) 分成兩個字串 \(A,B\) 滿足 \(|A|=i,|B|=|S|−i,S=AB\)
  • 通過一個給定的函式 \(f(U)\)\(A\) 的末尾三位數變為一位數,一直重複直到 \(A\) 只剩下一個數
  • \(A+B\) 替代 \(S\)

現在給定一個字串,包含 0 1 ? 三種字元,? 可以替換為 0 或者 1,請問有多少種替換方案使得替換的結果是一個好串。

#大體思路

DP 套 DP。

我們考慮如何判斷一個串是否合法,不難發現,那些操作都可以轉化成下面兩個操作:

  • 把兩個字元壓入棧。
  • 把一個字元壓入棧,進行縮字串,然後再讓另一個字元入棧。

雖然棧的情況可能很多,但其實去掉我們不管的,只有 \(4\) 種情況:

  1. 當前棧中序列加入 1 之後能縮成 1
  2. 當前棧中序列加入 1 之後能縮成 0
  3. 當前棧中序列加入 0 之後能縮成 1
  4. 當前棧中序列加入 0 之後能縮成 0

上面四種情況就是我們所關心的。然後我們就可以設計 DP 狀態:

\(f_{i,a}\) 表示考慮前 \(i\) 個字元,第 \(a\) 個放法是否合法,然後考察整個字串的話,只需要看 \(f_{n−1}\) 即可。

接下來我們考慮計數,通過上面的啟發,再加上字串是不確定的,所以我們設狀態 \(g_{a,b,c,d}\) 分別表示上面 \(4\) 種轉移是否合法,\(f_{i,S}\) 表示考慮到第 \(i\) 位,合法轉移為 \(S\) 的情況的數量。

我們每次考慮兩個數,先考慮兩個字元壓入棧的情況,然後列舉合法情況,轉移即可。

#Code

#define ll long long
#define mset(l, x) memset(l, x, sizeof(l))

const int N = 100010;
const int MOD = 1e9 + 7;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

int n, t, f[N][16], a[N], ans; char s[N], g[N];

inline int get(int x, int y, int z) {return g[x | (y << 1) | (z << 2)] - '0';}
inline int Madd(int x, int y) {return x + y >= MOD ? x + y - MOD : x + y;}

void MAIN() {
    scanf("%s%s", g, s + 1); n = strlen(s + 1);
    mset(f, 0); ans = 0; f[0][9] = 1;
    for (int i = 2; i < n; ++ i) 
      for (a[i - 1] = 0; a[i - 1] < 2; ++ a[i - 1])
        for (a[i] = 0; a[i] < 2; ++ a[i]) {
            if (s[i - 1] != '?' && s[i - 1] != a[i - 1] + '0') continue;
            if (s[i] != '?' && s[i] != a[i] + '0') continue;
            for (int S = 0; S < 16; ++ S) {
                if (!f[i - 2][S]) continue; int SS = 0;
                for (int p1 = 0; p1 < 2; ++ p1)
                  for (int p2 = 0; p2 < 2; ++ p2) {
                      int p = ((S & (1 << (get(a[i - 1], a[i], p1) << 1) + p2)) > 0);
                      if (get(0, a[i], p1) == p2) p |= ((S & (1 << (a[i - 1] << 1))) > 0);
                      if (get(1, a[i], p1) == p2) p |= ((S & (1 << (a[i - 1] << 1) + 1)) > 0);
                      if (p) SS |= (1 << (p1 << 1) + p2);
                  }
                f[i][SS] = Madd(f[i][SS], f[i - 2][S]);
            }
        }
    for (a[n] = 0; a[n] < 2; ++ a[n]) {
        if ((s[n] != '?') && (s[n] != a[n] + '0'))continue;
        for (int S = 0; S < 16; ++ S)
          if (S & (1 << (a[n] << 1) + 1))
            ans = Madd(ans, f[n - 1][S]);
    }
    printf("%d\n", ans);
}

int main() {
    read(t); while (t --) MAIN(); return 0;
}