[考試總結]ZROI-21-NOIP衝刺-TEST17 總結
#屑在前面
由於身體原因及某幾場考試的 T3、T4 不可補,於是中間就咕咕咕了幾篇,補不補隨緣吧/wq
#T1 博弈
Time Limit: 1s | Memory Limit: 512MiB | 題目連線
#大體思路
首先顯然開環的順序由先手決定,考慮大於等於四個結的環,如果後手不選擇交換,先手一定只能拿到 4 個,如果選擇交換那麼先手一定一個都拿不到,所以顯然先手會選擇從小到大開環,然後我們 DP 一遍即可,時間複雜度為 \(O(n\log n)\).
資料範圍只有 \(100\) 就離大譜...
#Code
#define ll long long const int N = 105; const int INF = 0x3fffffff; template <typename T> inline void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } ll dp[N][2][2]; int n, a[N]; int main() { read(n); for (int i = 1; i <= n; ++ i) read(a[i]); sort(a + 1, a + 1 + n); for (int i = n; i; -- i) { if (a[i] >= 4) { if (dp[i + 1][1][0] + a[i] - 4 > dp[i][1][0]) { dp[i][0][0] = dp[i + 1][0][0] + 4; dp[i][1][0] = dp[i + 1][1][0] + a[i] - 4; } if (dp[i + 1][0][1] + a[i] - 4) { dp[i][1][1] = dp[i + 1][1][1] + 4; dp[i][0][1] = dp[i + 1][0][1] + a[i] - 4; } } if (dp[i + 1][1][1] + a[i] > dp[i][1][0]) { dp[i][0][0] = dp[i + 1][0][1]; dp[i][1][0] = dp[i + 1][1][1] + a[i]; } if (dp[i + 1][0][0] + a[i] > dp[i][0][1]) { dp[i][1][1] = dp[i + 1][1][0]; dp[i][0][1] = dp[i + 1][0][0] + a[i]; } } printf("%lld %lld", dp[1][0][0], dp[1][1][0]); return 0; }
#T2 齒輪
Time Limit: 1s | Memory Limit: 512MiB | 題目連線
#大體思路
注意到在同一連通塊中的任意兩個齒輪之間的轉速關係僅與兩者的齒數有關,中間的所有齒輪的影響都可以被抵消掉,於是我們可以將一個連通塊中的所有齒輪根據旋轉方向的不同分為兩個不交的集合(是二分圖),考慮將兩個齒輪咬合後連通塊鎖死當且僅當兩者咬合前在同一連通塊且所屬集合相同,於是我們需要維護每個齒輪所屬的連通塊及所屬的集合。
當兩個連通塊合併時,採用啟發式合併,將齒輪數小的集合併到較大的集合中,若其中一方鎖死,則整體鎖死,否則當咬合的兩個齒輪咬合前所屬的集合相同時,應當將較小的集合中的所有點所屬的集合取反,均攤後時間複雜度為 \(O(n\log n)\)
對於修改齒數及詢問直接處理即可,整體時間複雜度為 \(O((q+n)\log n)\).
#Code
#define ll long long #define int long long const int N = 100010; const int M = 200010; template <typename T> inline void read(T &x) { int f = 1; x = 0; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } struct node {int nxt, u, v;} e[M]; int head[N], cnt(1), n, m, T, a[N]; int ans, val, siz[N], f[N], bel[N]; bool locked[N]; int find(int x) {while (x != f[x]) x = f[x] = f[f[x]]; return x;} int gcd(int x, int y) {return y ? gcd(y, x % y) : x;} void add_edge(int u, int v) { e[cnt].u = u, e[cnt].v = v; e[cnt].nxt = head[u], head[u] = cnt ++; } void dfs(int u,int fa) { bel[u] ^= 1; for(int i = head[u]; i; i = e[i].nxt) if(e[i].v != fa) dfs(e[i].v, u); } signed main() { read(n), read(m); for (int i = 1; i <= n; ++ i) read(a[i]); for (int i = 1; i <= n; ++ i) f[i] = i; for (int i = 1; i <= n; ++ i) siz[i] = 1; while (m --) { int opt, x, y, c; read(opt); if (opt == 1) read(x), read(c), a[x] = c; else if (opt == 2) { read(x), read(y); if (x == y) continue; int fu = find(x), fv = find(y); if (siz[fu] > siz[fv]) swap(x, y), swap(fu, fv); if (fu == fv) { if (bel[x] == bel[y]) locked[fv] = true; } else { siz[fv] += siz[fu], f[fu] = fv; if (bel[x] == bel[y]) dfs(x, 0); add_edge(x, y), add_edge(y, x); if (locked[fu]) locked[fv] = true; } } else if (opt == 3) { read(x), read(y), read(c); int fu = find(x), fv = find(y); if (fu != fv) {puts("0"); continue;} if (locked[fu]) {puts("0"); continue;} else { int fst = c * a[x], scd = a[y]; int g = gcd(fst, scd); fst /= g, scd /= g; if (bel[x] != bel[y]) fst = -fst; printf("%lld/%lld\n", fst, scd); } } } return 0; }
#T3 排班方案
Time Limit: 2s | Memory Limit: 512MiB | 題目連線
#大體思路
簡單頹一頹柿子不難得到:
\[\prod_{i=1}^n(d_{i-1}-(i-1)+d_i-d_{i-1})=\prod_{i=1}^n(d_i-i+1)=C, \]由於限制 \(d_{i-1}\leq d_i\),於是應當有 \(d_{i-1}-(i-1)+1\leq d_i-i+1+1\),於是考慮從小到大列舉 \(C\) 的因數作為 \(d_{n}-n+1\),然後根據直接暴力 DFS,顯然剩下的越大越好,於是直接貪心構造剩下的序列即可,這樣的遞迴不會超過 \(n\) 層,於是跑滿的時間複雜度為 \(O(n\sqrt n)\),簡單剪剪枝就能過了。
#Code
const int N = 10000010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> inline T Max(T a, T b) {return a > b ? a : b;}
int t, n, c, d[N], dv[N], dcnt;
bool solve(int x, int lmt, int rst) {
if (rst == 1) {for (int i = 1; i <= x; ++ i) d[i] = 1; return true;}
if (!x) return false; int mx = 0;
if (rst <= lmt) {d[x] = rst; for (int i = 1; i < x; ++ i) d[i] = 1; return true;}
for (int i = 2; i * i <= rst && i <= lmt; ++ i) if (!(rst % i)) {
mx = Max(mx, i);
if (rst / i <= lmt) {d[x] = rst / i; return solve(x - 1, d[x] + 1, i);}
}
if (!mx) return false; else d[x] = mx;
return solve(x - 1, d[x] + 1, rst / mx);
}
void MAIN() {
read(n), read(c); dcnt = 0; bool flg = 0;
for (int i = 1; i <= n; ++ i) d[i] = 1;
for (int i = 1; i * i <= c && !flg; ++ i) if (!(c % i)) {
dv[++ dcnt] = c / i; d[n] = i;
if (solve(n - 1, d[n] + 1, c / i)) flg = 1;
}
if (!flg) for (int i = dcnt; i >= 1; -- i) {
d[n] = dv[i];
if (solve(n - 1, d[n] + 1, c / dv[i])) break;
}
for (int i = 1; i <= n; ++ i) printf("%d ", d[i] + i - 1); puts("");
}
int main() {read(t); while (t --) MAIN(); return 0;}
#T4 簡單的資料結構題
Time Limit: 3s | Memory Limit: 512MiB | 題目連線
#大體思路
考慮將詢問離線,然後維護以 \(i\) 為右端點的所有不同的區間與,同時記錄以該值為區間與的左端點的範圍(考慮區間與一定是單調不降的),顯然這個左端點範圍內的所有都可以與 \(i\) 組合,這樣的區間與最多隻會有 \(\log a_i=30\) 種,然後考慮將這個序列從以 \(i-1\) 為右端點轉移到以 \(i\) 為右端點,顯然只需要加入一個 \(a_i\),然後將所有的原本的區間與全部與上一個 \(a_i\),同時去重,這一步的複雜度為 \(O(\log n)\),然後檢查所有的區間與,如果是完全平方數,那麼就將該區間與對應的左端點區間全部加一,最後對於每個詢問檢查對應區間和即可,注意每一次修改都是在之前的基礎上進行修改。時間複雜度為 \(O(q\log n+n\log n\log a)\).
#Code
#define ll long long
#define pii pair <int, int>
#define fir first
#define sec second
#define mp(a, b) make_pair(a, b)
#define pbk(x) push_back(x)
const int N = 500010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
struct Node {int l, r, val;} e[N], ne[N];
struct Bit {
ll val[2][N]; int lmt;
inline void init(int x) {lmt = x;}
inline void reset() {while (lmt) val[0][lmt] = val[1][lmt] = 0, lmt --;}
inline void change(int x, ll y) {
for (int i = x; i <= lmt; i += (i & -i))
val[0][i] += y, val[1][i] += y * x;
}
inline ll ask(int x) {
ll res = 0;
for (int i = x; i; i -= (i & -i))
res += 1ll * (x + 1) * val[0][i] - val[1][i];
return res;
}
inline void modify(int x, int y, ll c) {change(x, c), change(y + 1, -c);}
inline ll query(int x, int y) {return ask(y) - ask(x - 1);}
} t;
int T, n, q, a[N], cnt, tot; ll ans[N];
vector <pii > sq[N];
inline void reset() {
for (int i = 1; i <= n; ++ i) sq[i].clear();
t.reset(); cnt = 0;
}
inline bool check(int x) {int dv = sqrt(x); return (dv * dv) == x;}
void MAIN() {
read(n), read(q); reset(); t.init(n);
for (int i = 1; i <= n; ++ i) read(a[i]);
for (int i = 1; i <= q; ++ i) {
int l, r; read(l), read(r);
sq[r].pbk(mp(l, i));
}
for (int i = 1; i <= n; ++ i) {
tot = 0;
e[++ cnt] = (Node){i, i, a[i]};
for (int j = 1; j <= cnt; ++ j) {
Node now = e[j]; now.val &= a[i];
if (tot && ne[tot].val == now.val)
ne[tot].r = now.r;
else ne[++ tot] = now;
}
cnt = tot;
for (int j = 1; j <= cnt; ++ j) e[j] = ne[j];
for (int j = 1; j <= cnt; ++ j)
if (check(e[j].val)) t.modify(e[j].l, e[j].r, 1);
for (auto now : sq[i]) ans[now.sec] = t.query(now.fir, i);
}
for (int i = 1; i <= q; ++ i) printf("%lld\n", ans[i]);
}
int main() {read(T); while (T --) MAIN(); return 0;}