以前寫過一份 Day2 的。
這套題難度很大。當年賽場上貌似得分率不高。
尋寶遊戲
把 \(\vee\) 和 \(\land\) 看成 0/1,與原序列對比發現變成答案中的 0/1 等價於比較數字的大小。最後排序即可。
#include <bits/stdc++.h>
using std::sort;
const int N = 1005, M = 5005, P = 1e9 + 7;
int n, m, Q, a[M][N], l[M]; char ch[M];
bool cmp(int x, int y) {
for (int i = n; i; i--)
if (a[x][i] != a[y][i]) return a[x][i] < a[y][i];
return 0;
}
int main() {
scanf("%d%d%d", &n, &m, &Q);
for (int i = 1; i <= n; i++) {
scanf("%s", ch+1);
for (int j = 1; j <= m; j++)
a[j][i] = ch[j]-'0';
}
for (int i = 1; i <= m; i++) l[i] = i;
sort(l+1, l+m+1, cmp);
while (Q--) {
int x = 0, y = 0, ok = 1;
scanf("%s", ch+1);
for (int i = 1; i <= m; i++) {
if (ch[l[i]] == '1' && !x) x = i;
if (ch[l[i]] == '0') y = i;
}
if (x) for (int i = n; i; i--)
if (a[l[x]][i] != a[l[y]][i]) {
if (a[l[x]][i] < a[l[y]][i]) ok = 0;
break;
}
if (!ok) puts("0");
else {
int ans = x?0:1;
for (int i = n; i; i--)
ans = ((2*ans+a[l[x]][i]-a[l[y]][i])%P + P)%P;
printf("%d\n", ans);
}
}
return 0;
}
轉盤
兩處縮放:第一處在選取解時,我們把選取每個物品的時間在總時間不變的情況下儘可能推遲。發現當一個遞增序列的值處處大於時間則遞增序列最大值即爲答案。
第二處在於發現上述問題的性質,破環成鏈再對下標差分後,求 每個長度爲 \(n\) 的區間最大值+一個與下標相關的偏移量 前面這個整體的 最小值。分析發現可以進一步把區間長度放到後綴長度。這一點很重要。
後綴max遞減,最後發現實際上就是一個樓房重建問題。複雜度 \(\mathcal O(n\log^2 n)\)。
#include <bits/stdc++.h>
using std::max;
using std::min;
const int N = 100005;
int n, m, p, las;
#define lc (o << 1)
#define rc (o << 1 | 1)
int ma[N*4], val[N*4], fix[N*4];
int query(int o, int l, int r, int v) {
if (l == r) return v < ma[o] ? val[o] : v+l;
int mid = l+r>>1;
return v > ma[rc] ? min(query(lc, l, mid, v), v+mid+1) : min(fix[o], query(rc, mid+1, r, v));
}
void pushup(int o, int l, int r) {
int mid = l+r>>1;
ma[o] = max(ma[lc], ma[rc]);
val[o] = min(val[rc], fix[o] = query(lc, l, mid, ma[rc]));
}
void modify(int o, int l, int r, int p, int v) {
if (l == r) { ma[o] = v; val[o] = v+p; return; }
int mid = l+r>>1;
p <= mid ? modify(lc, l, mid, p, v) : modify(rc, mid+1, r, p, v);
pushup(o, l, r);
}
int main() {
scanf("%d%d%d", &n, &m, &p);
for (int i = 1; i <= n; i++) { int x; scanf("%d", &x), modify(1, 1, n, i, x-i); }
printf("%d\n", las = query(1, 1, n, ma[1]-n) + n - 1);
while (m--) {
int x, y; scanf("%d%d", &x, &y); p && (x ^= las, y ^= las);
modify(1, 1, n, x, y-x);
printf("%d\n", las = query(1, 1, n, ma[1]-n) + n - 1);
}
return 0;
}
毒瘤
合理刪去 \(n-m+1\) 條邊後是一棵樹。這樣子做 DP 還需要關心去掉的 \(n-m+1\) 對限制。
枚舉這些限制,總方案數爲 \(3\times 2^{n-m+1}=\mathcal O(2^{n-m+1})\)。給上限制再做 DP 每次都要 \(\mathcal O(n)\) 的複雜度,太屑了。
考慮優化,注意到 DP 實際上只跟這些點構成的虛樹有關。然後就可以類似 NOIP2018 的 D2T3 一樣搞,只不過這裏不用倍增。仍然要注意維護 DP 值的順序。
總複雜度 \(\mathcal O(n + s2^s)\),這裏 \(s=n-m+1\)。
#include <bits/stdc++.h>
using std::vector;
const int N = 100020, P = 998244353;
int n, m, ans = 0;
struct Edge {
int v, nxt;
} e[N*2];
int edges = 0, G[N];
void adde(int u, int v) {
e[edges++] = (Edge){v, G[u]}, G[u] = edges-1;
}
int fa[N];
int fset(int x) { return fa[x] == x ? x : fa[x] = fset(fa[x]); }
void merge(int x, int y) { fa[fset(x)] = fset(y); }
int tag[N], f[N][2], g[N][2][2];
vector<int> e2[N], l;
int dfs1(int u, int fa) {
f[u][0] = f[u][1] = 1;
for (int i = G[u], v; ~i; i = e[i].nxt) {
if (v = e[i].v, v == fa) continue;
int t = dfs1(v, u);
f[u][0] = 1LL * f[u][0] * (f[v][0] + f[v][1]) % P;
f[u][1] = 1LL * f[u][1] * f[v][0] % P;
if (t) e2[u].push_back(t);
}
if (!tag[u]) tag[u] = e2[u].size() > 1 ? u : e2[u].size() ? e2[u][0] : 0;
return tag[u];
}
void dfs2(int u, int fa) {
for (int i = G[u], v; ~i; i = e[i].nxt) {
if (v = e[i].v, v == fa) continue;
dfs2(v, u); int x = tag[v];
if (!x) continue;
g[x][0][0] = (g[x][0][0] + g[x][0][1]) % P, g[x][0][1] = (g[x][0][0] + P - g[x][0][1]) % P;
g[x][1][0] = (g[x][1][0] + g[x][1][1]) % P, g[x][1][1] = (g[x][1][0] + P - g[x][1][1]) % P;
}
g[u][0][0] = g[u][1][1] = 1;
for (int i = G[u], v; ~i; i = e[i].nxt) {
if (v = e[i].v, v == fa || tag[v]) continue;
int x = tag[u];
g[x][0][0] = 1LL * g[x][0][0] * (f[v][0] + f[v][1]) % P;
g[x][0][1] = 1LL * g[x][0][1] * f[v][0] % P;
g[x][1][0] = 1LL * g[x][1][0] * (f[v][0] + f[v][1]) % P;
g[x][1][1] = 1LL * g[x][1][1] * f[v][0] % P;
}
}
int dp[N][2], col[N];
void calc(int u) {
dp[u][0] = dp[u][1] = 1;
if (~col[u]) dp[u][col[u]^1] = 0;
for (int i = 0, v; i < e2[u].size(); i++) {
v = e2[u][i]; calc(v);
dp[u][0] = 1LL * dp[u][0] * ((1LL * dp[v][0] * g[v][0][0] + 1LL * dp[v][1] * g[v][1][0]) % P) % P;
dp[u][1] = 1LL * dp[u][1] * ((1LL * dp[v][0] * g[v][0][1] + 1LL * dp[v][1] * g[v][1][1]) % P) % P;
}
}
int main() {
scanf("%d%d", &n, &m);
memset(G, -1, sizeof G);
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d%d", &u, &v);
if (fset(u) == fset(v)) l.push_back(u), l.push_back(v), tag[u] = u, tag[v] = v;
else adde(u, v), adde(v, u), merge(u, v);
}
tag[1] = 1; dfs1(1, 0); dfs2(1, 0);
memset(col, -1, sizeof col);
for (int S = 0; S < 1<<(m-n+1)*2; S++) { // 這裏不小心寫成了2^2s的枚舉,不過人沒事
for (int i = 0; i < (m-n+1)*2; i++) col[l[i]] = -1;
int ok = 1;
for (int i = 0; i < (m-n+1)*2; i++) {
if (~col[l[i]] && col[l[i]] != (S >> i & 1)) { ok = 0; break; }
col[l[i]] = S >> i & 1;
}
for (int i = 0; i < (m-n+1)*2; i += 2)
if (col[l[i]] && col[l[i+1]]) { ok = 0; break; }
if (!ok) continue;
calc(1);
ans = (ans + 1LL * dp[1][0] * g[1][0][0] + 1LL * dp[1][1] * g[1][1][1]) % P;
}
printf("%d", ans);
return 0;
}