2016 Multi-University Training Contest 1----解题报告
1.HDOJ 5726 GCD
比赛做这道题的时候,没有做出来,想到解法写不出来,唉,悲哀了。这道题看了标程的解法,感觉写的太简单了,没看懂,所以搜了一下有没有用线段树的大神,结果还真找到了,谢谢just_sort提供的思路,我一开始确实想的是,第一个输出用线段树的区间GCD,然后第二个输出通过暴力打表的方式,可是真的很暴力,完全就是超时的写法。
这里暴力打表有一个概念就是,当我在计算2 4 6 7这组数据的时候,4 6和2 4 6的GCD都是2,那么当7和4 6, 2 4 6求GCD和的时候,实际上就是和2就求GCD和,结果为1,如果这里7之前GCD和为2的有n组,那么我们可以断定GCD和为1的组数肯定会增加n组,代码里面虽然使用了双重for循环,但是其实,第二个for循环用来遍历map里面记录的GCD和,而且我们可以知道数据量一旦大了,GCD和很多时候都是1了,所有这个map并不大。
代码如下:
const int MAXN = 1000010;
int sum[MAXN << 2];
int a[MAXN];
int m = 1;
/线段树模板//
void PushUP(int root)
{
sum[root] = __gcd(sum[root<<1], sum[root<<1|1]);
}
void build(int root, int left, int right)
{
if (left == right)
{
sum[root] = a[m++];
return;
}
int middle = (left + right) >> 1;
build(root << 1, left, middle);
build(root << 1|1, middle + 1, right);
PushUP(root);
}
int Query(int L, int R, int root, int left, int right)
{
if (L <= left && right <= R)
{
return sum[root];
}
int m = (left + right) >> 1;
int ret = 0;
if (L <= m)
ret = __gcd(ret, Query(L, R, root << 1, left, m));
if (R > m)
ret = __gcd(ret, Query(L, R, root << 1|1, m + 1, right));
return ret;
}
///以上是线段树模板/
int n;
map<LL, LL>ans; //保存结果
map<LL, LL>mp1; //记录部分GCD和的组数
map<LL, LL>mp2; //中间变量
int main()
{
#ifdef LOCAL
///freopen("in.txt", "r", stdin);
///freopen("out.txt", "w", stdout);
#endif // LOCAL
int T;
scanf("%d", &T);
int Ca = 1;
while(T--)
{
scanf("%d", &n);
ans.clear();
mp1.clear();
mp2.clear();
m = 1;
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
build(1, 1, n);
mp1[a[1]]++;
ans[a[1]]++;
for(int i = 2; i <= n; ++i)
{
LL now = a[i];
mp2[now]++;
ans[now]++;
for(auto it = mp1.begin(); it != mp1.end(); it++) //扫描确定新的gcd和
{
int next = __gcd(now, it->first);
ans[next] += it->second; //这里代表就是上面说的前面有n组,只要之前的GCD和相等那么,他与当前数字的GCD和的组合只需要加上n即可
mp2[next] += it->second;
}
mp1.clear();
mp1 = mp2; //复制
mp2.clear();
}
int q;
printf("Case #%d:\n", Ca++);
scanf("%d", &q);
while(q--)
{
int l, r;
scanf("%d%d", &l, &r);
LL temp = Query(l, r, 1, 1, n);
printf("%I64d %I64d\n", temp, ans[temp]);
}
}
return 0;
}