BZOJ 杂题选记(8.31 - 9.6)

P3493 WSP-Island

调了很久,并发现了一些求半平面交的细节。
1.优雅地求两个直线的交点
考虑 \(l_1 : P_1 + k \times \vec{v_1}\)\(l_2 : P_2 + t \times \vec{v_2}\) 的交点 \(O\)。假设 \(\vec{P_2O} = s \times \vec{v_2}\)。因为 \(O\) 也在 \(l_1\) 上,所以 \(\vec{P_1O} \times \vec{v_1} = 0\),那么 \((\vec{P_1P_2} + \vec{P_2O}) \times \vec{v_1} = 0\)。拆开,带入,整理一下得 \(s = -\frac{\vec{P_1P_2} \times \vec{v_1}}{\vec{v_2} \times \vec{v_1}}\)。这个就非常好看了,然后把 \(s\) 带回去就行。
2.无穷交的情况不添加边界是没办法处理的
屏幕截图 2025-08-31 215055
这个图就是一个反例,可以自己手模一下。那为什么按单调栈求上凸包的方法可以处理这种问题呢,这主要是由于斜率和极角的性质不同导致的。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long double ldb;
const int N = 1e5 + 5;
const ldb eps = 1e-12, pi = acos(-1);
int n, m, tot;
struct vec{
	ldb x, y;
	vec operator + (vec b){ return {x + b.x, y + b.y}; }
	vec operator - (vec b){ return {x - b.x, y - b.y}; }
	vec operator * (ldb k){ return {k * x, k * y}; }
}p[N];
ldb len(vec x){ return sqrt(x.x * x.x + x.y * x.y); }
ldb cross(vec a, vec b){ return a.x * b.y - a.y * b.x; }
struct line{
	vec P, v; ldb ang;
	void assign(vec p1, vec p2){ P = p1, v = p2 - p1, ang = atan2(v.y, v.x);}
}li[N];
vec inter(line a, line b){
	ldb t = -cross(b.P - a.P, a.v) / cross(b.v, a.v);
	return b.P + b.v * t; 
}
int q[N], l = 1, r = 1;
bool right(vec a, line b){ return cross(a - b.P, b.v) >= 0; }
void solve(){
	sort(li + 1, li + 1 + tot, [](line a, line b){
		return ((fabs(a.ang - b.ang) < eps) ? (cross(b.v, a.P - b.P) >= 0) : a.ang < b.ang);
	});
	q[1] = 1;
	for(int i = 2; i <= tot; ++i){
		if(fabs(li[i].ang - li[i - 1].ang) < eps) continue;
		while(l < r && right(inter(li[q[r]], li[q[r - 1]]), li[i])) --r;
		while(l < r && right(inter(li[q[l]], li[q[l + 1]]), li[i])) ++l;
		q[++r] = i; 
	}
	while(l < r && right(inter(li[q[r]], li[q[r - 1]]), li[q[l]])) r--;
	q[++r] = q[l];
} 
vector<int> e[N];
int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n >> m;
	for(int i = 1; i <= n; ++i){
		cin >> p[i].x >> p[i].y;
	}
	for(int i = 1; i <= m; ++i){
		int u, v;
		cin >> u >> v;
		if(u < v) swap(u, v);
		e[u].push_back(v);
	}
	for(int i = 1; i <= n; ++i){
		sort(e[i].begin(), e[i].end());
		int mex = 1;
		for(int j : e[i]){
			if(mex == j) ++mex;
			else break;
		}
		if(i == n && mex == 1) return cout << len(p[n] - p[1]) << '\n', 0;
		if(mex < i) li[++tot].assign(p[i], p[mex]);
	}
	li[++tot].assign(p[1], p[n]);
	solve();
	ldb ans = 0;
	vec lst = inter(li[q[l]], li[q[l + 1]]);
	for(int i = l + 1; i <= r - 1; ++i){
		vec tmp = inter(li[q[i]], li[q[i + 1]]);
		ans += len(tmp - lst);
		lst = tmp;
	}
	ans -= len(p[n] - p[1]) - len(lst - inter(li[q[l]], li[q[l + 1]]));
	cout << fixed << setprecision(10) << ans;
	return 0;
}

P3485 BAJ-The Walk of Bytie-boy

状态还是蛮好想的,主要是转移顺序怎么办。
trick : 考虑用 bfs 钦定转移顺序。然后发现直接转移是 \(O(M ^ 2)\) 的。考虑先枚举一边,定义一下辅助数组 \(g\),这道题就可以了。复杂度是 \(O(NM + N ^ 2)\)

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 4e2 + 5;
vector<int> e[N][26], _e[N][26]; 
int f[N][N], g[N][N][26], n, m, a[N];
string s;
struct node{
    int x, y, c;
}fpre[N][N], gpre[N][N][26];
queue<node> Q, q;
void prt(int x, int y){
    if(!x || !y || x == y) return;
    int u = fpre[x][y].x, v = fpre[x][y].y, c = fpre[x][y].c;
    s.push_back((char)c + 'a');
    prt(gpre[u][v][c].x, gpre[u][v][c].y);
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> m;
    for(int i = 1; i <= m; ++i){
        int u, v; char c;
        cin >> u >> v >> c;
        int x = c - 'a';
        e[u][x].push_back(v);
        _e[v][x].push_back(u); 
    }
    memset(f, 0x3f, sizeof(f));
    memset(g, 0x3f, sizeof(g));
    for(int i = 1; i <= n; ++i){
        Q.push(node{i, i, -1});
        f[i][i] = 0;
    } 
    for(int i = 1; i <= n; ++i){
        for(int j = 0; j < 26; ++j){
            for(int v : e[i][j])
                Q.push(node{i, v, -1}), f[i][v] = 1, fpre[i][v] = node{0, 0, j};
        }
    }
    while(!Q.empty()){
        while(!Q.empty()){
            int i = Q.front().x, j = Q.front().y;
            Q.pop();
            for(int c = 0; c < 26; ++c){
                for(int v : e[j][c]){
                    if(g[i][v][c] > f[i][j] + 1){
                        g[i][v][c] = f[i][j] + 1;
                        gpre[i][v][c] = node{i, j, -1};
                        q.push(node{i, v, c}); 
                    }
                }
            }
        }
        while(!q.empty()){
            int i = q.front().x, j = q.front().y, c = q.front().c;
            q.pop();
            for(int u : _e[i][c]){
                if(f[u][j] > g[i][j][c] + 1){
                    f[u][j] = g[i][j][c] + 1;
                    fpre[u][j] = node{i, j, c};
                    Q.push(node{u, j, -1});
                }
            }
        }
    }
    int t;
    cin >> t;
    for(int i = 1; i <= t; ++i) cin >> a[i];
    for(int i = 1; i < t; ++i){
        int ans = f[a[i]][a[i + 1]];
        if(ans == f[0][0]) cout << -1 << '\n';
        else{
            s.clear();
            prt(a[i], a[i + 1]);
            string tmp = s.substr(0, ans / 2);
            reverse(tmp.begin(), tmp.end());
            cout << ans << ' ' << s + tmp << '\n';
        }
    }
    return 0;
}

P3494 KOD-The Code

洛谷的翻译就是依托,去 loj 看吧

首先考虑题意的转化,01trie 肯定要建出来。我们发现解码的过程就是沿着 trie 走,走到叶子了(叶子一定是某个编码的结尾),就把这个编码对应的字符输出出来,然后跳回根 \(T\),继续走。

又发现,这个同步编码 \(\omega\) 所满足的性质是:从根开始走,走过任意字符编码的后缀,任意若干字符和 \(\omega\) 要回到根,这样 \(\omega\) 之后的解码才不会受影响。

然后就可以分三步走,第一步先求出任意字符编码的后缀可以到达树上的点的集合 \(V\)。这一步是 trival 的,直接建 AC 自动机然后跳 fail 就可以了。这里复杂度是 \(O(N)\) 的。

第二步就是要求出从 \(V\) 中的点开始走,走过若干任意字符,能到达的点 \(V'\)。最先想到的是暴力 bfs,复杂度是 \(O(NL)\) 的。然后就是比较神的一步: 在 trie 树上走代替暴力 bfs。具体来说,假设我们现在要从 \(u\) 这个点开始扩展。为方便叙述,定义一个 \(\operatorname{move}(u, S)\) 表示从 \(u\) 开始走过 \(S\) 这个 01 串会到达哪个树上的点。bfs 要求我们每次扩展 \(u\) 时遍历所有字符集中的 \(S_i\) 并找到 \(\operatorname{move}(u, S_i)\)。考虑在 trie 上做这一步,具体来说我们在两颗 trie 上深搜,第一颗以 \(u\) 为起点,第二颗以 trie 的根为起点。每次同步移动两颗树上的点 \(\operatorname{move}(x, 0)\)\(\operatorname{move}(y, 0)\),然后继续搜索,对于 1 儿子也是同理。边界是,如果此时 \(y\) 走到了一个叶子节点,那么就将此时的 \(x\) 加入 \(V'\),并返回。注意 \(x\) 走到叶子了就把它移到根继续搜就行(这是解码的过程告诉我们的)。这样子,每次扩展就是第二颗 trie 上走一遍,因此复杂度是 \(O(N^2)\) 的。接着简单剪枝,对于从起点 \(u\) 走到某个叶子的部分就暴力做,走到叶子之后,此时 \(x\) 会跳回根,记录一下此时的 \(y\)。我们发现对于相同的 \(y\)\(\operatorname{dfs}(T,y)\) 做一遍就行了,这样的话对于不同的 \(y\)\(y\) 子树内的点都会被遍历一次,复杂度就是 \(O(\sum_y siz_y)\)。在最后我们将说明这个复杂度是正确的。

第三步就是要求满足条件的 \(\omega\),即 $ \forall u \in V', \operatorname{move}(u, \omega) = T$。还是考虑在 trie 上做这个,跟第二步中的做法非常类似,只是边界操作有所不同。第一颗 trie 以 \(u\) 为起点,第二课还是以根为起点。如果此时 \(y\) 走到了叶子节点,但是 \(x\) 不在叶子节点,说明这个 \(\operatorname{move}(u, \omega)\) 走不到根,那么就标记这个 \(y\) 对应的编码为非同步编码。对每个 \(u\) 都做一遍就行,剪枝方法也一样,复杂度还是 \(O(\sum_y siz_y)\)

最后考虑我们证明 \(\sum_y siz_y = 1 + 2 \times L\)。POI 官解是归纳证的,归纳法对我们加深理解并没有什么好处,我很菜也想不到别的办法,但还是为了完整性写在这里。证明首先需要一个观察,发现题目给的 trie 一定是严格二叉树,那么对于一颗叶子节点数量为 \(n\) 的严格二叉树,总结点数量是 \(2 \times n - 1\)。这是因为合并 \(n\) 个叶子节点需要 \(n - 1\) 次。假设 \(T\) 这颗 trie 中包含了 \(K\) 个 01 串,每个串是 \(s_i\)。对于一颗只有根的 trie,\(1 = 1 + 2 \times 0\) 满足。考虑两颗 trie \(T_1\)\(T_2\) 合并在 \(T\) 上时。假设 \(T_1\)\(T_2\) 满足归纳假设,有如下推导。
\(\begin{aligned} \sum_{y \in T} siz_y &= \sum_{u \in T_1} siz_u + \sum_{v \in T_2} siz_v + siz_{T_1} + siz_{T_2} + 1 \\ &= (1 + 2\sum_{i = 1}^{K_1} |s_i|) + (1 + 2\sum_{i = 1}^{K_2} |s_i|) + 2|K_1| - 1 + 2|K_2| - 1 + 1 \\ &= 1 + 2(\sum_{i = 1}^{K_1} |s_i| + \sum_{i = 1}^{K_2} |s_i| + |K_1| + |K_2|) \\ &= 1 + 2(\sum_{i = 1}^{K_1} (|s_i| + 1) + \sum_{i = 1}^{K_2} (|s_i| + 1)) \\ &= 1 + 2 \times \sum_{i = 1}^{K} |s_i| \end{aligned}\)

这就说明总的复杂度是 \(O(N + L)\) 的,可以通过。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1.5e6 + 5;
int ed[N], tr[N][2], fail[N], fa[N];
bitset<N> vis, ans;  
char c;
int tot, n;
bool isleaf(int u){ return ed[u] <= n && ed[u] != 0; }
queue<int> q;
void init(){
    for(int i = 0; i < 2; ++i) if(tr[0][i]) q.push(tr[0][i]);
    while(!q.empty()){
        int u = q.front(); 
        q.pop();
        for(int i = 0; i < 2; ++i){
            if(tr[u][i]) fail[tr[u][i]] = tr[fail[u]][i], q.push(tr[u][i]);
            else tr[u][i] = tr[fail[u]][i];
        }
    }
    for(int i = 0; i <= tot; ++i){
        if(isleaf(i)) tr[i][0] = tr[i][1] = 1;
    }
    for(int i = 1; i <= tot; ++i){
        if(!ed[i]) continue;
        int p = i;
        while(p != 0){
            if(!ed[p]) ed[p] = n + 1; 
            p = fail[p];    
        }
    }
}
void get(int x, int y){
    if(isleaf(x)){
        if(vis[y]) return;
        vis[y] = 1;
        get(0, y);
    }
    else{
        if(isleaf(y)){
            if(!ed[x]) ed[x] = n + 1, q.push(x);
            return;
        }
        get(tr[x][0], tr[y][0]);
        get(tr[x][1], tr[y][1]);
    }
}
void solve(){
    for(int i = 0; i <= tot; ++i){
        if(ed[i] && !isleaf(i)) q.push(i);
    }
    while(!q.empty()){
        get(q.front(), 0);
        q.pop();
    }
}
void getans(int x, int y){
    if(isleaf(y)){
        if(!isleaf(x)) ans[y] = 0;
        return;
    }
    if(isleaf(x)){
        if(vis[y]) return;
        vis[y] = 1;
        getans(0, y); 
    }
    else{
        getans(tr[x][0], tr[y][0]);
        getans(tr[x][1], tr[y][1]);
    }
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    int p = 0, cnt = 0;
    for(int i = 1; i <= n; ++i){
        cin >> c;
        if(c == 'X') ed[p] = ++cnt, ans[p] = 1;
        else if(c == 'B') p = fa[p];
        else{
            int x = c - '0';
            ++tot;
            tr[p][x] = tot;
            fa[tot] = p;
            p = tot;
        } 
    }
    init(), solve();
    vis.reset();
    for(int i = 0; i <= tot; ++i){
        if(ed[i] && !isleaf(i)) getans(i, 0); 
    }
    cout << ans.count() << '\n';
    for(int i = 0; i <= tot; ++i){
        if(ans[i]) cout << ed[i] << '\n';
    }
    return 0;
}

P3491 SLW-Words

很神的思维题,完全不会。
有一些性质:因为 \(h(k) = h(h(k - 1))\),所以 \(h(h(k_1 - 1)h(k_2 - 1)...h(k_n - 1)) = h(k_1)h(k_2)...h(k_n)\)。如果 \(h(k_1)h(k_2)...h(k_n)\) 是某个 \(h(m)\) 的子串。这意味着,\(h(h(k_1 - 1)h(k_2 - 1)...h(k_n - 1))\)\(h(h(m-1))\) 的子串。由于 \(h\) 是单射,感性理解一下,这意味着 \(h(k_1 - 1)h(k_2 - 1)...h(k_n - 1)\)\(h(m - 1)\) 的子串。反过来说,如果有 \(h(k_1 - 1)h(k_2 - 1)...h(k_n - 1)\)\(h(m - 1)\) 的子串,那 \(h(k_1)h(k_2)...h(k_n)\) 也是 \(h(m)\) 的子串。然后我们就一直对 \(k\) 序列减一,当发现一些数变成 0 时,做一些特殊的判断。具体来说,可以发现 \(h(m)\) 一定不会出现连续的两个 \(0\)\(101010\),而 \(h(5)0 = 101101010\),因此如果发现一个 \(k_i = 0\) 前面不是 \(k_{i-1} = 1\) 或者 \(3\),就 NIE。记得如果发现 \(k_1\) 变成 0 了,把它变成 2。因为在开头和末尾添加 1 或者删去 1 显然没有影响。如果最后只剩一个数了,那么原序列肯定就 TAK 了。
但我们发现这样做会有一些问题,比如 \(k_n = 3\) 时,\(101\) 不一定只由 \(10\) 变过来,也有可能是原序列中的 \(11\) 变成 \(1010\) 然后去掉 \(0\) 变过来的,\(k_n = 1\) 也类似。而且这种情况只有在 \(k_n\) 上才会发生。怎么修呢?我们可以考虑直接把 \(101\) 变成 \(10\) 再去进行减一的操作,这样就没问题了。而对于偶数和 \(k_n \ge 5\) 的奇数这种情况不会出现,理由和判断那里一样。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int k[N], n;
bool solve(){
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> k[i];
    while(n > 1){
        if(k[1] == 0) k[1] = 2;
        if(k[n] == 1) k[n] = -1;
        if(k[n] == 3) k[n] = 2;
        for(int i = 2; i <= n; ++i){
            if(k[i] == 0){
                if(k[i - 1] == 1) k[i - 1] = 2, k[i] = -1;
                else if(k[i - 1] == 3) k[i - 1] = 2, k[i] = 2;
                else return 0;
            }
        }
        int m = 0;
        for(int i = 1; i <= n; ++i) if(k[i] != -1)
            k[++m] = k[i];
        n = m;
        for(int i = 1; i <= n; ++i) k[i]--;
    }
    return 1;
}
int main(){
    cin.tie(0)->sync_with_stdio(0);
    int T; cin >> T;
    while(T--) cout << (solve() ? "TAK" : "NIE") << '\n';
    return 0;
}

P4298 祭祀

Dilworth 定理。主要内容是最小链覆盖 = 最大反链个数,注意在 Hasse 图上,最小链覆盖是可重的,传递闭包后可以变为不可重。
一个经典模型:就是如何求 DAG 上的最小链覆盖。建模方法看题解吧。
方案就按题解说的求,记录一下最大独立集的一种求法。

从网络流的角度看,最小点覆盖问题就是最小割问题:选择左部点,相当于切割它与源点的连边;选择右部点,相当于切割它与汇点的连边。
把最小点覆盖的集合 \(S\) 求出来,那么最大独立集就是 \(V - S\)。因为如果有两个最大独立集里的点 \(u\)\(v\) 连通, 那么 \((u,v)\) 这条边就没被覆盖。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e2 + 5;
#define in(i) i
#define out(i) i + n
int s, t, dep[N], head[N], tot = 1, cur[N], n, m;
bitset<N> g[N], on, vis;
struct edge{
    int v, pre, f; 
}e[N * N];
void init(){
    memset(head, 0, sizeof(head));
    tot = 1;
}
void adde(int u, int v){
    e[++tot] = {v, head[u], 1}, head[u] = tot;
    e[++tot] = {u, head[v], 0}, head[v] = tot;
}
bool bfs(){
    memset(dep, 0, sizeof(dep));
    memcpy(cur, head, sizeof(cur));
    dep[s] = 1;
    queue<int> q; q.push(s);
    while(!q.empty()){
        int u = q.front(); q.pop();
        for(int k = head[u]; k; k = e[k].pre){
            int v = e[k].v, f = e[k].f;
            if(f){
                if(dep[v]) continue;
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }   
    } 
    return dep[t];
}
int dinic(int u, int ff){
    if(u == t) return ff;
    int su = 0;
    for(int k = cur[u]; k; k = e[k].pre){
        int v = e[k].v, &f = e[k].f;
        if(dep[v] != dep[u] + 1) continue;
        if(f){
            cur[u] = k;
            int sv = dinic(v, min(ff - su, f));
            f -= sv, e[k ^ 1].f += sv;
            su += sv;
            if(su == ff) return su;
        }
    }  
    if(su == 0) dep[u] = 0;
    return su;
}
void dfs(int u){
    if(vis[u]) return;
    vis[u] = on[u] = 1;
    for(int k = head[u]; k; k = e[k].pre){
        int v = e[k].v;
        if(e[k].f) dfs(v);
    }
}
int maxflow(){
    int ret = 0;
    while(bfs()) ret += dinic(s, 1e9);
    return ret;
}

int main(){
    cin.tie(0)->sync_with_stdio(0);
    cin >> n >> m;
    for(int i = 1; i <= m; ++i){
        int u, v; 
        cin >> u >> v;
        g[u][v] = 1;
    }
    for(int k = 1; k <= n; ++k){
        for(int i = 1; i <= n; ++i){
            if(g[i][k]) g[i] |= g[k];
        }
    } 
    s = 2 * n + 1, t = 2 * n + 2;
    for(int i = 1; i <= n; ++i) adde(s, out(i)), adde(in(i), t);
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            if(g[i][j]) adde(out(i), in(j));
        }
    }
    int ans = n - maxflow();
    cout << ans << '\n';
    dfs(s);
    for(int i = 1; i <= n; ++i) on[i].flip();
    for(int i = 1; i <= n; ++i) cout << (on[in(i)] && on[out(i)]);
    cout << '\n';
    for(int x = 1; x <= n; ++x){
        init();
        int cnt = 0;
        for(int i = 1; i <= n; ++i) vis[i] = (x == i || g[x][i] || g[i][x]); 
        for(int i = 1; i <= n; ++i) if(!vis[i]) adde(s, out(i)), adde(in(i), t), ++cnt;
        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= n; ++j){
                if(vis[i] || vis[j]) continue;
                if(g[i][j]) adde(out(i), in(j));
            }
        }
        int res = cnt - maxflow();
        cout << (res == ans - 1);
    }
    return 0;
}

P4527 奥运抽奖

怎么求出 \(L\) 集合呢?trick:考虑一种指针的做法,比方说对于 \({2, 3, 5, 6}\),假设我们已经求出了 \(L\) 的一部分 \(A\)。扩展第 \(i\) 个时,把 \(p_2\) 后移到第一个 \(A_{p_2} \times 2 > A_{i-1}\) 的地方,对于其他也是同理,然后找出其中的最小的(比大小取对数就行),加入进去。
然后再考虑一下 \(f(S) = d +(1 + qd) \times f(S / d)\) 有什么性质。呃呃呃可以类似数列待定系数法推一下通项,可以算出 \(f(S) + \frac{1}{q} = (1 + qd)(f(S / d) + \frac{1}{q})\),去一下分母,得到 \(qf(S) + 1 = (1 + qd)(qf(S / d) + 1)\),也就是 \(qf(S) + 1 = \Pi_{k \in S} (1 + qk)\)。进一步发现对于 \(B \subset A,C = A - B,qf(A) + 1 = (qf(B) + 1)(qf(C) + 1) \implies f(A) = f(B) + f(C) + q \times f(B) \times f(C)\),这个性质非常重要,这意味着合并两个集合变成非常容易的事情。
接着考虑询问 \(a,b\),实际上我们发现只要以 \({2,3,5}\) 为底,\(T_{a,b} = \{x | x \in L, \nu_2(a) \le \nu_2(x) \le \nu_2(b) \land \nu_3(a) \le \nu_3(x) \le \nu_3(b) \land \nu_5(a) \le \nu_5(x) \le \nu_5(b) \}\)。然后三维线段树维护这个就行。
一个细节,如果全都以 \({2, 3, 5}\) 为底,会在题目给定底 \(\{6\}\)\(\{5,6\}\) 时炸掉。比方说 \(\{5, 6\}\) 达到了 \(422 \times 422 \times 470 = 83,699,480\),再加上线段树自带常数,试了一下不能通过。至于怎么算出来过不了的我先鸽着

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef array<int, 5> arr;
typedef long double ldb;
const int N = 1e5 + 5;
ll q, mod, pw[6][N];
struct Segment{
    int tot = 0, siz[4][2];
    #define ls(p) tr[p].ls
    #define rs(p) tr[p].rs
    #define rt(p) tr[p].rt
    struct node{
        int ls, rs, rt;
        ll val;
    }tr[N * 16];
    ll merge(ll a, ll b){ return (a + b + q * a % mod * b % mod) % mod; }
    void pushup(int p){ tr[p].val = merge(tr[ls(p)].val, tr[rs(p)].val); }
    void add(int z, ll k, int &p, int pl, int pr){
        if(!p) p = ++tot;
        if(pl == pr){ return tr[p].val = merge(tr[p].val, k), void(); }
        int mid = (pl + pr) >> 1;
        if(z <= mid) add(z, k, ls(p), pl, mid);
        else add(z, k, rs(p), mid + 1, pr);
        pushup(p);
    }
    void upd(int y, int z, ll k, int &p, int pl, int pr){
        if(!p) p = ++tot;
        add(z, k, rt(p), siz[3][0], siz[3][1]);
        if(pl == pr) return;
        int mid = (pl + pr) >> 1;
        if(y <= mid) upd(y, z, k, ls(p), pl, mid);
        else upd(y, z, k, rs(p), mid + 1, pr);
    }
    void update(int x, int y, int z, ll k, int &p, int pl, int pr){
        if(!p) p = ++tot;
        upd(y, z, k, rt(p), siz[2][0], siz[2][1]);
        if(pl == pr) return;
        int mid = (pl + pr) >> 1;
        if(x <= mid) update(x, y, z, k, ls(p), pl, mid);
        else update(x, y, z, k, rs(p), mid + 1, pr); 
    }
    ll qry(int p, int pl, int pr, int L, int R){
        if(!p) return 0;
        if(L <= pl && R >= pr) return tr[p].val;
        int mid = (pl + pr) >> 1;
        ll ret = 0;
        if(L <= mid) ret = qry(ls(p), pl, mid, L, R);
        if(R > mid) ret = merge(ret, qry(rs(p), mid + 1, pr, L, R));
        return ret;
    }
    ll ask(int p, int pl, int pr, int Ly, int Ry, int Lz, int Rz){
        if(!p) return 0;
        if(Ly <= pl && Ry >= pr) return qry(rt(p), siz[3][0], siz[3][1], Lz, Rz);
        int mid = (pl + pr) >> 1;
        ll ret = 0;
        if(Ly <= mid) ret = ask(ls(p), pl, mid, Ly, Ry, Lz, Rz);
        if(Ry > mid) ret = merge(ret, ask(rs(p), mid + 1, pr, Ly, Ry, Lz, Rz));
        return ret;
    }
    ll query(int p, int pl, int pr, int Lx, int Rx, int Ly, int Ry, int Lz, int Rz){
        if(!p) return 0;
        if(Lx <= pl && Rx >= pr) return ask(rt(p), siz[2][0], siz[2][1], Ly, Ry, Lz, Rz);
        int mid = (pl + pr) >> 1;
        ll ret = 0;
        if(Lx <= mid) ret = query(ls(p), pl, mid, Lx, Rx, Ly, Ry, Lz, Rz);
        if(Rx > mid) ret = merge(ret, query(rs(p), mid + 1, pr, Lx, Rx, Ly, Ry, Lz, Rz));
        return ret;
    }
}T;
int bse[4], tot, cnt, v[10], buc[7][4], tp[7], s[N], Rt;
ldb Log[10];
arr a[N];
bool ge(arr x, arr y){
    ldb ret = 0;
    for(int i = 1; i <= tot; ++i){
        ret += (x[i] - y[i]) * Log[bse[i]];
    }
    return ret >= 0;
}
bool fl[7];
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    for(int i = 2; i <= 6; ++i) {
        cin >> fl[i];
        Log[i] = log(i);
        if(fl[i]){
            v[++cnt] = i;
        }
    }
    if(cnt == 1 && fl[6]){
        tot = 1, bse[1] = 6;
    }
    else if(cnt == 2 && fl[5] && fl[6]){
        tot = 2, bse[1] = 5, bse[2] = 6;
    }
    else{
        if(fl[2] || fl[4] || fl[6]) bse[++tot] = 2;
        if(fl[3] || fl[6]) bse[++tot] = 3;
        if(fl[5]) bse[++tot] = 5;
    }
    for(int i = 1; i <= cnt; ++i){
        for(int j = 1; j <= tot; ++j){
            while(v[i] % bse[j] == 0)
                buc[i][j]++, v[i] /= bse[j];
        }
    }
    for(int j = 1; j <= 3; ++j) pw[j][0] = 1;
    cin >> mod >> q;
    for(int i = 1; i <= 100000; ++i){
        arr mn = arr{100000, 100000, 100000}, now;
        for(int j = 1; j <= tot; ++j) (pw[j][i] = pw[j][i - 1] * bse[j]) %= mod;
        auto move = [&](int i){
            for(int j = 1; j <= tot; ++j) now[j] += buc[i][j];
        };
        for(int j = 1; j <= cnt; ++j){
            now = a[tp[j]];
            move(j);
            while(ge(a[i - 1], now)){
                tp[j]++;
                now = a[tp[j]];
                move(j);
            }
            mn = (ge(mn, now) ? now : mn);
        }
        a[i] = mn;
        for(int j = 1; j <= tot; ++j){
            if(T.siz[j][1] < a[i][j]) T.siz[j][1] = a[i][j];
        }
    }   
    for(int i = 1; i <= 100000; ++i) {
        ll k = pw[1][a[i][1]] * pw[2][a[i][2]] % mod * pw[3][a[i][3]] % mod;
        T.update(a[i][1], a[i][2], a[i][3], k, Rt, T.siz[1][0], T.siz[1][1]);
    }
         
    int Q; cin >> Q;
    while(Q--){
        int x, y; 
        cin >> x >> y;
        int Lx = a[x][1], Rx = a[y][1], Ly = a[x][2], Ry = a[y][2], Lz = a[x][3], Rz = a[y][3], l = T.siz[1][0], r = T.siz[1][1];
        if(Lx > Rx || Ly > Ry || Lz > Rz) cout << 0 << '\n';
        else cout << T.query(Rt, l, r, Lx, Rx, Ly, Ry, Lz, Rz) << '\n';
    } 
    return 0;
}
posted @ 2025-08-31 21:56  Hengsber  阅读(19)  评论(0)    收藏  举报