一类子区间gcd,按位与,按位或的问题

TT 组询问,每次给出 nn 个数 aia_i。[P7009 CERC2013] Magical GCD - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

你需要找到这个数组的一个子序列(要求编号连续),使得该序列中所有数的最大公约数和序列长度的乘积最大,并输出这个最大值。

Sol:重要性质如果右端点不同,那么gcd意义下本质不同的左端点只有log个https://www.luogu.com.cn/problem/P5502

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
#define LL long long
queue<int>q,r;
int n;LL a[N],res;
LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);res=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);LL last=0;res=max(res,a[i]);
while(!q.empty())
{
int x=q.front();q.pop();a[x]=gcd(a[x],a[i]);//x就是最小的满足区间[x,i]的gcd为a[x]的左端点
res=max(res,a[x]*(i-x+1));
if(a[x]==a[last]) continue;//一些左端点就可以合并掉
r.push(x);last=x;
}
while(!r.empty())
{
q.push(r.front());
r.pop();
}
if(a[last]!=a[i]) q.push(i);//i也可以作为一个新的左端点
}
printf("%lld\n",res);
while(!q.empty()) q.pop();
}
return 0;
}

Sol2:解法2:固定左端点,用于倍增优化右端点的确定(其实也可以二分)。但是在倍增的过程中,需要多次查询区间最大公约数;我们可以用ST表的思路对序列预处理,做到 O*(1) 复杂度查询。

  • 稍加观察,可以发现当左端点确定时,右端点右移一个单位,原子段的最大公约数要么维持不变;要么变为原最大公约数的一个约数。换言之,当左端点确定时,不同的右端点最多对应 log⁡2𝑛log2n 个不同的最大公约数值。算法复杂度得以控制在 $𝑂(𝑛(log⁡𝑛)^2) $。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}

int T, N;
ll ans;
ll a[100005];
ll st[100005][18];
int lg[100005];

inline ll query(int l, int r) {
int k = lg[r - l + 1];
return gcd(st[l][k], st[r - (1 << k) + 1][k]);
}

int main() {
lg[1] = 0;
for (int i = 2; i <= 100000; ++i)
lg[i] = lg[i >> 1] + 1;
scanf("%d", &N);
for ( int i = 1; i <= N; ++i)
scanf("%lld", &st[i][0]);
for ( int j = 1; j <= lg[N]; ++j)
for ( int i = 1; i + (1 << j) - 1 <= N; ++i)
st[i][j] = gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
for ( int l = 1; l <= N; ++l) {
int r = l;
while (r <= N) {
ll cur = query(l, r);
for ( int i = lg[N]; i >= 0; --i) {
if (r + (1 << i) <= N && query(l, r + (1 << i)) == cur)
r += (1 << i);
}
ans = max(ans, cur * (r - l + 1));
r += 1;
}
}
printf("%lld", ans);
return 0;
}

二分写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <bits/stdc++.h>
using namespace std;
int n, T;
long long gcd[100001][21], maxx;
long long gcdqj(int l, int r)
{
int p = log2(r - l + 1);
return __gcd(gcd[l][p], gcd[r - (1 << p) + 1][p]);
}
int search(int i, int l, int r, long long value)
{
while (r - l > 1)
{
int mid = (l + r) / 2;
if (gcdqj(i, mid) == value)
l = mid;
else
r = mid - 1;
}
if (gcdqj(i, r) == value)
return r;
return l;
}
int main()
{
cin >> T;
while (T--)
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> gcd[i][0];
for (int i = 1; i <= 20; i++)
for (int j = 1; j + (1 << i) - 1 <= n; j++)
gcd[j][i] = __gcd(gcd[j][i - 1], gcd[j + (1 << i - 1)][i - 1]);
maxx = 0;
for (int i = 1; i <= n; i++)
{
for (int l = i, r = 0; l <= n; l = r + 1)
{
r = search(i, l, n, gcdqj(i, l));
maxx = max(maxx, (r - i + 1) * gcdqj(i, l));
}
}
cout << maxx << endl;
}
}
https://ac.nowcoder.com/acm/contest/76681/K

求所有数组子区间的区间gcd之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}

ll T, N;

ll a[200005];
ll st[200005][20];
int lg[200005];
//const int mod = 998244353;
inline ll query(int l, int r) {
int k = lg[r - l + 1];
return gcd(st[l][k], st[r - (1 << k) + 1][k]);
}
//int ans[200005];
//int cnt[200005];

Z ans=0;
int main() {
lg[1] = 0;
for ( int i = 2; i <= 200000; ++i)
lg[i] = lg[i >> 1] + 1;
scanf("%lld", &N);
for ( int i = 1; i <= N; ++i)
scanf("%lld", &st[i][0]);
for (int j = 1; j <= lg[N]; ++j)
for (int i = 1; i + (1 << j) - 1 <= N; ++i)
st[i][j] = gcd(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
for ( int l = 1; l <= N; ++l) {
int r = l;
int last=l;
while (r <= N) {
ll cur = query(l, r);
for ( int i = lg[N]; i >= 0; --i) {
if (r + (1 << i) <= N && query(l, r + (1 << i)) == cur)
r += (1 << i);
}
Z len=r-last+1;
ans+=len*(last-l+1+r-l+1)*cur/2;
last=r+1;

r += 1;
}

}

Z tmp=(N*(N+1)/2);
Z res=tmp.inv()*ans;
cout<<res<<endl;
return 0;
}

题意:另一个重要性质

给出一个长度为n的序列和q次询问,每次询问把序列中的每个元素加上x,输出整个序列的gcd。

思路:
我们都知道gcd(a,b)=gcd(a,b-a)
对于多个数组而言,gcd(a,b,c,d)=gcd(a,b-a,c-b,d-c)
所以gcd(a+x,b+x,c+x,d+x)=gcd(a+x,b-a,c-b,d-c)
这就是gcd的更相减损性。
————————————————

链接:https://ac.nowcoder.com/acm/contest/96/I

给一个数列共n(n<=100,000)个数,a1,a2,…,an.(0<=ai<=1000,000,000).有q(q<=100,000)个询问。每个询问为l,r(1<=l<=r<=n).求gcd(al,al+1,…,ar). 再求区间[l,r]的子区间中(l<=l’<=r’<=r)满足gcd(al,al+1,…,ar) = gcd(al’,al’+1,…ar’)的子区间个数.

image-20240512014736082

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#include <bits/stdc++.h>
#define rep(i,j,k) for (int i = j; i <= k; i++)
#define per(i,j,k) for (int i = j; i >= k; i--)
#define loop(i,j,k) for (int i = j;i != -1; i = k[i])
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ff first
#define ss second
#define mp(i,j) make_pair(i,j)
#define pb push_back
#define pii pair<int,LL>
#define in(x) scanf("%d", &x);
using namespace std;
typedef long long LL;
const int low(int x) { return x&-x; }
const double eps = 1e-4;
const int INF = 0x7FFFFFFF;
const int N = 4e5 + 10;
int T, n, m, l, r;
int g[N],d[N];

long long s[N], k[N];

int gcd(int x, int y) {
return x%y ? gcd(y, x%y) : y;
}

void build(int x,int l,int r) {
if (l == r) {
scanf("%d",&g[x]);
d[r] = g[x];
}
else {
int mid = l + r >> 1;
build(lson);
build(rson);
g[x] = gcd(g[x<<1], g[x<<1|1]);
}
}

int query(int x,int l,int r,int ll,int rr) {
if (ll<=l && r<=rr) return g[x];
int mid = l + r>>1;
if (rr <= mid) return query(lson,ll,rr);
else if (ll > mid) return query(rson,ll,rr);
else return gcd(query(lson,ll,rr),query(rson,ll,rr));
}

void pushUp(int rt) {
s[rt] = s[2 * rt] + s[2 * rt + 1];
}

void pushDown(int rt, int l, int r) {
if(k[rt] == 0) return;
int mid = (l + r) / 2;
s[2 * rt] += (k[rt] * (mid - l + 1));
s[2 * rt + 1] += (k[rt] * (r - mid));
k[2 * rt] += k[rt];
k[2 * rt + 1] += k[rt];
k[rt] = 0;
}

void update(int L, int R, long long val, int l, int r, int rt) {
if(L <= l && r <= R) {
s[rt] += (val * (r - l + 1));
k[rt] += val;
return;
}
int mid = (l + r) / 2;
pushDown(rt, l, r);
if(L <= mid) update(L, R, val, l, mid, 2 * rt);
if(R > mid) update(L, R, val, mid + 1, r, 2 * rt + 1);
pushUp(rt);
}

long long get(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) return s[rt];
pushDown(rt, l, r);
int mid = (l + r) / 2;
long long left = 0, right = 0;
if(L <= mid) left = get(L, R, l, mid, 2 * rt);
if(R > mid) right = get(L, R, mid + 1, r, 2 * rt + 1);
pushUp(rt);
return left + right;
}

struct point{
int l,r,g, id;
}c[N];

struct point2 {
int l1, l2, r, g;
}h[N];
int szh;

long long ans[N];

bool cmpc(const point& a, const point& b) {
if(a.g != b.g) return a.g < b.g;
return a.r < b.r;
}

bool cmph(const point2& a, const point2& b) {
if(a.g != b.g) return a.g < b.g;
return a.r < b.r;
}

bool cmpid(const point& a, const point& b ) {
return a.id < b.id;
}

int main() {
#ifdef ZHOUZHENTAO
freopen("test.in", "r", stdin);
#endif

scanf("%d", &T);
int cas = 0;
while (T--) {
scanf("%d", &n);
build(1, 1, n);
scanf("%d", &m);
printf("Case #%d:\n", ++cas);
for (int i = 0;i< m ;i++) {
scanf("%d%d", &c[i].l, &c[i].r);
c[i].g = query(1, 1, n, c[i].l, c[i].r);
c[i].id = i;
}

szh = 0;
stack<pii> a, b;
rep(i, 1, n) {
a.push(mp(d[i], 1));
while (!a.empty()) b.push(mp(gcd(a.top().ff, d[i]), a.top().ss)), a.pop();
while (!b.empty()) {
pii q = b.top(); b.pop();
if (!a.empty() && a.top().ff == q.ff) q.ss += a.top().ss, a.pop();
a.push(q);
}

int L = i;
while (!a.empty()) {
pii q = a.top(); a.pop();
int LL = L - q.second + 1;
h[szh].l1 = LL;
h[szh].l2 = L;
h[szh].r = i;
h[szh].g = q.first;
szh ++;
L = LL - 1;
b.push(q);
}
while (!b.empty()) {
a.push(b.top()); b.pop();
}
}

sort(c, c + m, cmpc);
sort(h, h + szh, cmph);
/*
for(int i = 0; i < szh; i ++) {
cout << h[i].l1 << " " << h[i].l2 << " " << h[i].r << " " << h[i].g << endl;
}

for(int i = 0; i < m; i ++) {
cout << c[i].l << " " << c[i].r << " " << c[i].g << endl;
}
*/
for(int i = 0; i < m; i ++) {
ans[i] = 0;
}
for(int i = 0, j = 0; i < szh; i = j + 1, j = i) {
while(j < szh - 1 && h[j].g == h[j + 1].g) j ++;
// [i, j]

int L = 0, R = m - 1, pos1 = -1, pos2 = -1;
while(L <= R) {
int mid = (L + R) / 2;
if(c[mid].g < h[i].g) L = mid + 1;
else if(c[mid].g > h[i].g) R = mid - 1;
else pos1 = mid, R = mid - 1;
}

if(pos1 == -1) continue;

L = 0, R = m - 1;
while(L <= R) {
int mid = (L + R) / 2;
if(c[mid].g < h[i].g) L = mid + 1;
else if(c[mid].g > h[i].g) R = mid - 1;
else pos2 = mid, L = mid + 1;
}

//[pos1, pos2]
/*
printf("[%d, %d] [%d, %d]\n", i, j, pos1, pos2);
*/
int y = i;
for(int k = pos1; k <= pos2; k ++) {
while(y <= j && h[y].r <= c[k].r) {
update(h[y].l1, h[y].l2, 1LL, 1, n, 1);
y ++;
}
ans[c[k].id] = get(c[k].l, c[k].r, 1, n, 1);
}

for(int k = i; k < y; k ++) {
update(h[k].l1, h[k].l2, -1LL, 1, n, 1);
}

}

sort(c, c + m, cmpid);
for(int i = 0; i < m; i ++) {
printf("%d ", c[i].g);
printf("%lld\n", ans[i]);
}

}
return 0;
}