[HDU4867]Xor (线段树分治+类数位dp)

[HDU4867]Xor (线段树分治+类数位dp)

提供一种\((m+n) log a log m\)带有常数约\(\frac{1}{log n}\)的算法

处理询问,将后来加入的数算进序列中,则每个数\(a_i\)都有一段出现的区间\([L,R]\)

离线询问后,我们考虑用线段树分治将这些数加入到询问区间上

由于最多只有5000个修改操作,事实上这些数在线段树上覆盖的区间最多只有\(10000logm\)个,并且有着极其不满的常数(因为每个位置上的数都由多段区间组合而来,总长为\(m\),或者你可以觉得我在放屁)

如果直接处理每个数的贡献,那么这个\(dp\)是\(a*a\)转移的

然而事实上我们存在一种\(a*loga\)的转移方法

对于一个数\(x\),如果我们取\(y \leq x\)时,最高位为\(0\),则后面的位均可以随便取

换句话说,对于每一个前\(k\)位相同的集合,它们都能够转移到它们之间的任何一个,可以直接累和

同样的,考虑在第\(k\)位出现一个\(x\)在该位为\(1\),\(y\)为\(0\),都具有类似的转移性质

最后写出来跟数位\(dp\)一个样子。。。

(真不行你可以试试某变换,但是我不会!)

这样的情况个数即这个数\(1\)位的个数,这样的个数期望情况下可以看做常数。。。

所以我们得到了一个期望优秀的算法,实际运行时间也非常优秀

#include<cstdio>
#include<cctype>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)

char IO;
int rd(){
    int s=0,f=0;
    while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    do s=(s<<1)+(s<<3)+(IO^'0');
    while(isdigit(IO=getchar()));
    return f?-s:s;
}

const int N=1e5+10,P=1e9+7;

int n,m,E[N],Now[N],L[N],R[N],A=1023;
int dp[1024],tmp[1024],tmp2[1024];
int a[20],l,Ans[N],Qx[N],sq[N];
char opt[N];

#define Mod(x) ((x>=P)&&(x-=P))

void Solve(int p,int Up,int lim){
    if(p<0) {
        rep(S,0,A) tmp[S]+=dp[S^Up],Mod(tmp[S]);
        return;
    }
    if(!lim) {
        reg int t,Down=(1<<(p+1))-1;
        rep(S,0,A) tmp2[S]=0;
        rep(S,0,A) tmp2[t=Up^S^(S&Down)]+=dp[S],Mod(tmp2[t]); // 前几位相同的累和
        rep(S,0,A) tmp[S]+=tmp2[S^(S&Down)],Mod(tmp[S]);
        return;
    }
    rep(i,0,a[p]) Solve(p-1,Up|(i<<p),i==a[p]);
}

void Add(int x){
    if(!x) return;
    l=-1;
    while(x) a[++l]=(x&1),x>>=1;
    Solve(l,0,1);
    rep(S,0,A) dp[S]=tmp[S],tmp[S]=0;
}

vector <int> G[N];
void AddQue(int p,int l,int r,int ql,int qr,int x){
    if(l==ql&&r==qr) {
        G[p].push_back(x);
        return;
    }
    int mid=(l+r)>>1;
    if(qr<=mid) AddQue(p<<1,l,mid,ql,qr,x);
    else if(ql>mid) AddQue(p<<1|1,mid+1,r,ql,qr,x);
    else AddQue(p<<1,l,mid,ql,mid,x),AddQue(p<<1|1,mid+1,r,mid+1,qr,x);
}

int tmp3[20][1024];
void AnsQue(int p,int l,int r,int dep){
    rep(S,0,A) tmp3[dep][S]=dp[S];
    rep(i,0,G[p].size()-1) Add(G[p][i]);
    if(l==r) {
        Ans[l]=dp[Qx[l]];
        return;
    }
    int mid=(l+r)>>1;
    AnsQue(p<<1,l,mid,dep+1);
    AnsQue(p<<1|1,mid+1,r,dep+1);
    rep(S,0,A) dp[S]=tmp3[dep][S];
}

int main(){
    rep(kase,1,rd()) {
        memset(dp,0,sizeof dp),dp[0]=1;
        n=rd(),m=rd();
        rep(i,1,n) Now[i]=i,E[i]=rd(),L[i]=1,R[i]=m;
        rep(i,1,m*4) G[i].clear();
        rep(i,1,m) {
            while(!isalpha(opt[i]=getchar()));
            if(opt[i]=='C') {
                int x=rd()+1,y=rd();
                R[Now[x]]=i-1;
                Now[x]=++n;
                E[n]=y;
                L[n]=i;
                R[n]=m;
            } else Qx[i]=rd();
            sq[i]=sq[i-1]+(opt[i]=='Q');
        }
        rep(i,1,n) AddQue(1,1,m,L[i],R[i],E[i]);
        AnsQue(1,1,m,0);
        rep(i,1,m) if(opt[i]=='Q') printf("%d\n",Ans[i]);
    }
}

原文地址:https://www.cnblogs.com/chasedeath/p/11832142.html

时间: 2024-11-05 22:47:49

[HDU4867]Xor (线段树分治+类数位dp)的相关文章

线段树分治总结

目录 类型一 例题1:八纵八横 代码: 例题2:时空旅行 首先,要求可以离线. 线段树分治有两种. 类型一 操作基于区间,单点询问. 有时,进行的一种操作可以快速完成,但是,要实现这种操作的逆操作较难. 因为,通常情况下,需要实现的逆操作都是很久以前执行的. 但是,如果只撤销上次操作,就会简单得多. 比如,维护一些连通性,或直径,线性基等问题. 这类问题加边很好做,但删边很难实现. 我们可以扫一遍操作,得到每个操作的有效区间. 然后,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处

【线段树分治 线性基】luoguP3733 [HAOI2017]八纵八横

不知道为什么bzoj没有HAOI2017 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个城市),保证任意两个城市都可以通过高速公路互达. 国正在筹划“八纵八横”的高铁建设计划,计划要修建一些高速铁路,每条高速铁路两端也都是城市(可能两端是同一个城市),也都有一个非负整数的经济影响因子.国家还计划在“八纵八横”计划建成之后,将“一带一路”扩展为“一带_路一

线段树分治

2014徐寅展论文<线段树在一类分治问题上的应用>读后感. 线段树分治 线段树分治其实就是有撤销操作的时间分治. 题目让你维护一些信息,每次可以询问,可以执行一种操作,也可以将之前的某个这种操作撤回. 操作容易维护,但撤回操作不容易维护. 需要将操作,询问都离线下来.将时间轴画出来,那么每个操作只在时间轴上的一个区间内生效. 用线段树给这个区间打上这个操作的标记,维护信息. TJOI2018 数学计算 小豆现在有一个数x,初始值为1. 小豆有Q次操作,操作有两种类型: m: x = x * m

3237: [Ahoi2013]连通图 线段树分治

题解: 线段树分治裸题 apio t1是这个所以就学习了一下 #include <bits/stdc++.h> using namespace std; const int N=2e5+10; struct re{ int x,y; }a[N*2]; int b[N+20][5],cnt,now,n,m,k; int ls[N*15],rs[N*15],data[N*15],last[N+20]; int ph[N*4],pt[N*4],count2[N+20],f[N]; bool ft[N

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并

bzoj 4137 [FJOI2015]火星商店问题——线段树分治+可持久化01trie树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4137 关于可持久化01trie树:https://www.cnblogs.com/LadyLex/p/7281110.html 看了看它的两道例题,就没写. 特殊商品可以直接用可持久化trie做. 其他部分用线段树分治.修改是单点的,询问是区间,原来想的是把询问区间定位后有 mlogn 个,在线段树的每个叶子上贡献一番:结果TLE了,因为若是在叶子处贡献,一个询问就要做 r-l+1 次.

loj#2312. 「HAOI2017」八纵八横(线性基 线段树分治)

题意 题目链接 Sol 线性基+线段树分治板子题.. 调起来有点自闭.. #include<bits/stdc++.h> #define fi first #define se second #define pb push_back #define bit bitset<B + 1> using namespace std; const int MAXN = 501, B = 1001, SS = 4001; inline int read() { char c = getchar

HAOI2017 八纵八横——线段树分治+线性基

题目大意 给定一个图,每次加一些边,或者删掉一些后来加上去的边,定义一个环的价值为环上所有的边的异或和,重复走的边重复算.每次询问这个时刻图中的所有经过1号点的环的最大价值. 思路 首先考虑对于一个静态的图如何求解图中所有经过1号点的环的最大价值,发现这个经过1号点就是唬人的,图中任意一个环都可以经过1号点再走回来. 于是题目变成了求解图中环的最大价值,可以将图中所有的简单环给拎出来放到线性基里面求最大价值,不难发现这是对的. 然后题目转化为了如何求图中所有的简单环,一般我们可以直接对图dfs找