A(水題)
題目連結
⭐
題目:
給出指定\(n\),求解出一段區間\([l,r]\)使得\(\sum\limits_{i=l}^ri=n\)
解析:
從點0,1兩點作為起點分別向左右延伸長度,每次延伸單位長度時,這段區間和也加1,因此答案為\([1-n,n]\)
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
long long t;
scanf("%d", &T);
while (T--) {
scanf("%lld", &t);
printf("%lld %lld\n", 1 - t, t);
}
}
B(水題)
題目連結
⭐
題目:
給出底數\(n\),由若干不同的\(n^i(i\ge0)\)組成的數中,第\(k\)大的數是多少
解析:
即\(n^i\)只有出現或者不出現兩種可能,也就是說它的係數只能是0或1,這樣第\(k\)大對應的係數即為\(k\)對應的二進位制形式
#include<bits/stdc++.h>
using namespace std;
long long n, k, mod = 1e9 + 7;
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%lld%lld", &n, &k);
long long t = 1, ans = 0;
while (k) {
if (k & 1) ans = (ans + t) % mod;
k >>= 1;
t = t * n % mod;
}
printf("%lld\n", ans);
}
}
C(埃篩)
題目連結
⭐
題目:
給出一組下標\(a(a_i\le n)\),每次可以指定一個\(x\),將\(x\nmid a_i\)則消除\(a_i\),問最少指定幾個\(x\)
解析:
- 對於特殊情況如果\(a\)的長度為\(0\),則直接輸出\(0\)
- 考慮如何一次清除,可以用類似埃篩的思想,對於任意一個不在\(a\)中的下標,考慮它小於\(n\)的倍數是否在陣列中,如果均不在陣列中,則輸出這個值
優化: 考慮證明結論如果n的後一半數中,存在某個值不在下標序列中,則這個值可以作為答案。充分性顯然成立,必要性考慮\(n\)的前一半數如果作為答案,則一定有一個它的倍數屬於\(n\)的後一半數,且不在下標序列中 - 上述情況都不滿足時,輸出\(n,n-1\)即可消除所有的數(因為\(x=n\)時,無法消除\(n\))
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
char str[maxn];
int main() {
int T;
int n;
char ch;
scanf("%d", &T);
while (T--) {
scanf("%d %c%s", &n, &ch, str);
bool ok = 0;
for (int i = 0; i < n; ++i)
if (str[i] != ch) {
ok = true;
break;
}
if (ok) {
for (int i = n / 2; i < n; ++i) {
if (str[i] == ch) {
ok = false;
printf("1\n%d\n", i + 1);
break;
}
}
if (ok)
printf("2\n%d %d\n", n - 1, n);
}
else printf("0\n");
}
}
D(帶權並查集)
題目連結
⭐⭐
題目:
現在有\(n\)個人,有誠實的人也有撒謊的人。誠實的人只說真話,撒謊的人只說假話,他們共說了\(m\)句話,以\(i\quad j\quad identity\)的形式給出,代表\(i\)認為\(j\)是什麼身份,如果言語不合法則輸出-1,否則輸出最多的撒謊者個數
解析:
這道題與UVA 10158非常類似,如果\(i\)認為\(j\)是誠實的人,則\(i,j\)同真同假,反之一真一假,設\([1,n]\)代表1\(\sim n\)是誠實的人的情況,\([n+1,2n]\)代表是撒謊者的情況,初始時對於所有撒謊情況賦予權重1
- 如果屬於同真同假,則判斷是否二者已處於一真一假的狀態,如果是則集合加入失敗,反之將\((i,j)\)與\((i+n,j+n)\)加入同一集合
- 如果屬於一真一假,則判斷是否二者已處於同真同假的狀態,如果是則集合加入失敗,反之將\((i,j+n)\)與\((i+n,j)\)加入同一集合
最後統計答案時,考慮遍歷所有人,如果未加入答案集合中,則新增\(\max\{sz_{撒謊者},sz_{誠實的人}\}\),並將其本身與連帶關係加入答案集合
注意:
由於已有關係仍然可能出現,而在建立關係過程中更改了\(sz\)陣列,所以此時要對建立關係的步驟進行忽略
#include<cstdio>
#include<algorithm>
using namespace std;
int n, m;
const int maxn = 4e5 + 5;
int p[maxn], sz[maxn];
int find(int x) {
if (p[x] == x) return x;
return p[x] = find(p[x]);
}
bool set2(int x, int y) {
int x1 = find(x), x2 = find(y), y1 = find(x + n), y2 = find(y + n);
if (x1 == x2) return false;
if (x1 == y2 || x2 == y1) return true;
sz[x1] += sz[y2];
sz[y1] += sz[x2];
p[y2] = x1;
p[x2] = y1;
return true;
}
bool set1(int x, int y) {
int x1 = find(x), x2 = find(y), y1 = find(x + n), y2 = find(y + n);
if (x1 == y2 || x2 == y1) return false;
if (x1 == x2) return true;
sz[x1] += sz[x2];
sz[y1] += sz[y2];
p[x2] = x1;
p[y2] = y1;
return true;
}
char str[1000];
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; ++i)
sz[i] = 0, p[i] = i;
for (int i = n + 1; i <= 2 * n; ++i)
sz[i] = 1, p[i] = i;
int a, b;
bool ok = true;
while (m--) {
scanf("%d%d%s", &a, &b, str);
if (str[0] == 'i')
ok &= set2(a, b);
else ok &= set1(a, b);
}
for (int i = 1; i <= n; ++i)
if (find(i) && find(i + n)) {
sz[0] += max(sz[find(i)], sz[find(i + n)]);
p[find(i)] = p[find(i + n)] = 0;
}
if (ok)
printf("%d", sz[0]);
else
printf("-1");
printf("\n");
}
}
E hard version(搜尋+狀態轉移+組合)
題目連結
⭐⭐⭐
題目:
給出一個高度為\(k\)的滿二叉樹,編號按層次遍歷,現要給每個節點染色,其中相鄰節點不能均為(紅、橙)(白、黃)(綠、藍)三對中某一對,且有些節點色彩已被指定,問染色方案數
解析:
根據Easy Version的結論,對於每個節點來說,如果父節點顏色已被確定,則該節點有4種方案,所以當某些節點(假設總個數未\(cnt_{else}\))未被指定顏色節點影響時,貢獻為\(4^{cnt_{else}}\)
對於那些被影響的節點,考慮使用\(map\)儲存每個節點的顏色屬於的對,並且對他的所有父節點進行標記(被影響),\(cnt_{else}=2^k-1-map_{size}\),考慮進行搜尋(假設\(v[0,1,2]\)分別代表當前節點取某個顏色對中的顏色時,可行的方案數)不難得到以下轉移式
最後答案即為\((v_1[0]+v_1[1]+v_1[2])\times 4^{cnt_{else}}\)
注意:
博主也不知道為什麼,這裡使用\(map\)可以AC,但使用\(unordered\_map\)會TLE在第9個樣例(若有同學知曉,可否評論區指點一二)
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
char str[maxn];
map<long long, int> m;
int k, n;
const long long mod = 1e9 + 7;
long long ksm(long long a, long long b) {
long long ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
b >>= 1;
a = a * a % mod;
}
return ans;
}
vector<long long> dfs(long long x) {
vector<long long> t = { 2,2,2 };
if (m.count(x) && ~m[x]) {
for (int i = 0; i < 3; ++i)
t[i] = i == m[x];
}
for (long long i : {x << 1, x << 1 | 1}) {
if (!m.count(i)) continue;
vector<long long> s = dfs(i);
t[0] = (t[0] * (s[1] + s[2]) % mod) % mod;
t[1] = (t[1] * (s[0] + s[2]) % mod) % mod;
t[2] = (t[2] * (s[0] + s[1]) % mod) % mod;
}
//printf("%lld:%lld %lld %lld\n", x, t[0], t[1], t[2]);
return t;
}
int main() {
long long v;
char ch;
scanf("%d%d", &k, &n);
while (n--) {
scanf("%lld %c%*s", &v, &ch);
switch (ch) {
case 'r':
case 'o': m[v] = 0; break;
case 'w':
case 'y': m[v] = 1; break;
default: m[v] = 2;
}
v >>= 1;
while (v) {
if (!m.count(v))
m[v] = -1;
else break;
v >>= 1;
}
}
vector<long long> ans = dfs(1);
printf("%lld", (ans[0] + ans[1] + ans[2]) % mod * ksm(4, (1ll << k) - 1 - m.size()) % mod);
}
F(構造)
題目連結
⭐⭐⭐
題目:
將\(s\)個物品分為\(n\)堆,每堆編號從\(1\sim n\),且保證每堆至少有一個物品,問是否對於任意一種物品分發方式都存在一個連續區間\([l,r]\)使得,這段區間內對應堆中物品數量和為\(k\)
解析:
當\(s=k\)時,即每次取所有堆的情況,顯然成立
當\(s<k\)時,取所有物品也到不了\(k\),顯然不成立
對於\(s>k\)的情況,考慮構造一個所用物品最小,且不滿足條件的分配方式
不難發現這樣的構造方式下,需要對每個堆分發一個物品,剩下的物品數量為\(s-n\),而下標為\(k\)的倍數的位置,需要再多分配\(k\)個物品,總計為\(\lfloor \frac{n}{k}\rfloor k\)個物品,那麼當\(s-n<\lfloor \frac{n}{k}\rfloor k\)時無法構造
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
long long s, n, k;
scanf("%d", &T);
while (T--) {
scanf("%lld%lld%lld", &s, &n, &k);
if (s == k) printf("YES\n");
else if (s < k) printf("NO\n");
else printf("%s\n", n / k * k > s - n ? "YES" : "NO");
}
}