T1
洛谷 P5921 原始生物
將每個特徵視為一條邊,建出有向圖。每個弱連通分量是獨立的,分開考慮。如果某一整個弱連通分量一起構成一個尤拉回路,則這個弱連通分量對答案的貢獻就是邊數 \(+ 1\)。否則該弱連通分量中每個點對答案的貢獻為 \(\max \{ in_i, out_i \}\),其中 \(in_i, out_i\) 代表 \(i\) 的入度和出度。感性理解一下,這個點要作為路徑中的點被經過 兩者的最小值 次,剩下的就透過構造以 \(i\) 為起點或終點的路徑解決掉,則這個點正好就出現了 \(\max \{ in_i, out_i \}\) 次。
程式碼
#include <iostream>
using namespace std;
int f[1005], ok[1005], in[1005], out[1005];
int getf(int x) { return (x == f[x] ? x : (f[x] = getf(f[x]))); }
void Merge(int x, int y) {
x = getf(x), y = getf(y);
if (x != y)
f[x] = y;
}
int c[1005];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= 1000; i++) f[i] = i, ok[i] = 1;
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
out[x]++, in[y]++;
Merge(x, y);
}
for (int i = 1; i <= 1000; i++) {
ok[getf(i)] &= (in[i] == out[i]);
c[f[i]] += max(in[i], out[i]);
}
int ans = 0;
for (int i = 1; i <= 1000; i++) {
if (f[i] != i || (!in[i] && !out[i]))
continue;
ans += c[i] + (ok[i]);
}
cout << ans << "\n";
return 0;
}
T2
洛谷 P5944 出圈遊戲
首先把題目給的條件變成第 \(i\) 個出圈的是誰。然後對於每一輪報數,可以透過暴力模擬求出這一輪報數的人和這一輪出圈的人之間的距離,這個距離就是 \(k\) 對這一輪開始時剩餘人數取模所得的值。這樣就可以構造出一個 \(n\) 個同餘方程的同餘方程組,直接使用 excrt 解即可。
程式碼
#include <iostream>
#define int long long
#define r first
#define m second
using namespace std;
int n;
pair<int, int> asdf[200005]; // r, m
bool del[200005];
int A[200005];
int gcd(int a, int b) { return (b ? gcd(b, a % b) : a); }
void exgcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1, y = 0;
return;
}
int y1;
exgcd(b, a % b, y1, x);
y = y1 - (a / b) * x;
}
pair<int, int> Merge(pair<int, int> a, pair<int, int> b) {
pair<int, int> c;
int m1 = a.m, m2 = b.m, r1 = a.r, r2 = b.r, d = gcd(m1, m2);
if ((r2 - r1) % d != 0) {
cout << "NIE\n";
exit(0);
}
int p1 = m1 / d, p2 = m2 / d;
int x, y;
exgcd(p1, p2, x, y);
c.r = x * (r2 - r1) * m1 / d + r1;
c.m = m1 * m2 / d;
c.r = (c.r % c.m + c.m) % c.m;
return c;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
A[x] = i;
}
int st = 1;
for (int i = 1; i <= n; i++) {
int cnt = 0;
for (int j = st; ; j = j % n + 1) {
cnt += (!del[j]);
if (j == A[i])
break;
}
del[A[i]] = 1;
st = A[i];
asdf[i] = make_pair(cnt, n - i + 1);
asdf[i].r %= asdf[i].m;
}
int sz = n;
while (sz ^ 1) {
pair<int, int> a, b;
a = asdf[sz], b = asdf[sz - 1];
asdf[--sz] = Merge(a, b);
}
int ans = (asdf[1].r % asdf[1].m + asdf[1].m) % asdf[1].m;
cout << (ans == 0 ? asdf[1].m : ans) << "\n";
return 0;
}
T3
洛谷 P8857 滑雪者
每條邊設一個下界 \(1\),直接跑有源匯上下界最小流即可。
有源匯上下界網路流第一次構造出的可行流流量是從小 T 到小 S 的附加邊流量!!!
程式碼
#include <iostream>
#include <queue>
#define int long long
using namespace std;
const int inf = 2147483647;
int n;
int S, T;
int head[5005], nxt[5000005], to[5000005], res[5000005], ecnt = 1;
int cur[5005];
void add(int u, int v, int ww) {
to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt, res[ecnt] = ww;
to[++ecnt] = u, nxt[ecnt] = head[v], head[v] = ecnt, res[ecnt] = 0;
}
int in[5005], out[5005];
void add(int u, int v, int l, int r) {
add(u, v, r - l);
in[v] += l;
out[u] += l;
}
queue<int> q;
int dep[5005];
bool bfs() {
for (int i = 1; i <= n + 2; i++) dep[i] = -1;
dep[S] = 1;
q.push(S);
while (!q.empty()) {
int x = q.front();
q.pop();
for (int i = head[x]; i; i = nxt[i]) {
int v = to[i];
if (res[i] && dep[v] == -1) {
dep[v] = dep[x] + 1;
q.push(v);
}
}
}
return (dep[T] != -1);
}
int dfs(int x, int flow) {
if (x == T)
return flow;
int ret = 0;
for (int i = cur[x]; i && flow; i = nxt[i]) {
cur[x] = i;
int v = to[i];
if (dep[v] == dep[x] + 1 && res[i]) {
int tmp = dfs(v, min(flow, res[i]));
if (tmp) {
res[i] -= tmp;
res[i ^ 1] += tmp;
flow -= tmp;
ret += tmp;
}
}
}
if (!ret)
dep[x] = -1;
return ret;
}
int dinic() {
int ret = 0;
while (bfs()) {
for (int i = 1; i <= n + 2; i++) cur[i] = head[i];
ret += dfs(S, inf);
}
return ret;
}
signed main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
cin >> n;
S = n + 1, T = n + 2;
for (int i = 1; i < n; i++) {
int k;
cin >> k;
while (k--) {
int v;
cin >> v;
add(i, v, 1, inf);
}
}
int rec = ecnt;
add(n, 1, 0, inf);
for (int i = 1; i <= n; i++) {
if (in[i] > out[i])
add(S, i, in[i] - out[i]);
if (in[i] < out[i])
add(i, T, out[i] - in[i]);
}
dinic();
int f1 = res[rec + 2]; // !!!!! 不是上面那個 dinic() 的返回值 !!!!!
for (int i = rec + 1; i <= ecnt; i++) res[i] = 0;
S = n, T = 1;
cout << f1 - dinic() << "\n";
return 0;
}
T6
洛谷 P5919 MAK
觀察題目,發現實際上這個置換構成一堆環,要求所有環長度和為 \(n\),然後求這些環長的 \(lcm\) 的最大值。我們先考慮如何最小化一個環的字典序。設環長為 \(k\),環上最小的點為 \(a\),則最小的字典序一定是 \(a + 1, a + 2, \cdots, a + k - 1, a\)。然後來觀察性質。
-
在合法且 \(lcm\) 相同的方案中,自環個數越多的方案一定更好。設某方案中自環的個數為 \(cnt\)。首先肯定把所有自環往前扔,不然一定不優。這樣兩個方案最終排出來一定是 \(1, 2, \cdots, cnt_1\) 和 \(1, 2, \cdots, cnt_2\),然後 \(cnt\) 的後一項和 \(cnt\) 的差就肯定不是 \(1\) 了。根據字典序,可以發現 \(cnt\) 更大的更優。
-
設某最優方案中所有環長為 \(x_1, x_2, \cdots, x_n\),則每兩個 \(x\) 都是互質的。如果有兩個 \(x\) 不互質,就可以把其中一個除掉它們的 \(\gcd\),使得整個方案的 \(lcm\) 不變,而多出來的這些可以全填成長度為 \(1\) 的環,這樣根據上一個性質,新方案更優。所以最優方案中所有環長兩兩互質。
-
最優方案中所有環長一定都只有 \(1\) 種質因子。假設某個環長 \(x\) 有兩種或更多質因子,那我們一定可以把 \(x\) 寫成 \(p^a \times q^b \times c\) 的形式,其中 \(p, q\) 為質數,\(a, b, c\) 為正整數。注意到此時我們有 \(p^a \ge 2\),\(q^b \times c \ge 2\)。所以我們可以得出 \(p^a \times q^b \times c > p^a + q^b \times c\)(\(a, b > 1 \implies (a - 1)(b - 1) > 0 \implies ab > a + b\))。這樣我們就可以把一個長度為 \(x\) 的環拆成兩個長度分別為 \(p^a\) 和 \(q^b\times c\) 的環,而保證所有數的和變小。這樣多出來的這些和我們就又可以拿來當自環,從而根據性質 \(1\) 使得答案更優。
這樣我們就發現所有答案中的環長一定是 \(p^a\) 次方的形式,其中 \(p\) 為質數,\(a\) 為自然數。這樣我們就可以進行一個 dp。先篩出 \(10^4\) 次方以內的所有素數,然後設 \(dp[i][j]\) 表示使用了前 \(i\) 個素數,所有環長和為 \(j\) 時的最大 \(lcm\)。轉移時列舉所有素數,再列舉所有和,然後列舉所有這個素數的次方進行轉移,方程為 \(dp[i][j] = \max\limits_{k}^{p_i^k \le j} \{ dp[i - 1][j - p_i^k] \times p_i^k \}\),其中 \(p_i\) 表示從小往大第 \(i\) 個素數。然後由於要輸出方案,所以要記錄轉移路徑。
輸出方案就是把最優方案中的所有環長搞出來,排個序,從小往大依次往裡填即可。
有幾個地方要注意:
-
一個質數可以不選,也可以選擇其 \(0\) 次方,這兩者是不同的。具體見程式碼。
-
由於 dp 值可能過於巨大,需要開 long double 存 dp 值。我開的是 long double,所以不知道開 double 能不能過。
-
雖然 \(10^4\) 以內有將近 \(3000\) 個素數,但是我們 dp 時只取前 \(200\) 個即可透過,否則陣列不一定開的下。
程式碼
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int N = 10000;
int p[100005], pcnt;
bool mark[100005];
void F(int n) {
for (int i = 2; i <= n; i++) {
if (!mark[i]) {
p[++pcnt] = i;
}
for (int j = 1; j <= pcnt && i * p[j] <= n; j++) {
mark[i * p[j]] = 1;
if (i % p[j] == 0)
break;
}
}
}
long double f[205][10005];
int g[205][10005];
void ini() {
F(N);
f[0][0] = 1;
pcnt = 200;
for (int i = 1; i <= pcnt; i++) {
for (int j = 0; j <= N; j++) { // 不選這個質數
f[i][j] = f[i - 1][j];
g[i][j] = 0;
}
for (int j = 1; j <= N; j *= p[i]) { // j = 1 即為選 0 次方
for (int k = j; k <= N; k++) {
if (f[i - 1][k - j] * j > f[i][k])
f[i][k] = f[i - 1][k - j] * j, g[i][k] = j;
}
}
}
}
int c[10005], ccnt;
void Answer(int n) {
int s = n;
ccnt = 0;
for (int i = pcnt; i; i--) {
c[++ccnt] = g[i][s];
s -= c[ccnt];
}
sort(c + 1, c + ccnt + 1);
int cur = 1;
for (int i = 1; i <= ccnt; i++) {
if (c[i] == 0)
continue;
for (int j = 1; j < c[i]; j++) cout << cur + j << " ";
cout << cur << " ";
cur += c[i];
}
cout << "\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int tc;
cin >> tc;
ini();
while (tc--) {
int n;
cin >> n;
Answer(n);
}
return 0;
}