A(水題)
題目連結
⭐
題目:
給出一個\(01\)序列,有2種操作:1.將某個位置取反;2.詢問\(01\)序列中第\(k\)大的數
解析:
顯然維護1的數目即可
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/
int ones = 0;
const int maxn = 1e5 + 5;
int dat[maxn];
int main() {
int n, q, a, b;
scanf("%d%d", &n, &q);
for (int i = 0; i < n; ++i)
{
scanf("%d", &dat[i]);
ones += dat[i];
};
while (q--) {
scanf("%d%d", &a, &b);
if (a == 1) {
--b;
if (dat[b])
--ones;
else
++ones;
dat[b] = 1 - dat[b];
}
else {
printf("%d\n", ones >= b);
}
}
}
B(貪心)
題目連結
⭐⭐
題目:
給出一張圖,由\(n(n\le100)\)行(行從1開始編號),\(10^6+1\)列組成(列從0開始編號),在所給矩陣的每一行存在一個障礙,現在可以花費\(v\)使得障礙水平移動,\(u\)使得障礙豎直移動,問若要從\((1,0)\)可以到達\((n,10^6+1)\),最小花費是多少?
(題目所給障礙物水平移動範圍不包含兩端)
解析:
由於水平移動範圍不包含兩端,所以不會出現上下封閉的情況,那麼只有一種情況下無法到達,即所有障礙形成一條連續線段,將圖分割成左右兩部分,在這樣的情況下,分以下兩種情況進行討論:
- 如果這是一條筆直的線段,即障礙所在列全部相同,則考慮將相鄰障礙物一個左移一個右移,或者將障礙物先水平移動再垂直移動到不同行,形成空缺
- 如果不是筆直的,那麼在可以線上段斜線處水平移動一次或者豎直移動一次,形成空缺
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/
int ones = 0;
const int maxn = 105;
int ob[maxn];
int main() {
int T;
int n, u, v;
scanf("%d", &T);
while (T--)
{
bool left = true;
scanf("%d%d%d", &n, &u, &v);
for (int i = 0; i < n; ++i)
scanf("%d", &ob[i]);
int last = 0;
bool equ = true, line = true;
for (int i = 1; i < n; ++i)
{
if (abs(ob[i] - ob[i - 1]) > 1)
{
line = false;
break;
}
if (ob[i] != ob[i - 1])
equ = false;
}
if (line) {
if (equ)
printf("%d", min(2 * v, u + v));
else
printf("%d", min(u, v));
}
else
printf("%d", 0);
printf("\n");
}
}
C(思維)
題目連結
⭐⭐⭐
題目:
給出\(n\)個蹦床,每個蹦床有一個強度\(S_i\),如果身處\(i\)蹦床,會跳躍至\(i+S_i\)處,且每次蹦床被踩壓後,強度會\(-1\),但至多減至1,現在可以從任意位置起跳,每次跳躍直到無蹦床可以踩壓為止,問將所有蹦床強度降至\(1\),所最少需要的遊戲次數
解析:
- 假設從\(i\)蹦床出發,他只會對\(\ge i\)的部分產生影響,所以如果全要降為\(1\),則需要從前到後的遍歷蹦床,並將其強度減到\(1\)為止
- 這就要求維護一個\(t\)陣列,統計\(i\)之前的蹦床跳躍時對\(i\)的影響,顯然仍需要跳躍的次數為\(\max(S_i-1-t_i,0)\)
- 將\(i\)位置的強度降為\(1\),則會踩壓到\(i+2,i+3,...,i+S_i\)的所有蹦床,即所屬\(t\)均要加\(1\)
- 同時也不難發現,可能由於受之前影響的踩壓次數過多(未遍歷到\(i\)以前,\(S_i\)已經降為\(1\)了),就可以傳遞給下一個蹦床,即\(t[i+1]=\max(t_i-S_i+1,0)\)
注意:
- 答案需要開\(long\ long\)儲存
- 第\(i\)號蹦床對後序的影響,即\(+1\)過程,可以用差分陣列維護
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e3 + 5;
int dat[maxn];
int t[maxn];
long long ret;
int main() {
int T,n;
scanf("%d", &T);
while (T--) {
ret = 0;
scanf("%d", &n);
for (int i = 0; i < n; ++i)
scanf("%d", &dat[i]);
memset(t, 0, sizeof(t));
for (int i = 0; i < n; ++i) {
int x = max(0, dat[i] - 1 - t[i]);
ret += x;
int end = min(n - 1, i + dat[i]);
for (int j = i + 2; j <= end; ++j)
++t[j];
t[i + 1] += max(t[i] - dat[i] + 1, 0);
}
printf("%lld\n", ret);
}
}
D(思維+位運算)
題目連結
⭐⭐⭐
題目:
規定如果\(u\&v=v\),則\(u\)可達\(u+v\),現在給出\(u,v\),問是否可以從\(u\)到\(v\)
解析:
- 如果\(u\rightarrow v\),則\(u<v\)是顯然的
- 不難發現對於任意一個\(u\),對於所有\(u\)可達點的中間變數\(v'\),滿足:\(v'\)中如果位數為1,則必有\(u\)中對應位也為\(1\),而\(u+v'\)可以使得\(1\)的位置向左移,如1101+0001=1110,且在\(1\)連續的情況下,可以選擇性的消除任意個多餘的\(1\),例如0111+0001=1000,或0111+0101=1100
- 那麼對於所給出的\(u\)與\(v\)只要保證每個\(v\)的每一位的右邊位置,\(u\)的\(1\)比\(v\)多,則就可以通過不斷左移或消除\(1\),獲得所需要的\(v\)序列。換句話說即每個\(v\)中,位為\(1\)的右邊位上還含有若干個未被抵消(使用)的\(u\)的1
附:
- \(+v'\)操作肯定不會使得\(u+v'>v\)原因在於,對於每個\(v\)如果有1,則他的右邊\(u\)存在很多1,這兩段二進位制程式碼的差一定是大於等於1的,如100000與0011010
- 如果當前\(u\)位也存在1,則可以將這些多餘的1放在後續中被消除,這是一定能完成的。因為初始時保證\(u\le v\),如果二者\(1\)的數目不等,則必然存在\(v\)的一個最高位使得\(v\)中有\(1\),而\(u\)終沒有
E(暴力+分治+樹)
題目連結
⭐⭐⭐⭐⭐
題目:
規定\(Fib-tree\)必須滿足以下條件
- 頂點數為\(Fibonacci數\)
- 只有一個頂點或者可以通過消除某條邊將樹分割成兩個\(Fib-tree\)
現在給出樹的邊,問是否是一個\(Fib-tree\)
解析:
-
首先對頂點數進行判定是否為\(Fibonacci數\)
-
如若這個樹可以分割成兩個子\(Fib-tree\),則一定能肯定兩個子樹的頂點數為\(fib[k-1],fib[k-2]\),同時也可以證明,如果存在多條(最多兩條,由樹的定義可知)可以分割的邊,則消除任意一條可行邊不會影響結果
證明:如果存在兩條邊分割子樹頂點數為\(fib[k-1],fib[k-2]\),如果使用某一條可行邊將其分割,另一條可行邊一定是在\(fib[k-1]\)對應的子樹中,會將\(fib[k-1]\)分割為\(fib[k-2],fib[k-3]\),所以兩條邊是等價的
-
這樣的情況下,構建一個\(getSize\)函式獲取以某個點為根,子樹的大小,如果子樹大小為\(fib[k-1]\)或者\(fib[k-2]\)則考慮分割這條邊,然後進行遞迴性的處理即可
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
vector<int> fib;
typedef pair<int, bool> P;
vector<P> e[maxn];
int siz[maxn];
int n;
void no() { printf("NO"); exit(0); }
void getSize(int u, int fa) {
siz[u] = 1;
for (auto& i : e[u]) {
if (i.second || i.first == fa) continue;
getSize(i.first, u);
siz[u] += siz[i.first];
}
}
void cutEdge(int u, int fa, int k, int& pu, int& pv, int& kd) {
for (auto& i : e[u]) {
if (pu) return;
if (i.second || i.first == fa) continue;
if (siz[i.first] == fib[k - 1] || siz[i.first] == fib[k - 2]) {
pu = u, pv = i.first;
kd = siz[i.first] == fib[k - 1] ? k - 1 : k - 2;
return;
}
cutEdge(i.first, u, k, pu, pv, kd);
}
}
void Check(int u, int k) {
if (k <= 1) return;
getSize(u, 0);
int pu = 0, pv = 0, kd = 0;
cutEdge(u, 0, k, pu, pv, kd);
if (!pu) no();
for (auto& i : e[pu])
if (i.first == pv) i.second = true;
for (auto& i : e[pv])
if (i.first == pu) i.second = true;
Check(pv, kd);
Check(pu, 2 * k - 3 - kd);
}
int main() {
int u, v;
scanf("%d", &n);
fib.push_back(1), fib.push_back(1);;
while (fib.back() < n)
fib.push_back(fib[fib.size() - 1] + fib[fib.size() - 2]);
for (int i = 1; i < n; ++i) {
scanf("%d%d", &u, &v);
e[u].push_back(P(v, false));
e[v].push_back(P(u, false));
}
if (fib.back() != n) no();
Check(1, fib.size() - 1);
printf("YES");
}