题目描述:
乔帝要规划一次星际旅行,星际空间可以视为一个 3 维坐标系,乔帝有 n(n≤1,000,000,000) 个动力装置排成一行(下标从 1 到 n )。
第 i 个动力装置可以让他的飞船 3 个维度的坐标分别增加 xi,yi,zi 。一开始这些动力装置的所有参数都是 0 。
在规划过程中,乔帝可能会对动力装置进行调整,也可能会对一些动力装置的动力进行评估。
具体来说,乔帝会进行 m(m≤40000) 次操作,每次操作可能是以下四种操作之一:
动力增加:指定一个区间 [L,R] 和三个参数 a,b,c ,令区间内所有动力装置的3维坐标分别增加 a,b,c
动力强化:指定一个区间 [L,R] 和一个参数 k ,令区间内所有动力装置的3维坐标分别乘 k
动力转向:指定一个区间 [L,R] ,令区间内所有动力装置的 x,y,z 坐标分别变为原来的 y 坐标, z 坐标,x 坐标
动力查询:指定一个区间 [L,R] ,询问如果使用区间内所有动力装置各一次能将乔帝送到离起点多远的地方(输出距离的平方除以1,000,000,007的余数)
输入格式
从标准输入读入数据。
第一行包含两个正整数 n,m。
接下来 m 行,每行用若干个空格分隔的整数表示一个操作。
每行的第一个整数表示这次进行的是哪一种操作,1,2,3,4分别表示动力增加、动力强化、动力转向、动力查询。
每行的接下来两个整数表示 L,R,含义如上面所述。
每行接下来若干个整数,根据操作类型确定,为a,b,c或k或空。
输出格式
输出到标准输出。
对于每个动力查询操作,输出一行,包含一个整数,表示查询的答案。
线段树方法
题目涉及多种区间操作,是线段树模板题,其中区间加和区间乘可以用懒标记来维护,可以有60分,但是还有个棘手的区间转换操作,我本来想直接交换所有所求区间内部的区间和和懒标记,然后和所求区间只有相交部分的节点就用push_up更新区间和,这样可以保证区间和一定正确,而且也不复杂。但是和所求区间相交部分的懒标记因此被破坏了,之后的操作会出错,我目前不知道怎么解决这点。解决了应该可以AC,等以后做出来了更新。有大佬是给旋转操作附了懒标记AC的,十分厉害,他的题解链接在这里,但是看懂他的代码需要一定的基础,要求有对线段树机制的理解。我的方法是对区间涉及到的部分全部push_down到根节点,交换,再push_up,这个过程有许多细节要注意,而且复杂度比较高,所以最后也只有60分,以后优化了会更新。最近太忙了没时间写注释,等我有空了或有人看的时候再写吧(可以评论)。其中频繁用到的移位操作是找子节点,这是完全二叉树构造线段树的优势。
//未优化的线段树
#include<bits/stdc++.h>
using namespace std;
#define bn 1000000007
typedef unsigned long long ll;
const ll MAXN = 1e5+10;
ll sum[3][MAXN<<2],add[3][MAXN<<2],re[3],temp[3],mul[3][MAXN<<2],ro[3][MAXN<<2],mulnum;
inline void push_up(ll rt,ll i){
sum[i][rt] = sum[i][rt<<1]%bn+sum[i][rt<<1|1]%bn;
}
inline void push_down(ll rt,ll m,ll i){
if(add[i][rt]||mul[i][rt]!=1){
sum[i][rt<<1] = sum[i][rt<<1]*mul[i][rt]%bn;
sum[i][rt<<1|1] = sum[i][rt<<1|1]*mul[i][rt]%bn;
sum[i][rt<<1] = ((m-(m>>1))*add[i][rt]+sum[i][rt<<1])%bn;
sum[i][rt<<1|1] = ((m>>1)*add[i][rt]+sum[i][rt<<1|1])%bn;
mul[i][rt<<1] = mul[i][rt<<1]*mul[i][rt]%bn;
mul[i][rt<<1|1] = mul[i][rt<<1|1]*mul[i][rt]%bn;
add[i][rt<<1] = add[i][rt<<1]*mul[i][rt]%bn;
add[i][rt<<1|1] = add[i][rt<<1|1]*mul[i][rt]%bn;
add[i][rt<<1] = (add[i][rt<<1]+add[i][rt])%bn;
add[i][rt<<1|1] = (add[i][rt<<1|1]+add[i][rt])%bn;
add[i][rt] = 0;
mul[i][rt] = 1;
}
}
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
void build(ll l, ll r, ll rt){
for(ll i=0;i<3;i++){
add[i][rt] = 0;
mul[i][rt] = 1;
ro[i][rt] = 0;
}
if(l==r){
sum[0][rt] = 0;
sum[1][rt] = 0;
sum[2][rt] = 0;
return;
}
ll mid = (l+r)>>1;
build(lson);
build(rson);
//push_up(rt);
}
void update(ll L,ll R,ll l,ll r,ll rt,ll type,ll i){
if(l>=L&&r<=R){
if(type==1){
sum[i][rt] = (sum[i][rt]+(r-l+1)*temp[i])%bn;
add[i][rt] = (add[i][rt]+temp[i])%bn;
return;
}
else if(type==2){
sum[i][rt] = sum[i][rt]*mulnum%bn;
add[i][rt] = add[i][rt]*mulnum%bn;
mul[i][rt] = mul[i][rt]*mulnum%bn;
return;
}
else{
ro[i][rt]++;
if(l==r){
if(i!=0){
swap(sum[i][rt],sum[i-1][rt]);
swap(mul[i][rt],mul[i-1][rt]);
swap(add[i][rt],add[i-1][rt]);
}
return;
}
}
}
push_down(rt,r-l+1,i);
ll mid = (l+r)>>1;
if(L<=mid) update(L,R,lson,type,i);
if(R>mid) update(L,R,rson,type,i);
if(type!=3) push_up(rt,i);
else{
if(i==1) push_up(rt,0);
if(i==2){
push_up(rt,1);
push_up(rt,2);
}
}
}
ll query(ll L,ll R,ll l,ll r,ll rt,ll i){
if(L<=l&&R>=r) return sum[i][rt];
push_down(rt,r-l+1,i);
ll mid = (l+r)>>1;
ll ans = 0;
if(L<=mid) ans+=query(L,R,lson,i);
if(R>mid) ans+=query(L,R,rson,i);
return ans;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
//freopen("in2.txt","r",stdin);
ll n,m;
cin>>n>>m;
build(1,n,1);
while(m--){
ll op,k,L,R;
ll a,b,c;
cin>>op;
if(op==1){
cin>>L>>R>>a>>b>>c;
temp[0] = a;
temp[1] = b;
temp[2] = c;
for(ll i=0;i<3;i++) update(L,R,1,n,1,1,i);
}
if(op==2){
cin>>L>>R>>k;
mulnum = k;
for(ll i=0;i<3;i++) update(L,R,1,n,1,2,i);
}
if(op==3){
cin>>L>>R;
for(ll i=0;i<3;i++) update(L,R,1,n,1,3,i);
}
if(op==4){
cin>>L>>R;
for(ll i=0;i<3;i++) re[i] = query(L,R,1,n,1,i);
cout<<((re[0]%bn)*(re[0]%bn)+(re[1]%bn)*(re[1]%bn)+(re[2]%bn)*(re[2]%bn))%bn<<endl;
}
}
//fclose(stdin);
return 0;
}
暴力法80分:
暴力法要想混高分,需要考虑许多细节和使用小技巧,此题最重要的是优化取余操作,因为每次乘法操作后都要取余,这不会影响最后的输出(根据完全平方公式),如果不取余很快就爆long long了,每次取余可以保证结果在int内,如果模数比较大,就需要用到快速乘算法,比如2019年12月的5题《魔数》,此题可以不用。由于加法影响比较小,可以默认不用取模,会节约零点几秒时间,但是要开unsinged long long,不然有一个样例会过不了。由于频繁取余,我们不将模数定为一个有数据类型的全局变量,而是define成一个机器码,这样取模的时候就直接从最高位开始,会节约大量时间,从40分到80分。
#include<bits/stdc++.h>
using namespace std;
#define bn 1000000007
typedef unsigned long long ll;
struct power{
ll x = 0;
ll y = 0;
ll z = 0;
}arr[100007];
ll x,y,z;
void op1(int left, int right, ll a, ll b, ll c){
for(int i=left; i<=right; i++){
arr[i].x += a;
arr[i].y += b;
arr[i].z += c;
}
}
void op2(int left, int right, int k){
for(int i=left; i<=right; i++){
arr[i].x *= k;
arr[i].y *= k;
arr[i].z *= k;
arr[i].x %= bn;
arr[i].y %= bn;
arr[i].z %= bn;
}
}
void op3(int left, int right){
for(int i=left; i<=right; i++){
swap(arr[i].x,arr[i].z);
swap(arr[i].x,arr[i].y);
}
}
ll op4(int left, int right){
x=y=z=0;
while(left<=right){
x += arr[left].x;
y += arr[left].y;
z += arr[left].z;
left++;
}
return ((x%bn)*(x%bn)+(y%bn)*(y%bn)+(z%bn)*(z%bn))%bn;
}
int main(){
ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
int count = 0;
for(int i=0; i<m; i++){
int op,k,L,R;
ll a,b,c;
cin>>op;
if(op==1){
cin>>L>>R>>a>>b>>c;
op1(L,R,a,b,c);
}
if(op==2){
cin>>L>>R>>k;
op2(L,R,k);
}
if(op==3){
cin>>L>>R;
op3(L,R);
}
if(op==4){
cin>>L>>R;
cout<<op4(L,R)<<endl;
}
}
return 0;
}