從這裏開始
Problem A Make it Zigzag
考慮使 $1, 3, 5, 7, \cdots, 2n - 3$ 這些位置後三個中的最大值在中間,最後再處理一下最後兩個位置就行了。
Code
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 5; int n; int a[N]; vector<int> ans; int maxp(int a, int b, int c) { if (c > max(a, b)) return 2; if (b > max(a, c)) return 1; return 0; } int main() { scanf("%d", &n); for (int i = 1; i <= (n << 1); i++) { scanf("%d", a + i); } for (int i = 1; i <= (n << 1) - 2; i += 2) { int d = maxp(a[i], a[i + 1], a[i + 2]); if (d != 1) { ans.push_back(min(i + d, i + 1)); swap(a[i + d], a[i + 1]); } } int n2 = n << 1; if (a[n2 - 1] > a[n2]) { ans.push_back(n2 - 1); } printf("%d\n", (signed) ans.size()); for (auto x : ans) { printf("%d ", x); } return 0; }
Problem B Adjacent Chmax
考慮其實問題相當於一個較大數可以把較小數覆蓋掉。
依次考慮原序列每個數在最終序列中佔了多長一段。容易發現只需要滿足佔的那一段裏沒有比它大的數就可以。
然後就是個簡單 dp。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend bool operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 5005; template <typename T> bool vmax(T& a, T b) { return a < b ? (a = b, true) : false; } int n; int vis[N]; int a[N], fa[N]; Zi f[N], g[N]; void build(int l, int r, int v) { if (l > r) { return; } int mx = -1, mxp = 0; for (int i = l; i <= r; i++) { if (vmax(mx, a[i])) { mxp = i; } } fa[mxp] = v; build(l, mxp - 1, mxp); build(mxp + 1, r, mxp); } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); } a[0] = N, a[n + 1] = N; f[0] = 1; for (int i = 1; i <= n; i++) { int l = i, r = i; while (a[l - 1] < a[i]) l--; while (a[r + 1] < a[i]) r++; Zi s = 0; for (int j = l; j <= r; j++) { g[j] += (s += f[j - 1]); } for (int j = 0; j <= n; j++) { g[j] += f[j]; f[j] = g[j]; g[j] = 0; } } printf("%d\n", f[n].v); return 0; }
Problem C Planar Tree
差點不會這題,感覺好毒瘤啊。
如果只有 1, 2 這個題瞎做就好了。
注意到如果奇數和偶數都能連,就是隻有 1, 2 的情況,可是 1, 4 不能連。考慮先把這兩個煩人的東西幹掉。
考慮 $(1, 2)$,$(3, 4)$ 相鄰的時候直接連起來,然後分別當做只有一個點 2 或者 3。假如原問題有解且不存在這條邊,那麼顯然這條邊可以加上去,然後環上隨便刪一條邊就行了。因此原問題和新問題有解性等價。
注意到如果有連續兩個數是相同的,那麼可以在其中 1 個連邊的時候,兩個一起連。考慮把這兩個縮一起。和上面同樣的方法證明兩個問題有解性等價,只是可以加邊的原因略有不同。
現在問題變成 1 不和 2 相鄰,4 不和 3 相鄰,相鄰的數不同,目標把所有的 1, 4 連到別的點上。可以發現,樹上不跨過 4 的 $(1, 2)$ 相連會導致至少 1 個 $3$ 不能繼續和 4 相連。(如果有跨過 4 的話就考慮這個 $(3, 4)$,這樣處理下去可能會得到不跨過 2 的 $(3, 4)$ 相連,但和這個情形是相似的)這樣的情況一定是 $(1, 3, 2)$,考慮把這個縮成 $2$,繼續這個操作。所以每消去一個 1 就會減少一個 3,每消去一個 4 就會減少一個 2。
如果不存在 $(1, 3, 2)$ 也不存 $(4, 2, 3)$,那麼說明不存在 $(2, 3)$ 相鄰的情況,此時可以得到 $4$ 的數量大於等於 $2$ 的數量, $3$ 的數量大於等於 $1$ 的數量(等於是因爲可能都沒有),此時顯然不可能。
相反如果滿足 $2$ 的數量加上 $3$ 的數量大於 $1$ 的數量加上 $4$ 的數量,同時 1 的數量大於等於 3 的數量,4 的數量大於等於 2 的數量,因爲每次兩個數量和同時減少,所以不可能出現不存在 $(2, 3)$ 相鄰的情形。因此只用處理一下,然後數數量就可以了。
Code
#include <bits/stdc++.h> using namespace std; const int N = 3e5 + 5; int T, n; int a[N], stk[N], cnt[5]; int merge(int x, int y) { if (x > y) swap(x, y); if (x == y) { return x; } if (x + 1 == y && x != 2) { return x == 1 ? y : x; } return 0; } void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); } int pos = 1, top = 1; while (a[pos] == a[1]) pos++; rotate(a + 1, a + pos, a + n + 1); stk[top] = a[1]; for (int i = 2, x; i <= n; i++) { x = merge(stk[top], a[i]); if (x) { stk[top] = x; } else { stk[++top] = a[i]; } } int frt = 1, tmp; while (frt < top && (tmp = merge(stk[frt], stk[top]))) { stk[top] = tmp; frt++; } memset(cnt, 0, sizeof(cnt)); for (int i = frt; i <= top; i++) { cnt[stk[i]]++; } if (cnt[4] <= cnt[2] && cnt[1] <= cnt[3] && cnt[1] + cnt[4] < cnt[2] + cnt[3]) { puts("Yes"); } else { puts("No"); } } int main() { scanf("%d", &T); while (T--) { solve(); } return 0; }
Problem D Yet Another ABC String
這種題比那種無聊的猜結論題有意思多了。
樸素容斥的話相當於硬點若干個位置,然後這個位置起始長度爲 3 的子串不合法。有重疊的話不好處理,主要因爲 3 種字符的數量有限制。
考慮讓硬點的位置後面一定不和它相連。就是 ABC 後面不是 A。相當於就是對每個這樣連續的 ABCABCAB 末做一個子集容斥。
然後就是個簡單組合計數。
Code
#include <bits/stdc++.h> using namespace std; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend bool operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 3e6 + 5; int A, B, C, S; Zi fac[N], _fac[N], pw2[N]; void init(int n) { fac[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = fac[i - 1] * i; } _fac[n] = ~fac[n]; for (int i = n; i; i--) { _fac[i - 1] = _fac[i] * i; } pw2[0] = 1; for (int i = 1; i <= n; i++) { pw2[i] = pw2[i - 1] * 2; } } void cswap(int& a, int& b) { if (a > b) { swap(a, b); } } Zi comb(int n, int m) { return n < m ? 0 : fac[n] * _fac[m] * _fac[n - m]; } int main() { scanf("%d%d%d", &A, &B, &C); S = A + B + C; cswap(B, C), cswap(A, B), cswap(B, C); init(S); Zi ans = 0, tmp; for (int i = 0; i <= A; i++) { // the last one isn't included int rest = S - 3 * i; tmp = comb(rest + i - 1, i) * pw2[i] * fac[rest] * _fac[A - i] * _fac[B - i] * _fac[C - i]; // the last one is included if (i) { tmp += comb(rest + i - 1, i - 1) * pw2[i - 1] * 3 * fac[rest] * _fac[A - i] * _fac[B - i] * _fac[C - i]; } if (i & 1) { ans -= tmp; } else { ans += tmp; } } printf("%d\n", ans.v); return 0; }
Problem E Nearer Permutation
感覺這種題和碼農題一樣無聊,反正順着思路做就完了,每步難度不大,就是思考的過程特別長。雖然也有可能是因爲我菜。
考慮怎麼找出 $z$。
首先考慮怎麼算 $d(x, z)$,相當於將 $x$ 第一個位置上的數,在 $z$ 中改爲 $1$,第二位置上的數,在 $z$ 中改爲 $2$,然後求 $z'$ 的逆序對數。
這個顯然相當於 $i$ 在兩個排列中出現的位置構成的有序二元組的逆序對數。
那麼現在有 2 列二元組,每列二元組只知道其中 1 個數,從小到大把 $1$ 到 $n$ 填進去,每次儘可能填較小的數對應的二元組。
現在問題變成判定把剩下的填進來其中 1 列的逆序對數是否不超過另一列的逆序對數。
可以發現,只有每對 $i$ 在 $x$ 和 $y$ 中出現的位置構成的有序二元組的逆序對會對差有貢獻,並且貢獻爲 $+1$ 或者 $-1$。(其實這個逆序對其實就是 $i$ 在 $x$ 中出現的位置 $p_i$ 的逆序對)如果 $p_i > p_j (i < j)$ 並且 $(p_i, ?)$ 被先填上數,那麼會產生 $+1$ 的差。(最終需要差小於等於 0)我們下面稱違反一個逆序對 $(i, j) (i < j, p_i > p_j)$ 指先在 $(p_i, ?)$ 填數。
因此我們可以至多違反逆序對總數除以 $2$ 向下取整個逆序對。
然後很容易得到一個明確的求 $z$ 的做法:假設當前在確定第 $i$ 個位置上的數,找儘量小的 $j$ 滿足上一條限制,然後填上 $(p_j, i)$。
現在回到原問題。
現在我們要填 $p_1, \cdots, p_n$,題目指定了上面做法填 $(p_i, ?)$ 的順序。
當我們填了一個 $(p_i, ?)$ 且前面有沒有填的 $(p_j, ?)$ 時相當於限制此時能違反的數量小於填 $(p_j, ?)$ 會違反的數量。考慮每次填的過程,允許違反的數量每次單調不增,後者至多減少 1。所以如果下一次填的位置 $(p_k, ?)$ 滿足 $k < i$,那麼意味着上面這個差是剛好變爲 $0$,所以填完 $(p_k, ?)$ 後不能再違反任何逆序對。這之後只能每次填 $p$ 最小的沒填的位置。
我們將 $p_i$ 分爲 4 類:
- 第一類位置:滿足 $i = z_i$
- 第三類位置:第一個滿足 $z_i > z_{i + 1}$ 的 $z_{i + 1}$
- 第二類位置:設第三類位置爲 $z_t$,那麼在 $z_1, \cdots, z_{t - 1}$ 中滿足 $z_i > i$ 的爲第二類位置
- 第四類位置:剩下的
容易發現對於第一類位置沒有限制。如果一個現在要填的位置 $p_i$,之前還有沒有填的位置 $p_j$,那麼一定滿足 $p_i < p_j$,否則能違反的限制數一定滿足能先填 $p_j$。因此在不考慮第三類位置的情況下,第二類位置的限制在處理出第四類位置的 $p$ 的相對順序後就已知了。
我們希望使得逆序對數減去 2 倍被違反的逆序對數爲 $0$ 或者 $1$。我們先考慮這個東西的最小值。注意到每產生一個被違反的逆序對的時候對這個值的貢獻爲 $-1$。
在不考慮第三類位置的時候,最優方案爲第一類位置依次填 $n, n - 1, \cdots$,第一個第二類位置填能填最大的數(能違反的逆序對數到這裏的時候最大值就是這個位置上的限制)剩下按順序填最小的數。
注意到你可以連續地把這個值加上 1,所以如果第三類位置在第一個第二類位置的後面就做完了。
考慮第三類位置在所有第二類位置之前的情形。
如果第三類位置填上後後面有 $x$ 個數比它小,那麼到這個位置的時候允許違反的逆序對數也是 $x$。第三類位置對於倒數第 $i$ 個被填上的第二類位置有一個限制:這後面至多有 $i - 1$ 個數比它小(注意求 $z$ 的過程運行到這裏的時候允許違反的逆序對數是 $x$)。此時顯然最優的話把第一個第二類位置填能填的最大的,剩下的沒填的按順序填能填的最小的。因此我們只用求一下最大可能的 $x$ 即可。(注意這裏所有的第二類位置都對這個第三類位置有限制,因爲經過第一個第二類位置後,允許違反的逆序對數是 $x$ 不是 $0$)。
Code
#include <bits/stdc++.h> using namespace std; template <typename T> bool vmin(T& a, T b) { return a > b ? (a = b, true) : false; } const int N = 3e5 + 5; typedef class Fenwick { public: int n; int a[N]; void init(int n) { this->n = n; fill(a, a + n + 1, 0); } #define lowbit(_) ((_) & -(_)) void add(int idx, int val) { for ( ; idx <= n; idx += lowbit(idx)) { a[idx] += val; } } int query(int idx) { int ret = 0; for ( ; idx; idx -= lowbit(idx)) { ret += a[idx]; } return ret; } int query(int l, int r) { return query(r) - query(l - 1); } } Fenwick; int T, n; Fenwick fen; int a[N], les[N]; void solve() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", a + i); } int p1, p2; for (p1 = 1; p1 <= n && a[p1] == p1; p1++); if (p1 >= n) { puts("Yes"); return; } for (p2 = p1 + 1; p2 <= n && a[p2] > a[p2 - 1]; p2++); long long dif = -1ll * (n + n - p1) * (p1 - 1) / 2; fen.init(n); for (int i = n; i >= p1; i--) { int p = a[i]; dif += fen.query(p); fen.add(p, 1); } for (int i = 1; i <= n; i++) { les[i] = n + 1; } fen.init(n); for (int i = p2; i <= n; i++) { int p = a[i]; if (i != p2) { les[p] = fen.query(p, n); } fen.add(p, 1); } if (a[p2] > a[p1]) { int lim = n + 1; for (int i = p1; i <= a[p1]; i++) { vmin(lim, les[i]); } dif -= lim + (p2 - p1 - 1); } else { int lim = n - a[p2] - (p2 - p1); for (int i = 2; i <= n; i++) { vmin(les[i], les[i - 1]); } for (int i = p1; i < p2; i++) { vmin(lim, les[a[i]] + p2 - i - 1); } vmin(lim, les[a[p2]] - 1); dif -= lim + p2 - p1 - 1; } puts((dif < 2) ? "Yes" : "No"); } int main() { scanf("%d", &T); while (T--) { solve(); } return 0; }
Problem F Authentic Tree DP
別催了,在路上了.jpg