比賽-thh學長的訓練賽 (Aug 16, 2018)
1.) 歐拉回路
打表找規律,打了斯特林,打了組合數,找不出,棄療。我太蠢了,這題和前面這些東西沒什麽關系啊。考慮 \(n\) 個點時的完全圖 \(n \cdot (n - 1) / 2\) 條邊,每條邊選與不選兩種決策,所以可以得到神奇的數字 \(2^{n(n-1)/2}\) ,而答案就是 \(2^{(n-1)(n-2)/2}\) 。具體證的話,對於 \(n\) 個點的圖,拿出一個點,把 \(n-1\) 個點胡亂連邊,方案數 \(2^{(n-1)(n-2)/2}\) 。然後讓單獨拿出來的點與其他點連邊。如果某點度數是奇度就把單獨拿出來的點與這個點連一條邊,否則不連。這樣的話,連邊方案是唯一的。可以得到:A.) 除了單獨拿出來的點外,其他點的度數在連邊後為偶數。同時:B.) 根據無向圖性質,所有點度數之和為偶數。由 A 和 B 可以得到,單獨拿出來的點的度數也為偶數。因為每個點度數都是偶數,所以這樣構造的 \(n\)
#include <cstdio> typedef long long ll; const ll MOD = 998244353ll; ll mul(ll a, ll b) { ll t = 0; while (b) { if (b & 1) t = (t + a) % MOD; b >>= 1; a = (a << 1) % MOD; } return t; } ll mont(ll a, ll b) { ll t = 1; a %= MOD; while (b) { if (b & 1) t = mul(t, a); b >>= 1; a = mul(a, a); } return t; } int main() { ll n; scanf("%lld", &n); if (n == 1) { printf("1\n"); return 0; } ll ans; if (n & 1) ans = mont(mont(2, (n-1)/2), n-2); else ans = mont(mont(2, (n-2)/2), n-1); printf("%lld\n", ans); return 0; }
2.) 烏鴉坐飛機
ljh 大佬考場上想的線段樹優化建圖,多麽窒息的操作……我非常幸運地一眼看出考倍增,然後玄學寫錯爆 0 ,考完重寫了一次就 A 了,暫時不知道錯哪。
推一下 dp 方程 \(f_i = min\{f_j + w_i\}\) ,要求 \(j\) 在 \(i\) 到根的路徑上。發現是樹上極值查詢,所以就是倍增啊。
#include <stdio.h> #include <stack> #include <cstring> #include <vector> #include <cmath> #include <ctype.h> using namespace std; typedef long long ll; const int _N = 320000; const int _lgN = 21; vector<int> G[_N]; int dad[_N][_lgN], D[_N], W[_N], depth[_N], LG[_N], N, lgN; ll f[_N][_lgN], ans[_N]; int moveup(int p, int s) { for (int i = 0; i <= lgN; ++i) if ((s >> i) & 1) p = dad[p][i]; return p; } void dfs(int p, int d) { if (p != 1) { depth[p] = depth[d] + 1; dad[p][0] = d; f[p][0] = ans[d]; for (int i = 1; i <= lgN; ++i) { dad[p][i] = dad[dad[p][i-1]][i-1]; f[p][i] = min(f[p][i-1], f[dad[p][i-1]][i-1]); } int x = min(D[p], depth[p]); int tmp = moveup(p, x-(1<<LG[x])); ans[p] = min(f[tmp][LG[x]], f[p][LG[x]]) + W[p]; } for (int i = G[p].size()-1; i >= 0; --i) { int v = G[p][i]; if (v != d) dfs(v, p); } return; } int main() { scanf("%d", &N); for (int x, y, i = 1; i < N; ++i) { scanf("%d%d", &x, &y); G[x].push_back(y); G[y].push_back(x); } for (int i = 1; i <= N; ++i) scanf("%d%d", &W[i], &D[i]); lgN = log2(N + 0.5) + 1; LG[0] = -1; for (int i = 1; i <= N; ++i) LG[i] = LG[i >> 1] + 1; dfs(1, 0); int T; scanf("%d", &T); while (T--) { int x; scanf("%d", &x); printf("%lld\n", ans[x]); } return 0; }
3.) 四邊形上樹
真是妙啊。先考慮暴力,存在四邊形的充要條件是任意三邊和大於第四邊。枚舉四邊形的最長邊 \(d\) ,只要有 \(a+b+c > d\) 就可以了。直接把兩點路徑上的點排序,然後貪心地找到比枚舉的 \(d\) 小的,最大的三個數,判斷是否可以作為邊長構成四邊形。然後就是很精妙的操作了。推理可得,要構造出連續很多個數,還要使他們總是滿足 \(a+b+c \leq d\) 的話,當這些數的個數為 40 時,最大的數已經超過 int 上限了。所以對於路徑長度 \(>40\) 的詢問直接輸出 \(Yes\) ,否則暴力排序並判斷就好了。
#include <stdio.h>
#include <vector>
#include <algorithm>
const int _N = 120000;
const int X = 40;
using namespace std;
#define SC(a, b) (static_cast<a>(b))
typedef long long ll;
vector<int> G[_N];
int dad[_N], A[_N], B[_N], Bcnt, N;
bool mk[_N];
void dfs(int p)
{
for (int i = G[p].size()-1; i >= 0; --i) {
int v = G[p][i];
if (v != dad[p]) {
dad[v] = p;
dfs(v);
}
}
return;
}
int main()
{
int T;
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d", &A[i]);
for (int x, y, i = 1; i < N; ++i) {
scanf("%d%d", &x, &y);
G[x].push_back(y), G[y].push_back(x);
}
scanf("%d", &T);
dfs(1);
while (T--) {
int ins, x, y;
scanf("%d%d%d", &ins, &x, &y);
if (ins == 1) {
A[x] = y;
} else {
int a, i;
for (a = x, i = 1; i <= X; ++i)
mk[a] = 1, a = dad[a];
for (a = y, i = 1; i <= X && !mk[a]; ++i)
a = dad[a];
if (!mk[a] || !a) {
printf("Yes\n");
for (a = x, i = 1; i <= X; ++i)
mk[a] = 0, a = dad[a];
continue;
}
int lca = a;
Bcnt = 0;
for (a = x; a != lca; a = dad[a])
B[++Bcnt] = A[a];
for (a = y; a != lca; a = dad[a])
B[++Bcnt] = A[a];
B[++Bcnt] = A[lca];
if (Bcnt < 4) {
printf("No\n");
} else {
sort(B+1, B+1+Bcnt);
for (i = 4; i <= Bcnt; ++i)
if (SC(ll, B[i-3])+B[i-2]+B[i-1] > SC(ll, B[i])) break;
if (i == Bcnt+1)
printf("No\n");
else
printf("Yes\n");
}
for (a = x, i = 1; i <= X; ++i)
mk[a] = 0, a = dad[a];
}
}
return 0;
}
比賽-thh學長的訓練賽 (Aug 16, 2018)