Say 题选记(9.21 - 9.27)

P2048 [NOI2010] 超级钢琴

如何求长度在 \([L,R]\) 的子串中,子串和前 \(k\) 大的那些。
首先显然可以转化为前缀和。考虑 \(k = 1\) 的情况,把以 \(i(1 \le i \le n)\) 为右端点,\(j \in [i - R + 1, i - L + 1]\) 为左端点中最大的字串再求一遍最大值即可。这个用 st 表可以做。然后 \(k > 1\) 的思路类似这题。都是开一个优先队列,每次取最大的,然后进行分裂,把新的可能的最大的推进去,进行 \(k\) 遍即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef tuple<int, int, int, int, int> tpi;
const int N = 500005;
int a[N], n, sum[N], st[N][20], k, L, R;
int get(int x, int y){
    return sum[x] < sum[y] ? x : y;
}
int query(int l, int r){
    if(l > r) return 1e9;
    int d = __lg(r - l + 1);
    return get(st[l][d], st[r - (1 << d) + 1][d]);
}
priority_queue<tpi> q;
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> k >> L >> R;
    for(int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i], st[i][0] = i;
    for(int j = 1; (1 << j) <= n; ++j){
        for(int i = 0; i + (1 << j) - 1 <= n; ++i){
            st[i][j] = get(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
        }
    }
    for(int i = L; i <= n; ++i){
        int l = i - R, r = i - L;
        l = max(l, 0);
        int pos = query(l, r);
        q.emplace(sum[i] - sum[pos], i, pos, l, r);
    }
    ll ans = 0;
    while(k){
        auto [val, i, t, _l, _r] = q.top();
        q.pop();
        ans += val;
        if(_l < t){
            int pos = query(_l, t - 1);
            q.emplace(sum[i] - sum[pos], i, pos, _l, t - 1);
        }
        if(t < _r){
            int pos = query(t + 1, _r);
            q.emplace(sum[i] - sum[pos], i, pos, t + 1, _r);
        }
        --k;
    }
    cout << ans;
    return 0;
}

P4436 [HNOI/AHOI2018] 游戏

首先可以先把没有锁的房间并在一起看。
部分分给了一些提示,当 \(y \le x\) 恒成立时,只有可能是左边房门开右边房门,并且如果左边的房间能开锁到底右边的房间,那么右边房间能到的左边房间也能到。因此考虑记忆化。左边更新时直接从右边能到的最远的看能不能打开门锁,复杂度就是线性了。
\(y > x\) 时也是同理,也可以记忆化。难处貌似是既有 \(y \le x\) 也有 \(y > x\) 时转移顺序不知道怎么搞。如果 \(y \le x\) 就连一条 \(x + 1 \to x\) 的边,表示得先更新 \(x + 1\) 再更新 \(x\),反过来同理,那么就可以用拓扑排序钦定转移顺序。
注意细节,如果左右都可以暴力扩展时,要同时向两边扩,因为有可能右边的钥匙在向左还没扩到的地方。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int p, n, f[N], m, e[N][2], l[N], r[N], g[N][2], odeg[N], ideg[N];
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> m >> p;
    for(int i = 1; i <= n; ++i) l[i] = r[i] = f[i] = i;
    for(int i = 1; i <= m; ++i){
        int u, t;
        cin >> u >> t;
        e[u][1] = t, e[u + 1][0] = t;
    }
    for(int i = 1; i <= n - 1; ++i) if(!e[i][1]) f[i + 1] = l[i + 1] = l[i];
    for(int i = n; i >= 2; --i) if(!e[i][0]) r[i - 1] = r[i];
    for(int i = 1; i <= n; ++i){
        // cout << f[i] << ' ' << l[i] << ' ' << r[i] << '\n';
        if(!e[i][1]) continue;
        if(e[i][1] <= i) g[f[i + 1]][odeg[f[i + 1]]++] = f[i], ++ideg[f[i]];
        else g[f[i]][odeg[f[i]]++] = f[i + 1], ++ideg[f[i + 1]];
    }
    queue<int> q;
    for(int i = 1; i <= n; ++i){
        if(!ideg[i] && f[i] == i) q.push(i);
    }
    while(!q.empty()){
        int u = q.front(); q.pop();
        // cout << u << ":\n";
        int x = u, y = u;
        while(1){
            bool fl = 0;
            if(x < n && e[r[f[x]]][1] >= l[u] && e[r[f[x]]][1] <= r[u]) x = r[f[x]] + 1, r[u] = r[f[x]], fl = 1;
            if(y > 1 && e[l[f[y]]][0] <= r[u] && e[l[f[y]]][0] >= l[u]) y = l[f[y]] - 1, l[u] = l[f[y]], fl = 1; 
            if(!fl) break;
        }
        for(int k = 0; k < odeg[u]; ++k){
            // cout << g[u][k] << '\n';
            --ideg[g[u][k]];
            if(!ideg[g[u][k]]) q.push(g[u][k]);
        }
    }
    while(p--){
        int x, y;
        cin >> x >> y;
        cout << (l[f[x]] <= y && r[f[x]] >= y ? "YES" : "NO") << '\n';
    }
    return 0;
}

P4517 [JSOI2018] 防御网络

给定一颗点仙人掌,求点集的所有子集的最小斯坦纳树权值之和。

点仙人掌的性质类似于基环树。还是考虑边的贡献。如果 \((u,v)\) 是一条树边,那么贡献是 \((2^{siz_u} - 1)(2^{siz_v} - 1)\)。环上的情况相对复杂。记 \(L\) 为环上被选择的点相邻的最大值(包括最后一个和第一个的距离),那么贡献为环长 \(c\) 减去 \(L\)。那么就可以考虑枚举 \(L\),和断环为链的地方,做一遍 dp,统计方案。限制就是相邻点距离。前缀和优化,可以做到 \(O(N^3)\)

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e2 + 5, mod = 1e9 + 7;
vector<int> e[N], vcc[N];
int n, m, cnt, dfn[N], low[N], tsp, st[N], tp;
bitset<N> on;
ll pw[N], val[N], f[N][2], ans, sumf[N][2];
ll qpow(ll a, ll b){
    ll res = 1;
    for(;b; b >>= 1, (a *= a) %= mod){
        if(b & 1) (res *= a) %= mod;
    }
    return res;
}
void tarjan(int u){
    dfn[u] = low[u] = ++tsp;
    st[++tp] = u;
    for(int v : e[u]){
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if(low[v] == dfn[u]){
                ++cnt;
                // cout << v << ' ' << u << ' ' << cnt << '\n';
                while(1){
                    vcc[cnt].emplace_back(st[tp]);
                    if(st[tp] == v) break;
                    --tp;
                }
                --tp, vcc[cnt].emplace_back(u);
            }
        }
        else low[u] = min(dfn[v], low[u]);
    }
}
int getsz(int u){
    int sz = 1;
    on[u] = 1;
    for(int v : e[u]){
        if(!on[v]) sz += getsz(v); 
    }
    return sz;
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> m;
    pw[0] = 1;
    for(int i = 1; i <= n; ++i) (pw[i] = pw[i - 1] * 2) %= mod;
    for(int i = 1; i <= m; ++i){
        int u, v;
        cin >> u >> v;
        e[u].emplace_back(v);
        e[v].emplace_back(u);
    }
    tarjan(1);
    for(int i = 1; i <= cnt; ++i){
        on.reset();
        for(int u : vcc[i]) on.set(u, 1);
        // cout << '\n';
        int c = vcc[i].size();
        for(int j = 0; j < c; ++j){
            int sz = getsz(vcc[i][j]);
            // cout << sz << ' ';
            val[j + 1] = pw[sz] - 1;
        }
        // cout << '\n';
        if(c == 2){ (ans += val[1] * val[2]) %= mod; continue; }
        for(int L = 1; L < c; ++L){
            for(int u = 1; u <= c; ++u){
                sumf[u - 1][0] = sumf[u - 1][1] = 0;
                sumf[u][0] = f[u][0] = val[u];
                sumf[u][1] = f[u][1] = 0;
                // cout << u << '\n';
                for(int v = u + 1; v <= c; ++v){
                    int l = max(v - L, u - 1), dist;
                    f[v][0] = (val[v] * (sumf[v - 1][0] - sumf[l][0])) % mod;
                    l = max(v - L - 1, u - 1);
                    f[v][1] = 0;
                    if(v - L >= u) (f[v][1] = val[v] * (f[v - L][0] + sumf[v - 1][1] - sumf[l][1])) %= mod;
                    if((dist = c - v + u) <= L){
                        (ans += (c - L) * f[v][1]) %= mod;
                        if(dist == L) (ans += (c - L) * f[v][0]) %= mod;
                    }
                    // cout << v << ' ' << f[v][0] << ' ' << f[v][1] << '\n';
                    (sumf[v][0] = sumf[v - 1][0] + f[v][0]) %= mod;
                    (sumf[v][1] = sumf[v - 1][1] + f[v][1]) %= mod;
                }
            }
        }
    }
    // cout << ans << '\n';
    cout << (ans * qpow(pw[n], mod - 2)) % mod;
    return 0;
}

P8100 [USACO22JAN] Counting Haybales P

考虑什么情况下 \(i\) 必须放在 \(j\) 的前面。由于只有 \(|h_i - h_{i + 1}| = 1\) 时才能交换两个数,并且认为相同的数之间不能交换。那么就可以考虑对于 \(i < j \land |h_i - h_j| \ne 1\) 都连一条 \((i,j)\) 的边,表示 \(i\) 必须放在 \(j\) 的前面。对这个 DAG 进行拓扑序计数,就是答案。
但是一般 DAG 是做不了的,发现奇偶的性质,即 \(h_i\) 为奇的和 \(h_i\) 为偶的实际上已经按原序列的顺序排好了。那么只用考虑两条链之间的拓扑限制。考虑 dp,\(dp_{i,j}\) 为第一条链前 \(i\) 个,第二条链前 \(j\) 个的方案数。如果发现 \(j + 1\) 的所有入边都在 \(\le i\) 的范围内了,那么就可以转移到 \(dp_{i, j + 1}\),表示这一位放 \(j + 1\)\(i + 1\) 同理。
这种考虑限制的想法可以稍微注意。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e3 + 5, mod = 1e9 + 7;
int n, a[N], dp[N][N], pos[2][N], pre[N], cnt[2];
void solve(){
    cin >> n;
    cnt[0] = cnt[1] = 0;
    for(int i = 1; i <= n; ++i){
        cin >> a[i];
        int x = a[i] & 1;
        pos[x][++cnt[x]] = i;
        pre[i] = 0;
        for(int j = i - 1; j >= 1; --j){
            if(x != (a[j] & 1) && abs(a[i] - a[j]) != 1){
                pre[i] = j;
                break;
            }
        }
    }
    dp[0][0] = 1;
    for(int i = 0; i <= cnt[0]; ++i){
        for(int j = 0; j <= cnt[1]; ++j){
            if(i == 0 && j == 0) continue;
            dp[i][j] = 0;
            if(i && pre[pos[0][i]] <= pos[1][j]) (dp[i][j] += dp[i - 1][j]) %= mod;
            if(j && pre[pos[1][j]] <= pos[0][i]) (dp[i][j] += dp[i][j - 1]) %= mod; 
        }
    }
    cout << dp[cnt[0]][cnt[1]] << '\n';
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while(T--) solve();
    return 0;
}

P4182 [USACO18JAN] Lifeguards P

有互相包含关系的线段中,肯定只选最外面那个。这样的话,剩下的线段就一定互不包含。按左端点排序后,右端点也会保持有序。
考虑 dp。\(f_{i, j}\) 表示考虑排序后前 \(i\) 条线段(第 \(i\) 条必须选),已经丢到了 \(j\) 条的最大覆盖长度。那么 \(f_{i, j} = \max_{i' = i - j - 1}^{i - 1}(f_{i', j - (i - i' - 1)} - \max(r_{i'}, l_i) + r_i )\),直接转移是 \(O(NK^2)\)
后面先考虑分讨把 \(\max\) 去了,整理一下,\(f_{i, j} = \max_{i'} (f_{i', i' + (j - i - 1)} - r_{i'})\)。这说明,转移只会在 \(f_{x,y}(y - x - 1 = C)\) 之间进行。考虑枚举 \(C\)\(j\),又因为我们分讨时是有单调性的,可以单调队列优化,按 \(i\) 从小到大把 \(f_{i,j} - r_i\) 推进去,如果发现对头的 \(r_x \le l_i\) 就弹出他,并更新前缀最大值 \(\max{f_{x,y}}\)(别忘了前面的分讨),由于 \(r_i\) 单调递增,所以前面 \(f\) 大的一定不会从队尾弹出,只需要在队头弹出时更新即可。边界是 \(f_{i,i} = 0\)
这样按 \(C\) 划分阶段,就可以 \(O(1)\) 转移。对于这种下标有偏移量的可以考虑这么处理。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, K = 1e2 + 5;
struct Seg{
    int pl, pr;
}s[N];
int L[N], R[N], n, _n, k, l, r, q[N], f[N][K], ans;
void chmax(int &x, int y){ x = max(x, y); }
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> _n >> k;
    for(int i = 1; i <= _n; ++i) cin >> s[i].pl >> s[i].pr;
    sort(s + 1, s + 1 + _n, [](Seg x, Seg y){
        return (x.pl == y.pl ? x.pr > y.pr : x.pl < y.pl); 
    });
    for(int i = 1; i <= _n; ++i){
        if(s[i].pr <= R[n]){
            --k;
            continue;
        }
        L[++n] = s[i].pl, R[n] = s[i].pr;
    }
    // for(int i = 1; i <= n; ++i)
    //     cout << L[i] << ' ' << R[i] << '\n';
    if(k <= 0){
        int ans = 0;
        for(int i = 1; i <= n; ++i) ans += R[i] - max(L[i], R[i - 1]);
        return cout << ans, 0;
    }
    for(int d = 1; d <= n; ++d){
        l = 1, r = 0;
        int x = -d + 1, pre = 0;
        for(int j = 0; j <= k; ++j){
            int i = j + d;
            if(i > n) break;
            while(l <= r && f[i - 1][j] - R[i - 1] >= f[q[r]][q[r] + x] - R[q[r]]) --r;
            q[++r] = i - 1;
            while(l <= r && R[q[l]] <= L[i]) chmax(pre, f[q[l]][q[l] + x]), ++l;
            f[i][j] = pre + R[i] - L[i];
            if(l <= r) chmax(f[i][j], f[q[l]][q[l] + x] + R[i] - R[q[l]]);
            if(j + (n - i) == k) chmax(ans, f[i][j]);
        }
    }
    cout << ans;
    return 0;
}

P5156 [USACO18DEC] Sort It Out P

难点在于题意的转化吧。要看出一个充要的条件。
题目原来说的操作很玄乎,但我们发现两条性质:

1.如果去掉所选集合之后原序列无序,那么做完操作之后肯定无序。因为只对集合内做交换操作不会影响剩下的元素的相对位置
2.如果去掉所选集合之后原序列有序,那么做完操作之后肯定有序。这个可以分讨加归纳地证明。

那么第一问的答案就是 \(n - len(LIS)\)。对于第二问的话,题目要我们求字典序第 \(k\) 小的,那么就等价于第 \(k\) 大的 \(LIS\) 的补集(这也是不难证明的)。这只需要把以 \(i\) 开头的 \(LIS\) 长度 \(f_i\) 和方案数 \(g_i\) 求出来就行。注意会爆 long long,给所有运算封上上限 \(10^{18}\) 即可。

Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll inf = 2e18;
const int N = 1e5 + 5;
int n, a[N], pos[N], f[N];
vector<int> e[N];
ll k, g[N];
void chmax(int &x, int y){ x = max(x, y); }
void add(ll &x, ll y){ x += y; if(x >= inf) x = inf; }
struct MAXBIT{
    int tr[N];
    #define lowbit(x) x & (-x)
    void upd(int x, int k){
        for(int i = x; i; i -= lowbit(i)) chmax(tr[i], k); 
    }
    int qry(int x){
        int ret = 0;
        for(int i = x; i <= n; i += lowbit(i)) chmax(ret, tr[i]);
        return ret;
    }
}tr;
struct SUMBIT{
    ll tr[N];
    vector<int> tmp;
    #define lowbit(x) x & (-x)
    void upd(int x, ll k){
        tmp.push_back(x);
        for(int i = x; i; i -= lowbit(i)) add(tr[i], k); 
    }
    ll qry(int x){
        ll ret = 0;
        for(int i = x; i <= n; i += lowbit(i)) add(ret, tr[i]);
        return ret;
    }
    void clear(){
        for(int pos : tmp){
            for(int i = pos; i; i -= lowbit(i)) tr[i] = 0;
        }
        tmp.clear();
    }
}sum;
bitset<N> on;
void dfs(int l, int x, ll k){
    if(l == 1) return;
    for(int t : e[l - 1]){
        if(pos[t] > pos[x]){
            if(k <= g[t]){
                on[t] = 1;
                dfs(l - 1, t, k);
                return;
            }
            k -= g[t];
        }
    }
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> k;
    for(int i = 1; i <= n; ++i) cin >> a[i], pos[a[i]] = i;
    int maxl = 0;
    for(int i = n; i >= 1; --i){
        f[i] = tr.qry(a[i]) + 1;
        e[f[i]].emplace_back(a[i]);
        chmax(maxl, f[i]);
        tr.upd(a[i], f[i]);
    }
    for(int i = 1; i <= maxl; ++i) sort(e[i].begin(), e[i].end(), greater<int>());
    for(int i : e[1]) g[i] = 1;
    for(int j = 2; j <= maxl; ++j){
        int it = 0;
        for(int i : e[j]){
            int x;
            while(it < e[j - 1].size() && (x = e[j - 1][it]) > i) sum.upd(pos[x], g[x]), ++it;
            g[i] = sum.qry(pos[i]);
        }
        sum.clear();
    }
    dfs(maxl + 1, 0, k);
    cout << n - maxl << '\n';
    for(int i = 1; i <= n; ++i){
        if(!on[i]) cout << i << '\n';
    }
    return 0;
}

P11845 [USACO25FEB] Min Max Subarrays P

考虑单次询问,对区间长度进行分类讨论。当区间长度为偶数时,最后一次操作为取 \(\max\),想了一下,感觉可以取到区间最大值。实际上,在 \(N \ge 5\) 的情况下确实是没有问题的(对 \(N = 5\) 时手模,发现最大值在哪个位置的时候都可以取到,那么更大的也就没问题了),只有在 \(N = 3\) 时,最大值如果在最中间,那么第一次就不得不把他消掉。当区间长度为奇数时,最后一次操作为取 \(\min\),貌似可以取到区间次大值?类似地,当 \(N \ge 8\) 时确实可以取到(讨论一下分成 5/3 和 4/4 的情况),大于 \(8\) 时就更加可以了。
那么只需要对一些 corner-cases 跑一个 \(2^len\) 的状压,看看最后能剩那些数。对于大的哪些,实际上要求的是长度为奇数的区间的区间最大值和长度为偶数的区间的区间次大值之和。
区间最大值之和是很经典的,如果在加上奇数的限制,那就讨论一下左边取的是奇数位置还是偶数位置,对应乘上右边的奇数位置和偶数位置。
次大值类似最大值的计算,都是考虑贡献。BenQ 先桶排之后,然后维护了一个链表,从小到大删数,这种写法同时免去了很多细节,可以记一下。

Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
vector<int> buc[N];
bool f[64];
int n, a[N], l[N], r[N], st[N], tp;
int count(int op, int pl, int pr){
    if((pl & 1) != op) ++pl;
    if((pr & 1) != op) --pr;
    if(pl > pr) return 0;
    return (pr - pl) / 2 + 1;
}
int getodd(int i){
    int ret = 0;
    for(int p : {0, 1}) ret += count(p, l[i] + 1, i) * count(p, i, r[i] - 1);
    return ret;
}
int geteven(int i){
    auto f = [](int x, int y){
        int ret = 0;
        for(int p : {0, 1}) ret += count(p, l[x] + 1, x) * count(p ^ 1, y, r[y] - 1);
        return ret;
    };
    return f(l[i], i) + f(i, r[i]);
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    for(int i = 1; i <= n; ++i){
        cin >> a[i];
        buc[a[i]].emplace_back(i);
    }
    l[0] = 0, r[n + 1] = n + 1;
    st[++tp] = 0;
    for(int i = 1; i <= n; ++i) l[i] = i - 1, r[i] = i + 1; 
    auto del = [](int x){
        r[l[x]] = r[x];
        l[r[x]] = l[x];
    };
    int ans = 0;
    for(int v = 1; v <= n; ++v){
        for(int i : buc[v]){
            int x = getodd(i), y = geteven(i);
            ans += (x + y) * a[i];
            del(i);
        }
    }
    for(int i = 1; i <= n; ++i){
        int mx = 0, se = 0;
        for(int j = i; j <= i + 5; ++j){
            if(j > n) break;
            if(a[j] > mx) se = mx, mx = a[j];
            else if(a[j] > se) se = a[j];
            int len = j - i + 1;
            if(len == 3 || len == 4 || len == 6){
                if(len & 1) ans -= mx;
                else ans -= se;
            }
        }   
    }
    for(int len : {3, 4, 6}){
        for(int i = 1; i + len - 1 <= n; ++i){
            memset(f, 0, sizeof(f));
            f[(1 << len) - 1] = 1;
            int tmp = 0;
            for(int j = (1 << len) - 1; j; --j){
                int lst = -1, cnt = __builtin_popcount(j);
                int op = (len - cnt) & 1;
                auto get= [&](int x, int y){
                    return (!op ? (a[x + i] > a[y + i] ? x : y) : (a[x + i] > a[y + i] ? y : x));
                };
                if(!f[j]) continue;
                for(int k = 0; k < len; ++k){
                    if(j & (1 << k)){
                        if(cnt == 1) tmp = max(tmp, a[i + k]);
                        if(lst != -1) f[j ^ (1 << get(lst, k))] |= f[j];
                        lst = k;
                    }
                }
            }
            ans += tmp;
        }
    }
    cout << ans;
    return 0;
}

P4620 [SDOI2018] 荣誉称号

下面的 \(k\) 都加了 1。
二叉树什么的都挺容易发现的,而且由于相隔 \(k + 1\) 层的同余性相同,最后发现只用对前 \(k\) 层考虑 dp。
注意一些细节,首先当 \(n\) 很小时,可能只有一小部分链有整除 \(m\) 的性质(因为其他链长度根本就不够 \(k\)),解决方法就是开一些空点,补满 \(2 ^ k\),起到一个调整的作用。
第二就是预处理代价时,便利每个点是 \(O(nm)\) 的,过不了。实际上可以记录 0 时的代价,然后记录增量,并且记录在 \(a_i\) 处要减 \(m\) 个代价(多转了一圈),这样就变成了 \(O(n + 2 ^ k m)\)

Code
#include <bits/stdc++.h>
using namespace std;
unsigned int SA, SB, SC;
int p, A, B;
unsigned int rng61(){
	SA ^= SA << 16;
	SA ^= SA >> 5;
	SA ^= SA << 1;
	unsigned int t = SA;
	SA = SB;
	SB = SC;
	SC ^= t ^ SA;
	return SC;
}
#define Tp template<typename T>
#define Ts template<typename T,typename... _T>
char buf[1<<20],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=buf+fread(p1=buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
Tp inline void read(T& x){
    x=0;char c=getchar();bool f=0;
    for(;!isdigit(c);c=getchar())if(c=='-')f=1;
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    f&&(x=-x);
}
#define int long long
const int N = 1e7 + 5, K = 15, M = 205, inf = 1e18;
int n, k, m, a[N], b[N], cost[1 << K][M], dp[1 << K][M], delta[1 << K];
inline void gen(){
	read(n), read(k), read(m), read(p), read(SA), read(SB), read(SC), read(A), read(B);
    ++k;
	for(int i = 1; i <= p; i++) read(a[i]), read(b[i]);
	for(int i = p + 1; i <= n; i++){
		a[i] = rng61() % A + 1;
		b[i] = rng61() % B + 1;
	}
}
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
inline void chmin(int &x, int y){ (x > y ? (x = y) : 0); }
int lim;
inline void solve(){
    memset(cost, 0, sizeof(cost));
    memset(dp, 0x3f, sizeof(dp));
    memset(delta, 0, sizeof(delta));
    gen();
    lim = (1 << k) - 1ll;
    if(n <= lim){
        for(int i = n + 1; i <= lim; ++i) a[i] = b[i] = 0;
        n = lim;
    }
    for(int i = 1; i <= n; ++i){
        a[i] %= m;
        int dep = __lg(i);
        int idx = (i >> (dep - dep % k));
        cost[idx][0] += b[i] * (m - a[i]);
        cost[idx][a[i]] -= b[i] * m;
        delta[idx] += b[i];
    }
    for(int i = 1; i <= lim; ++i){
        for(int j = 1; j < m; ++j) cost[i][j] += cost[i][j - 1] + delta[i];
    }
    for(int u = lim; u >= 1; --u){
        if(ls(u) <= lim && rs(u) <= lim){  
            for(int i = 0; i < m; ++i){
                for(int j = 0; j < m; ++j){
                    chmin(dp[u][(i + j) % m], cost[u][i] + dp[ls(u)][j] + dp[rs(u)][j]); 
                }
            }
        }
        else{
            for(int i = 0; i < m; ++i) dp[u][i] = cost[u][i];
        }
    }
    printf("%lld\n", dp[1][0]);
}
signed main(){
    int T;
    read(T);
    while(T--) solve();
    return 0;
}
posted @ 2025-09-25 22:25  Hengsber  阅读(3)  评论(0)    收藏  举报