[考試總結]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;
}