注意到操作有结合律,容易想到用一个矩形表示第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. 洛谷P4343 [SHOI2015]自动刷题机

    题目 易得该题目中的\(n\)和\(k\)具有单调性,满足二分的性质,因此该题目而已用二分来枚举\(n\),然后对于每个\(n\)模拟出它所对应的\(k\),然后注意注意代码细节,并且当当前\(k\) ...

  2. shell脚本编程基础之for循环

    循环结构 循环需要有进入条件和退出条件,如果没有退出条件,则就会一直循环下去 for 变量 in 列表:do 循环体 done 生成列表及示例 {1..100}:生成1到100的整数列表 `comma ...

  3. Hash算法解决冲突的四种方法

    Hash算法解决冲突的方法一般有以下几种常用的解决方法 1, 开放定址法: 所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入 公式为 ...

  4. 【Java 8】巧用Optional之优雅规避NPE问题

    避之不及的 NullPointerException NPE : NullPointerException 空指针异常是最常见的Java异常之一,抛出NPE错误不是用户操作的错误,而是开发人员的错误, ...

  5. ElasticSearch5.2.2 安装(老版本)

    https://www.elastic.co/downloads/elasticsearchElasticSearch是一个高可扩展的开源的全文搜索分析引擎.它允许你快速的存储.搜索和分析大量数据.E ...

  6. Android近场通信---NFC基础转)

    Android近场通信---NFC基础(一)(转) 本文介绍在Android系通过你所能执行的基本任务。它解释了如何用NDEF消息格式来发送和接收NFC数据,并且介绍了支持这些功能的Android框架 ...

  7. python开发--列表当全局变量来使用

    python中,申明全局变量的时候,一般该变量类型基本上是:字符串或数字: 比较少用“列表”当做变量, 当有作用域限制的情况下,想要外部调用内部作用域的“列表”变量时,可以用这种方式,外部申明一个空列 ...

  8. TeslaManage 运行日志

    “TeslaManage.exe”(Win32): 已加载“F:\TeslaManageProject\TeslaManage\x64\Debug\TeslaManage.exe”.已加载符号.“Te ...

  9. PHP 基于 Jenkins ansible 动态选择版本进行自动化部署与回滚(第二版)

    PHP 基于 Jenkins ansible 动态选择版本进行自动化部署与回滚(第二版) 先看流程图: 大概介绍一下: 版本选择使用jenkins 中的 git parameter 插件实现 回滚方式 ...

  10. OpenShift环境中手工模式添加etcd server

    模拟备份和恢复,在现有的集群环境,单master(etcd), infra和node上面添加另外一台机器作为etcd Server. 基于OpenShift 3.11版本,详情可以参考 https:/ ...