1. 程式人生 > 其它 >[考試總結]ZROI-21-十一集訓-贈送賽 總結

[考試總結]ZROI-21-十一集訓-贈送賽 總結

ZROI-21-十一集訓-結課測試 總結

#T1 有趣的數

#題意簡述

定義一個整數 \(N\) 是有趣的,當且僅當 \(N\) 滿足:

  • \(0\leq N\leq10^{18}\)
  • \(B_1\) 進位制下有 \(K_1\) 位,在 \(B_2\) 進位制下有 \(K_2\) 位;

其中 \(2\leq B_1,B_2\leq100\)\(1\leq K_1,K_2\leq20\)

#大體思路

不難發現在 \(B_1,K_1\) 給定的情況下,可能的有趣的數是一個區間,上下界可以用快速冪得到,\(B_2,K_2\) 同理,於是求區間交即可。

#Code

#define ll long long

const ll LMT = 1e18;

ll b1, d1, b2, d2;

inline ll get_limit(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res *= a;
        if (res >= LMT + 1 || res <= 0) return LMT + 1;
        b >>= 1; if (b) a *= a;
        if (a >= LMT + 1 || a <= 0) return LMT + 1;
    }
    return res;
}

int main() {
    scanf("%lld%lld%lld%lld", &b1, &d1, &b2, &d2);
    ll ul1 = get_limit(b1, d1);
    ll ll1 = get_limit(b1, d1 - 1);
    ll ul2 = get_limit(b2, d2);
    ll ll2 = get_limit(b2, d2 - 1);
    ll ul = min(ul1, ul2), lo = max(ll1, ll2);
    printf("%lld", ul > lo ? ul - lo : 0);
    return 0;
}

#T2 木棒

#題意簡述

給定十二根木棒的長度 \(l_i(l_i\leq10^9)\),問最多能同時得到多少個三角形。

多組資料,陣列組數 \(t\) 滿足 \(1\leq t\leq6000.\)

#大體思路

發現選擇狀態數極少,可以考慮狀壓 DP,但是直接轉移頗具難度,考慮用更加容易理解的記憶化搜尋。

每次找到一個最短的、未被考慮的木棍,他只有兩種選擇:

  • 丟掉;
  • 再找兩根與其配對;

進行記憶化搜尋即可。

#Code

const int N = 12;
const int init_S = (1 << N) - 1;

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;
}

template <typename T> inline T Max(T &a, T &b) {return a > b ? a : b;}

int t, T, l[N], f[(1 << N) + 10], vis[(1 << N) + 10];

int dfs(int S) {
    if (vis[S] == T || !S) return f[S];
    int now; f[S] = 0, vis[S] = T;
    for (int i = 0; i < N; ++ i)
      if ((S >> i) & 1) {now = i; break;}
    f[S] = dfs(S ^ (1 << now));
    for (int i = now + 1; i < N; ++ i) if ((S >> i) & 1)
      for (int j = i + 1; j < N; ++ j) if ((S >> j) & 1 && l[now] + l[i] > l[j])
        f[S] = max(f[S], dfs(S ^ (1 << now) ^ (1 << i) ^ (1 << j)) + 1);
    return f[S];
}

int main() {
    read(t);
    for (T = 1; T <= t; ++ T) {
       for (int i = 0; i < N; ++ i) read(l[i]);
        sort(l, l + N); printf("%d\n", dfs(init_S));
    }
    return 0;
}

#T3 裝飾

#題意簡述

一棵 \(n(n\leq10^5)\) 個節點、以 \(1\) 為根的樹,每個節點上有兩個引數 \(c_i,t_i\),分別表示以 \(i\) 為根的子樹中至少被選擇 \(c_i(1\leq c_i\leq10^7)\) 次,節點 \(i\) 被選一次的代價是 \(t_i(1\leq t_i\leq100)\),求最小總代價。

#大體思路

考慮如果一棵子樹上當前所選的數量不足,為了代價最小,應當選擇整棵子樹上代價最小的節點,按此思路進行樹上 DP(樹上貪心?)即可。

#Code

#define ll long long

const int N = 100010;

template <typename T> inline T Min(T a, T b) {return a < b ? a : b;}

struct Edge {int u, v, nxt;} e[N];

int n,ecnt(1), head[N], siz[N];
ll c[N], t[N], mn[N], f[N], tot[N];

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

void solve(int x) {
    int nowtot = 0; siz[x] = 1, mn[x] = t[x];
    for (int i = head[x]; i; i = e[i].nxt) {
        solve(e[i].v); siz[x] += siz[e[i].v];
        tot[x] += tot[e[i].v]; f[x] += f[e[i].v];
        mn[x] = Min(mn[x], mn[e[i].v]);
    }
    if (tot[x] < c[x]) {f[x] += mn[x] * (c[x] - tot[x]), tot[x] = c[x];}
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++ i) {
        int x; scanf("%d%lld%lld", &x, &c[i], &t[i]);
        if (x > 0) add_edge(x, i);
    }
    solve(1); printf("%lld", f[1]); return 0;
}

#T4 翻轉硬幣

#題意簡述

一個長度為 \(n(n\leq300)\) 的 01 串 \(s\),給定引數 \(m(1\leq m\leq n)\),要求通過以下兩種操作:

  • 將一個位置取反;
  • 將前 \(k\cdot m(k\in Z)\) 位取反(若 \(k\cdot m>n\) 則整體取反);

使得 \(s\) 最終滿足 \(s_1=s_{m+1},s_2=s_{m+2},\dots,s_{n-m}=s_{n}\),問最小運算元。

#大體思路

注意到一個位置單個、整體各最多被取反一次,否則沒有意義。

通過簡單畫圖不難發現,最終狀態滿足將原串從頭開始劃分為長度為 \(m\)\(\left\lceil\dfrac n m\right\rceil\) 小段(最後一段長度可能小於 \(m\)),其中前 \(\left\lfloor\dfrac {n - m} m\right\rfloor\) 段的每一段都相等,最後一點不足 \(m\) 的小段也與完整的小段的前部對應相等。

\(m>\sqrt n\) 時,段數極少,極限資料下不超過 \(17\) 段,於是可以考慮列舉每一段是否被翻轉,然後貪心地考慮每一位選什麼直接統計。

否則,考慮列舉每一段是什麼樣子,然後 DP 即可,每次轉移的時候貪心地選擇這一段是否要翻轉即可。

#Code

const int N = 310;
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;
}

char ca[N];
int n, m, a[N], id[N], sub[N][2], bcnt, f[N][2];

inline void divide() {
    for (int i = 1; i <= n; ++ i) id[i] = (i - 1) / m + 1;
    for (int i = 1; i <= id[n]; ++ i)
      sub[i][0] = (i - 1) * m + 1, sub[i][1] = i * m;
    sub[id[n]][1] = min(sub[id[n]][1], n); bcnt = id[n];
}

inline int bitcnt(int x) {
    int cnt = 0; while (x) x -= x & (-x), ++ cnt; return cnt;
}

int solve_1() {
    int res = INF;
    for (int s = 0; s < 1 << bcnt; ++ s) {
        int cnt = 0, lst = 0;
        for (int i = bcnt; i >= 1; -- i)
          if ((s >> (i - 1)) & 1 != lst) ++ cnt, lst ^= 1;
        for (int i = 1; i <= m; ++ i) {
            int c[2]; c[0] = c[1] = 0;
            for (int j = i; j <= n; j += m)
              ++ c[a[j] ^ (s >> (id[j] - 1)) & 1];
            cnt += min(c[0], c[1]);
        }
        res = min(res, cnt);
    }
    return res;
}

int solve_2() {
    int res = INF;
    for (int s = 0; s < 1 << m; ++ s) {
        for (int i = 1; i <= bcnt; ++ i) {
            int cnt[2]; cnt[0] = cnt[1] = 0;
            for (int j = sub[i][0]; j <= sub[i][1]; ++ j)
              ++ cnt[a[j] ^ (s >> (j - sub[i][0]) & 1)];
            f[i][0] = min(f[i - 1][0] + cnt[1], f[i - 1][1] + cnt[1] + 1);
            f[i][1] = min(f[i - 1][1] + cnt[0], f[i - 1][0] + cnt[0] + 1);
        }
        res = min(res, min(f[bcnt][0], f[bcnt][1] + 1));
    }
    return res;
}

int main() {
    scanf("%s", ca); n = strlen(ca); read(m);
    for (int i = 0; i < n; ++ i) a[i + 1] = ca[i] - '0';
    divide(); printf("%d", m > sqrt(n) ? solve_1() : solve_2());
    return 0;
}