题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4407
题目大意:
刚开始有一个长度为N的序列,排列为{1、2、3、…、N}。下边是M个操作指令。
有两种操作:
操作1:给你三个整数 Left Right d 求出区间[Left,Right]上与整数d互素的数的和
操作2:给你两个整数 pos d 将第pos个位置上的数改为d。
解题思路:
求出区间[Left,Right]上与整数d互素的数的和可以用容斥定理求出,类似于HDU4135。
下边考虑更改操作。看了题意,1 <= M <= 1000。最多只有1000个操作,那么可以把
每次的更改操作都保存起来,在求[Left,Right]上与整数d互素的数的和时,可以先求出
原先未改变时的结果,再加上改变操作中改变的结果,即为最终结果。具体参考代码。
参考博文:http://blog.youkuaiyun.com/ok_again/article/details/11167313
AC代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define LL __int64
using namespace std;
int GCD(int a,int b)
{
if(b == 0)
return a;
return GCD(b,a%b);
}
int Factor[22],ct;
int A[1100],B[1100];
//A[]纪录更改位置(下标) B[]纪录更改的值
void Divide(int N)
{
ct = 0;
for(int i = 2; i <= sqrt(N*1.0); ++i)
{
if(N % i == 0)
{
Factor[ct++] = i;
while(N % i == 0)
N /= i;
}
}
if(N != 1)
Factor[ct++] = N;
}
LL Solve(int Left, int Right)
{
LL ans = 0;
for(int i = 1; i < (1 << ct); ++i)
{
int odd = 0;
int tmp = 1;
for(int j = 0; j < ct; ++j)
{
if((1 << j) & i)
{
odd++;
tmp *= Factor[j];
}
}
LL Num = Right / tmp - (Left-1) / tmp; //区间[L,R]中因子tmp的倍数个数
LL Cnt = Num * (Num+1) / 2 * tmp + ((Left-1)/tmp) * tmp * Num; //[L,R]上因子tmp的倍数和
if(odd & 1) //奇加偶减
ans += Cnt;
else
ans -= Cnt;
}
return (LL)(Left+Right) * (LL)(Right-Left+1) / 2 - ans; //得到区间[L,R]与d互质的数的和
}
int main()
{
int T,N,M,op;
int Left,Right,d,pos;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&N,&M);
int id = 0;
while(M--)
{
scanf("%d",&op);
if(op == 1)
{
scanf("%d%d%d",&Left,&Right,&d);
Divide(d);
LL ans = Solve(Left,Right);
for(int i = 0; i < id; ++i)
{
if(A[i] >= Left && A[i] <= Right)
{
if(GCD(d,A[i]) == 1) //因为原先第i位置上的数为i。计算[L,R]中互质数已经计算过一次,所以要减去
ans -= A[i];
if(GCD(d,B[i]) == 1) //如果更改的数与d互质,则加上该数。
ans += B[i];
}
}
printf("%I64d\n",ans);
}
else
{
int Flag = 0;
scanf("%d%d",&pos,&d);
for(int i = 0; i < id; ++ i)
{
if(A[i] == pos)
{
Flag = 1;
B[i] = d;
break;
}
}
if(!Flag)
{
B[id] = d;
A[id++] = pos;
}
}
}
}
return 0;
}