注意到操作有结合律,容易想到用一个矩形表示第i次操作对第j个位置的数的影响。那么修改是单行内的区间修改,而查询是单列内的区间查询。这样二维线段树上以列为外层行为内层直接打标记就可以维护。然后就喜闻乐见的被卡常了。当年的标算似乎就是树套树,然而都是可持久化AVL树之类难懂的话。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
#define mp(x,y) make_pair((x),(y))
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
typedef pair<int,int> pii;
int n,m,q,t,a[N],root[N<<2],lastans,isonline,cnt;
pii o;
struct data{int l,r;pii x,y;
}tree[N<<8];
pii trans(pii a,pii b){a.first=1ll*a.first*b.first%m;a.second=1ll*a.second*b.first%m;a.second=(a.second+b.second)%m;return a;}
void up(int k){tree[k].x=trans(tree[tree[k].l].x,tree[tree[k].r].x);}
int newnode(){int k=++cnt;tree[k].x=tree[k].y=o;return k;}
void update(int &k,pii p)
{
if (!k) k=newnode();
tree[k].x=trans(tree[k].x,p);
tree[k].y=trans(tree[k].y,p);
}
void down(int k)
{
update(tree[k].l,tree[k].y);
update(tree[k].r,tree[k].y);
tree[k].y=o;
}
void mul(int &k,int l,int r,int x,int y,pii p)
{
if (!k) k=newnode();
if (l==x&&r==y)
{
update(k,p);
return;
}
if (tree[k].y!=o) down(k);
int mid=l+r>>1;
if (y<=mid) mul(tree[k].l,l,mid,x,y,p);
else if (x>mid) mul(tree[k].r,mid+1,r,x,y,p);
else mul(tree[k].l,l,mid,x,mid,p),mul(tree[k].r,mid+1,r,mid+1,y,p);
up(k);
}
void modify(int k,int l,int r,int x,int p,int q,pii y)
{
mul(root[k],1,n,p,q,y);
if (l==r) return;
int mid=l+r>>1;
if (x<=mid) modify(k<<1,l,mid,x,p,q,y);
else modify(k<<1|1,mid+1,r,x,p,q,y);
}
pii Q(int &k,int l,int r,int x)
{
if (!k) return o;
if (l==r) return tree[k].x;
if (tree[k].y!=o) down(k);
int mid=l+r>>1;
if (x<=mid) return Q(tree[k].l,l,mid,x);
else return Q(tree[k].r,mid+1,r,x);
}
pii query(int k,int l,int r,int x,int y,int p)
{
if (l==x&&r==y) return Q(root[k],1,n,p);
int mid=l+r>>1;
if (y<=mid) return query(k<<1,l,mid,x,y,p);
else if (x>mid) return query(k<<1|1,mid+1,r,x,y,p);
else return trans(query(k<<1,l,mid,x,mid,p),query(k<<1|1,mid+1,r,mid+1,y,p));
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ex_input3.txt","r",stdin);
freopen("a.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
if (read()&1) isonline=1;o.first=1;tree[0].x=o;
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]=read();
q=read();
while (q--)
{
int op=read();
if (op==1)
{
int l=read(),r=read(),a=read(),b=read();
if (isonline) l^=lastans,r^=lastans;
t++;modify(1,1,100000,t,l,r,mp(a,b));
}
else
{
int l=read(),r=read(),x=read();
if (isonline) l^=lastans,r^=lastans,x^=lastans;
pii u=query(1,1,100000,l,r,x);
printf("%d\n",lastans=(1ll*a[x]*u.first+u.second)%m);
}
}
return 0;
}

  考虑小常数做法。注意到x次修改至多会将序列划分成2x+1个不同的段,那么用线段树对修改进行维护,节点内记录这些修改将序列划分成的段,显然总段数是O(nlogn)的(nq同阶)。然而无法在每次修改时都对所有影响到的节点进行修改,因为单个节点修改并非O(1)或O(log)。不过可以在一个节点的修改全部出现后,通过归并排序两个子节点来得到该节点信息。于是这样修改总复杂度就是O(nlogn)。查询时在线段树上区间查询再在节点上二分即可。同样是两个log但常数显然小了很多。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
#define mp(x,y) make_pair((x),(y))
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
int x=0,f=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
typedef pair<int,int> pii;
int n,m,q,t,a[N],lastans,isonline,L[N<<2],R[N<<2];
pii o;
pii trans(pii a,pii b){a.first=1ll*a.first*b.first%m;a.second=1ll*a.second*b.first%m;a.second=(1ll*a.second+b.second)%m;return a;}
struct seg{int l,r;pii x;};
vector<seg> tree[N<<2];
vector<int> id[N];
void build(int k,int l,int r)
{
id[r].push_back(k);L[k]=l,R[k]=r;
tree[k].push_back((seg){1,n,o});
if (l==r) return;
int mid=l+r>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
}
vector<seg> merge(vector<seg> a,vector<seg> b)
{
vector<seg> c;
for (int i=0,j=0;i<a.size()||j<b.size();)
if (a[i].r==b[j].r) c.push_back((seg){max(a[i].l,b[j].l),a[i].r,trans(a[i].x,b[j].x)}),i++,j++;
else if (a[i].r<b[j].r) c.push_back((seg){max(a[i].l,b[j].l),a[i].r,trans(a[i].x,b[j].x)}),i++;
else c.push_back((seg){max(a[i].l,b[j].l),b[j].r,trans(a[i].x,b[j].x)}),j++;
return c;
}
pii query(int k,int l,int r,int x)
{
if (L[k]==l&&R[k]==r)
{
int l=0,r=tree[k].size(),p=0;
while (l<=r)
{
int mid=l+r>>1;
if (x<=tree[k][mid].r) p=mid,r=mid-1;
else l=mid+1;
}
return tree[k][p].x;
}
int mid=L[k]+R[k]>>1;
if (r<=mid) return query(k<<1,l,r,x);
else if (l>mid) return query(k<<1|1,l,r,x);
else return trans(query(k<<1,l,mid,x),query(k<<1|1,mid+1,r,x));
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("b.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
if (read()&1) isonline=1;o.first=1;
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]=read();
q=read();build(1,1,100000);for (int i=1;i<=100000;i++) reverse(id[i].begin(),id[i].end());
while (q--)
{
int op=read();
if (op==1)
{
int l=read(),r=read(),a=read(),b=read();vector<seg>tmp;
if (isonline) l^=lastans,r^=lastans;
if (l>1) tmp.push_back((seg){1,l-1,o});
tmp.push_back((seg){l,r,mp(a,b)});
if (r<n) tmp.push_back((seg){r+1,n,o});
t++;
for (int k:id[t])
if (L[k]==R[k]) tree[k]=merge(tree[k],tmp);
else tree[k]=merge(tree[k<<1],tree[k<<1|1]);
}
else
{
int l=read(),r=read(),x=read();
if (isonline) l^=lastans,r^=lastans,x^=lastans;
pii u=query(1,l,r,x);
printf("%d\n",lastans=(1ll*a[x]*u.first+u.second)%m);
}
}
return 0;
}

  

UOJ46 清华集训2014玄学(线段树)的更多相关文章

  1. [UOJ46][清华集训2014]玄学

    uoj description 给出\(n\)个变换,第\(i\)个变换是将区间中\(l_i,r_i\)的数\(x\)变成\((a_ix+b_i)\mod m\). 每次会新增一个变换,或者查询询问如 ...

  2. 【题解】P4247 [清华集训]序列操作(线段树修改DP)

    [题解]P4247 [清华集训]序列操作(线段树修改DP) 一道神仙数据结构(DP)题. 题目大意 给定你一个序列,会区间加和区间变相反数,要你支持查询一段区间内任意选择\(c\)个数乘起来的和.对1 ...

  3. 【uoj#46】 [清华集训2014] 玄学

      题目传送门:uoj46   题意简述:要求在序列上维护一个操作间支持结合律的区间操作,查询连续一段时间内的操作对单点的作用效果,\(n \leq 10^5,m \leq 6 \times 10^5 ...

  4. 【uoj#164】[清华集训2015]V 线段树维护历史最值

    题目描述 给你一个长度为 $n$ 的序列,支持五种操作: $1\ l\ r\ x$ :将 $[l,r]$ 内的数加上 $x$ :$2\ l\ r\ x$ :将 $[l,r]$ 内的数减去 $x$ ,并 ...

  5. UOJ #164 [清华集训2015]V (线段树)

    题目链接 http://uoj.ac/problem/164 题解 神仙线段树题. 首先赋值操作可以等价于减掉正无穷再加上\(x\). 假设某个位置从前到后的操作序列是: \(x_1,x_2,..., ...

  6. uoj #46[清华集训2014]玄学

    uoj 因为询问是关于一段连续区间内的操作的,所以对操作构建线段树,这里每个点维护若干个不交的区间,每个区间\((l,r,a,b)\)表示区间\([l,r]\)内的数要变成\(ax+b\) 每次把新操 ...

  7. uoj 41 【清华集训2014】矩阵变换 婚姻稳定问题

    [清华集训2014]矩阵变换 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://uoj.ac/problem/41 Description 给出 ...

  8. AC日记——【清华集训2014】奇数国 uoj 38

    #38. [清华集训2014]奇数国 思路: 题目中的number与product不想冲: 即为number与product互素: 所以,求phi(product)即可: 除一个数等同于在模的意义下乘 ...

  9. UOJ46. 【清华集训2014】玄学 [线段树,二进制分组]

    UOJ 思路 模拟赛出了这题,结果我没学过二进制分组--一波主席树然后空间就爆炸了-- 用线段树维护时间序列,每个节点维护\(a_i\to x_i\times a_i+b_i,i\in [1,n]\) ...

随机推荐

  1. 计蒜客 39268.Tasks-签到 (The 2019 ACM-ICPC China Shannxi Provincial Programming Contest A.) 2019ICPC西安邀请赛现场赛重现赛

    Tasks It's too late now, but you still have too much work to do. There are nn tasks on your list. Th ...

  2. 大量数据通过Phoenix插入到hbase报错记录(2)

    错误: Caused by: java.sql.SQLException: ERROR (INT10): Unable to find cached index metadata 解决办法: 在hba ...

  3. spring boot后端使用fastjson,错误代码415, 500

    $.post({ url: "/register", dataType: "json", contentType: "application/json ...

  4. Tomcat的安装以及环境变量的配置

    目录 下载 解压并配置环境变量 测试 关闭服务 Tomcat启动时,控制台和IDEA控制台中文乱码解决方案 下载 官方网址:Apache Tomcat® https://tomcat.apache.o ...

  5. C# 反射、使用场景

    创建一个 Console 控制台应用程序, 1. 创建一个 Project 类 public class Project { public int ID { get; set; } public st ...

  6. git clone速度太慢的解决办法(亲测还有效)

    https://www.linuxidc.com/Linux/2019-05/158461.htm 1.查找域名对应的ip地址,并修改hosts文件 linuxidc@linuxidc:~/linux ...

  7. thinkphp---404错误页面

    在用thinkphp开发项目的时候,会额外的处理404错误页面,但是我们很多的开发人员,在处理404错误页面的时候,处理方式都是不对的. 普通处理404的操作是通过 $this->error() ...

  8. LinQ中List,取某个字段,然后用逗号拼接

    string htDetails = string.Join(",", DemoList.Select(t => t.id).Distinct().ToArray());

  9. Docker容器(六)——创建docker私有化仓库

    docker私有化仓库是为了节约带宽(外网速度慢或者干脆不能连外网),以及自己定制系统. (1).环境 youxi1 192.168.5.101 docker私有化仓库 youxi2 192.168. ...

  10. Grafana修改背景色

    grafana默认主题色是黑底白字,我们将它修改成白底黑字: in /etc/grafana/grafana.ini uncomment line and set default_theme = li ...