HDU 5381 The sum of gcd

52 篇文章 0 订阅
7 篇文章 0 订阅

Description

You have an array  ,the length of   is   
Let   

Input

There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case: 
First line has one integers   
Second line has   integers   
Third line has one integers  ,the number of questions 
Next there are Q lines,each line has two integers  ,  
 
 
 

Output

For each question,you need to print 

Sample Input

2
5
1 2 3 4 5
3
1 3
2 3
1 4
4
4 2 6 9
3
1 3
2 4
2 3

Sample Output

9
6
16
18
23

10

求一个区间里全部子区间的gcd和。

有离线和在线两种方法。

在线可以直接对n个点建立线段树,

每个节点记录左右端点开始的全部gcd的情况。

由于gcd本身是log的,每个节点记录的值的数量不会很多。

合并的时候统计一下大区间的gcd,和,以及新的左右情况。

这样对于每个询问可以直接找到区间合并起来就行了。

#include<set>
#include<map>
#include<ctime>
#include<cmath>
#include<stack>
#include<queue>
#include<bitset>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#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 mod = 9973;
const int N = 3e5 + 10;
int T, n, m, l, r;

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

struct point
{
	int g;
	LL sum;
	vector<pii> l, r;
	void clear()
	{
		sum = 0; l.clear();	r.clear();
	}
	void get()
	{
		in(g); sum = g;
		l.pb(mp(g, 1));
		r.pb(mp(g, 1));
	}
}f[N], ans;

point merge(point a, point b)
{
	point f;  f.clear();
	f.g = gcd(a.g, b.g);
	f.sum = a.sum + b.sum;
	rep(i, 1, a.l.size()) f.l.pb(a.l[i - 1]);
	rep(i, 1, b.l.size())
	{
		int q = gcd(a.g, b.l[i - 1].ff);
		if (q == f.l[f.l.size() - 1].ff) f.l[f.l.size() - 1].ss += b.l[i - 1].ss;
		else f.l.pb(mp(q, b.l[i - 1].ss));
	}
	rep(i, 1, b.r.size()) f.r.pb(b.r[i - 1]);
	rep(i, 1, a.r.size())
	{
		int q = gcd(b.g, a.r[i - 1].ff);
		if (q == f.r[f.r.size() - 1].ff) f.r[f.r.size() - 1].ss += a.r[i - 1].ss;
		else f.r.pb(mp(q, a.r[i - 1].ss));
	}
	rep(i, 1, a.r.size()) rep(j, 1, b.l.size())
	{
		int k = gcd(a.r[i - 1].ff, b.l[j - 1].ff);
		f.sum += a.r[i - 1].ss* b.l[j - 1].ss * k;
	}
	return f;
}

void build(int x, int l, int r)
{
	f[x].clear();
	if (l == r) { f[x].get(); return; }
	int mid = l + r >> 1;
	build(lson);	build(rson);
	f[x] = merge(f[x << 1], f[x << 1 | 1]);
}

void query(int x, int l, int r, int ll, int rr)
{
	if (ll <= l&&r <= rr)
	{
		if (ans.g) ans = merge(ans, f[x]);
		else ans = f[x];
	}
	else
	{
		int mid = l + r >> 1;
		if (ll <= mid) query(lson, ll, rr);
		if (rr > mid) query(rson, ll, rr);
	}
}

int main()
{
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);
		build(1, 1, n);
		scanf("%d", &m);
		while (m--)
		{
			scanf("%d%d", &l, &r);
			ans.g = 0;
			query(1, 1, n, l, r);
			printf("%lld\n", ans.sum);
		}
	}
	return 0;
}

离线可以考虑用莫队算法,不过还是线段树效率更高。

考虑从左往右不断加入新的数字对于之前答案的影响。

记当前放入的是第i个数,记线段树上每个节点的值j的含义是以j为起点的,终点分别从j到i的gcd的和。

放入第i个数之前,从1到i-1的节点值分别是对应节点到i-1的和,

放入第i个数相当于对这些区间分别加了一个对应的gcd(a[j],a[j+1]...a[i])的值。

我们可以根据相同的gcd合并log的得到第i个数对应于前面的log种不同的gcd值,然后加到对应的区间上。

就相当于区间修改,需要的答案就是对应的区间求和,这样用线段树搞定就行了。

#include<set>
#include<map>
#include<ctime>
#include<cmath>
#include<stack>
#include<queue>
#include<bitset>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#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,int>
#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 mod = 9973;
const int N = 3e5 + 10;
int T, n, m, x[N], y[N];
pii g[N];
LL ans[N], f[N], a[N];

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

bool cmp(int x, int y)
{
	return g[x].ss < g[y].ss;
}

void clear(int x, int l, int r)
{
	f[x] = a[x] = 0;
	if (l == r) return;
	int mid = l + r >> 1;
	clear(lson);	clear(rson);
}

void pushdown(int x, int l, int r)
{
	int mid = l + r >> 1;
	a[x << 1] += a[x];	a[x << 1 | 1] += a[x];
	f[x << 1] += a[x] * (mid - l + 1);
	f[x << 1 | 1] += a[x] * (r - mid);
	a[x] = 0;
}

LL get(int x, int l, int r, int ll, int rr)
{
	if (a[x]) pushdown(x, l, r);
	if (ll <= l&&r <= rr) return f[x];
	LL mid = l + r >> 1, res = 0;
	if (ll <= mid) res += get(lson, ll, rr);
	if (rr > mid) res += get(rson, ll, rr);
	return res;
}

void add(int x, int l, int r, int ll, int rr, int v)
{
	if (a[x]) pushdown(x, l, r);
	if (ll <= l&&r <= rr)
	{
		a[x] += v;	f[x] += 1LL * (r - l + 1)*v;
	}
	else
	{
		int mid = l + r >> 1;
		if (ll <= mid) add(lson, ll, rr, v);
		if (rr > mid) add(rson, ll, rr, v);
		f[x] = f[x << 1] + f[x << 1 | 1];
	}
}

int main()
{
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &n);
		rep(i, 1, n) scanf("%d", &x[i]);
		scanf("%d", &m);
		rep(i, 1, m) { in(g[i].ff); in(g[i].ss); y[i] = i; }
		sort(y + 1, y + m + 1, cmp);
		clear(1, 1, n);
		stack<pii> a, b;
		for (int i = 1, j = 1; i <= n; i++)
		{
			a.push(mp(x[i], 1));
			while (!a.empty()) b.push(mp(gcd(a.top().ff, x[i]), a.top().ss)), a.pop();
			int bef = 0;
			while (!b.empty())
			{
				pii q = b.top();	b.pop();
				add(1, 1, n, bef + 1, bef + q.ss, q.ff);	bef += q.ss;
				if (!a.empty() && a.top().ff == q.ff) q.ss += a.top().ss, a.pop();
				a.push(q);
			}
			for (; j <= m&&g[y[j]].ss == i; j++)
			{
				ans[y[j]] = get(1, 1, n, g[y[j]].ff, i);
			}
		}
		rep(i, 1, m) printf("%lld\n", ans[i]);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值