浅谈数据结构【复习】
线段树:
https://www.luogu.org/problem/CF558E
分析:
我们考虑建26棵线段树,
第i棵线段树的[x,y]表示在[x,y]中一共有多少个字母'a'+i-1 至于修改时我们可以以升序为例,
从a至z按顺序往前丢,记得要清空区间 同理,降序反过来就是了
code by wzxbeliever:
#include<bits/stdc++.h>
#define il inline
#define ri register int
using namespace std;
const int maxn=1e5+5;
int n,m;
char ch[maxn];
int tag[maxn<<2][27],tree[maxn<<2][27];
il void up(int rt,int num){tree[rt][num]=tree[rt<<1][num]+tree[rt<<1|1][num];}
il void bulid(int rt,int l,int r,int num){
if(l==r){tree[rt][ch[l]-'a'+1]=1;return;}
int mid=l+r>>1;
bulid(rt<<1,l,mid,num);
bulid(rt<<1|1,mid+1,r,num);
for(ri i=1;i<=26;i++)up(rt,i);
}
il void modify(int rt,int l,int r,int num,int val){tree[rt][num]=(r-l+1)*val;tag[rt][num]=val;return;}
il void down(int rt,int l,int r,int num){
if(tag[rt][num]==-1)return;
int mid=l+r>>1;
modify(rt<<1,l,mid,num,tag[rt][num]);
modify(rt<<1|1,mid+1,r,num,tag[rt][num]);
tag[rt][num]=-1;
}
il void upd(int rt,int l,int r,int L,int R,int num,int p){
if(L<=l&&r<=R){modify(rt,l,r,num,p);return;}
int mid=l+r>>1;
down(rt,l,r,num);
if(mid>=L)upd(rt<<1,l,mid,L,R,num,p);
if(R>mid)upd(rt<<1|1,mid+1,r,L,R,num,p);
up(rt,num);
}
il int query(int rt,int l,int r,int L,int R,int num){
if(L<=l&&r<=R)return tree[rt][num];
int mid=l+r>>1;
down(rt,l,r,num);
int sum=0;
if(mid>=L)sum+=query(rt<<1,l,mid,L,R,num);
if(R>mid)sum+=query(rt<<1|1,mid+1,r,L,R,num);
return sum;
}
il void output(int rt,int l,int r){
if(l==r){
for(ri i=1;i<=26;i++)
if(tree[rt][i]){ch[l]='a'+i-1;break;}
return ;
}
int mid=l+r>>1;
for(ri i=1;i<=26;i++)down(rt,l,r,i);
output(rt<<1,l,mid);
output(rt<<1|1,mid+1,r);
}
int main(){
scanf("%d%d",&n,&m);
cin>>ch+1;
for(ri i=1;i<=26;i++)bulid(1,1,n,i);
memset(tag,-1,sizeof tag);//记得所有tag都要-1,并不是只有点的才为-1
while(m--){
int l,r,op;
scanf("%d%d%d",&l,&r,&op);
if(op){
int tmp=l-1;
for(ri i=1;i<=26;i++){
int res=query(1,1,n,l,r,i);
if(!res)continue;
upd(1,1,n,l,r,i,0);
upd(1,1,n,tmp+1,tmp+res,i,1);
tmp+=res;
}
}
else{
int tmp=l-1;
for(ri i=26;i>=1;i--){
int res=query(1,1,n,l,r,i);
if(!res)continue;
upd(1,1,n,l,r,i,0);
upd(1,1,n,tmp+1,tmp+res,i,1);
tmp+=res;
}
}
}
output(1,1,n);
printf("%s\n",ch+1);
return 0;
}
https://www.luogu.org/problem/CF911G
分析:
线段树里面维护每个节点对应的区间里面,每个数将会变成哪个数
初始时tag[i]=i
下传标记时当前节点的tag等于父亲节点的tag
修改时枚举修改
递归输出
code by std:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <complex>
#include <algorithm>
#include <climits>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iomanip>
#define A 1000010
#define B 2010
using namespace std;
typedef long long ll;
struct node {
int l, r, w, tag[101];
}tree[A];
int n, q, a, b, x, y;
void build(int k, int l, int r) {
tree[k].l = l; tree[k].r = r;
for (int i = 1; i <= 100; i++) tree[k].tag[i] = i;
if (l == r) {
scanf("%d", &tree[k].w);
return;
}
int m = (l + r) >> 1;
build(k << 1, l, m);
build(k << 1 | 1, m + 1, r);
}
void down(int k) {
if (tree[k].l == tree[k].r) return;
for (int i = 1; i <= 100; i++)
tree[k << 1].tag[i] = tree[k].tag[tree[k << 1].tag[i]],
tree[k << 1 | 1].tag[i] = tree[k].tag[tree[k << 1 | 1].tag[i]];
for (int i = 1; i <= 100; i++) tree[k].tag[i] = i;
}
void change(int k) {
if (tree[k].l >= a and tree[k].r <= b) {
for (int i = 1; i <= 100; i++)
if (tree[k].tag[i] == x)
tree[k].tag[i] = y;
return;
}
down(k);
int m = (tree[k].l + tree[k].r) >> 1;
if (a <= m) change(k << 1);
if (b > m) change(k << 1 | 1);
}
void output(int k) {
if (tree[k].l == tree[k].r) {
int m = (tree[k].l + tree[k].r) >> 1;
cout << tree[k].tag[tree[k].w] << " ";
return;
}
down(k); output(k << 1); output(k << 1 | 1);
}
int main(int argc, char const *argv[]) {
cin >> n;
build(1, 1, n);
cin >> q;
while (q--) {
cin >> a >> b >> x >> y;
change(1);
}
output(1);
return 0;
}
树状数组:
如果在树上,首先想到可以树上树状数组,加一个撤销操作就可以
因为以前在这里吃过亏,还打权值线段树,真的大费周折
http://codeforces.com/contest/911/problem/D
题目大意:
给定一个数列,每次操作可以翻转一个区间内的所有数,求每个操作后整个区间的逆序对奇偶性
分析:
翻转这个数列之后,总的逆序对变化一定只会发生在这段区间内,而不会和之前的数或者之后的数结合
也就是意味着将这一段的逆序对数变成:这个区间内的数对总数-原有逆序对数
对整个排列的贡献为
原排列逆序对 - 翻转前逆序对 + 翻转前顺序对。
又可知对一个区间,他的总数对 = 顺序对 + 逆序对 = 区间长度 * (区间长度 - 1) / 2
这里总对数为偶数则不会变,为奇数则会变
插入一个就是一个排n列的逆序对数为偶数个数=逆序对数为奇数的个数
这里n较小所以暴力算逆序对
code:
#include<bits/stdc++.h>
using namespace std;
int a[10000];
int dp[2000][2000];
int main()
{
int n;
while (cin >> n)
{
for (int i = 1; i <= n; i++)
cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
if (a[i] > a[j])
ans ++;
}
}
int flag ;
if (ans % 2)
flag = 1;
else flag = 0;
int m;
cin >> m;
for (int i = 0; i < m; i++)
{
int l, r;
cin >> l >> r;
long long temp = (r - l + 1) * (r - l) / 2;
flag ^= (temp % 2);
if (flag)
cout << "odd" << endl;
else
cout << "even" << endl;
}
}
}
http://poj.org/problem?id=2893
分析:
先将二维序列转为一维序列
考虑目标序列特征
那就是逆序对为0
观察0左右上下移动
只有在上下移动的时候,序列的逆序对数才会发生变化
发现如果把这个数列的每个位置标号为 ,设空格的位置为 ,则上下交换实质上就是在数列上把第 个位置与第 的位置进行交换
所以我们会发现,如果在数列中表示,那这两个位置一定会相隔 个数
逆序对数改变也仅在于这m-1个数中
设空格上下交换的数为 ,这 个数里有 个比 大的, 个比 小的。
所以,以下交换为例,总的逆序对数将会
当 为偶数的时候, 与 奇偶性互异,所以每次上下交换都会改变逆序对的奇偶性。设当前空格的位置在第 ,则需要交换 次,判断交换后逆序对奇偶性与目标会不会相同即可。
当 为奇数的时候, 和 奇偶性相同,所以每次上下交换不会改变逆序对的奇偶性。所以直接判断目标状态与此时状态逆序对奇偶性相同不相同即可。
code :
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <set>
#include <map>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#define pb push_back
#define pob pop_back
#define mp make_pair
#define PII pair<int , int>
#define PI 3.14159265
#define lowbit(x) ((x) & (-x))
using namespace std;
const int maxn = 1000 * 1000;
int n , m , N , M , X , Y , a[maxn] , b[maxn] , c[maxn];
void update(int x , int val){
while(x <= N){
c[x] += val;
x += ((x) & (-x));
}
}
int query(int x){
int sum = 0;
while(x){
sum += c[x];
x -= ((x) & (-x));
}
return sum;
}
int calculate(int N){
int total = 0;
for(int i = N;i >= 1;i--){
total += query(a[i] - 1);
update(a[i] , 1);
}
return total;
}
int main(){
while(scanf("%d%d" , &n , &m)){
memset(c , 0 , sizeof(c));
if(n == 0 && m == 0) return 0;
N = 0;
int Line;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
int x;
scanf("%d" , &x);
if(x == 0){
Line = i;
}
else a[++N] = x;
}
}
int total = calculate(N);
if(m % 2 == 1){
if(total % 2 == 0){
puts("YES");
}
else
puts("NO");
}
else if(m % 2 == 0){
if((n - Line) % 2 == 1) total++;
if(total % 2 == 0){
puts("YES");
}
else
puts("NO");
}
}
return 0;
}
权值线段树:
直接上题目:
例题一:
http://acm.hdu.edu.cn/showproblem.php?pid=1394
分析:
考虑每个序列相比原序列只有第一个元素和最后一个元素发生变化了
所以每次减去第一个变化的元素贡献,再加上最后末尾的元素的贡献
就是当前序列的贡献,取个min就好
code:
// luogu-judger-enable-o2
//#pragma comment(linker,"/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<time.h>
#include<map>
#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define Pair pair<ll,int>
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// 水印
//std::ios::sync_with_stdio(false);
// register
const int MAXN=1e4+10;
const int INF32=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const ll mod=192600817;
const double EPS=1.0e-8;
const double PI=acos(-1.0);
int Tree[MAXN<<2];
int a[MAXN];
int n;
void Update(int l,int r,int x,int rt){//修改一个点
if(l==r){
Tree[rt]++;
return ;
}int mid=(l+r)>>1;
if(x<=mid) Update(l,mid,x,rt<<1);
else Update(mid+1,r,x,rt<<1|1);
Tree[rt]=Tree[rt<<1]+Tree[rt<<1|1];
}
int Query(int ql,int qr,int l,int r,int rt){
if(l>=ql&&r<=qr){
return Tree[rt];
}int mid=(l+r)>>1;
int ans=0;
if(ql<=mid) ans+=Query(ql,qr,l,mid,rt<<1);
if(qr>mid) ans+=Query(ql,qr,mid+1,r,rt<<1|1);
return ans;
}
int main(){
while(~scanf("%d",&n)){
clean(Tree,0);
int x,ans=0;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
ans+=Query(a[i]+1,n,1,n,1);
Update(1,n,a[i]+1,1);
}int res=ans;
for(int i=1;i<=n;++i){
res-=Query(1,a[i]+1,1,n,1)-1;
res+=Query(a[i]+1,n,1,n,1)-1;
ans=min(ans,res);
}printf("%d\n",ans);
}
}
/*
*/
树剖:
树剖真的难得调试!!!!
https://www.luogu.org/problem/P4211
分析:
code :
#include<bits/stdc++.h>
#define For(i,a,b) for(i=(a);i<=(b);++i)
#define Forward(i,a,b) for(i=(a);i>=(b);--i)
#define Rep(i,a,b) for(register int i=(a),i##end=(b);i<=i##end;++i)
#define Repe(i,a,b) for(register int i=(a),i##end=(b);i>=i##end;--i)
using namespace std;
template<typename T>inline void read(T &x)
{
T s=0,f=1;char k=getchar();
while(!isdigit(k)&&(k^'-'))k=getchar();
if(!isdigit(k)){f=-1;k=getchar();}
while(isdigit(k)){s=s*10+(k^48);k=getchar();}
x=s*f;
}
#define Chkmax(a,b) a=a>(b)?a:(b)
#define Chkmin(a,b) a=a<(b)?a:(b)
const int MAXN=5e4+7;
static struct edge
{
int v,nxt;
}p[MAXN];
static int n,m,e,head[MAXN],dep[MAXN];
static int fa[MAXN],sz[MAXN],son[MAXN];
inline void add(int u,int v)
{p[++e].v=v;p[e].nxt=head[u];head[u]=e;}
void dfs(int u)
{
sz[u]=1;dep[u]=dep[fa[u]]+1;
for(register int v=head[u];v;v=p[v].nxt)
{
dfs(p[v].v);sz[u]+=sz[p[v].v];
if(!son[u]||sz[p[v].v]>sz[son[u]])
son[u]=p[v].v;
}
}
static int top[MAXN],dfn[MAXN],ri[MAXN];
void dfs(int u,int tp)
{
ri[dfn[u]=++e]=u;top[u]=tp;
if(son[u])dfs(son[u],tp);
else return;
for(register int v=head[u];v;v=p[v].nxt)
if(p[v].v^son[u])dfs(p[v].v,p[v].v);
}
const int mod=201314;
static vector<int>pl[MAXN],mi[MAXN];
static int ask[MAXN];
inline void init()
{
read(n);read(m);
Rep(i,2,n)read(fa[i]),add(++fa[i],i);
dfs(1);e=0;dfs(1,1);
static int l,r;
Rep(i,1,m)read(l),read(r),read(ask[i])
,mi[l].push_back(i),pl[r+1].push_back(i)
,++ask[i];
}
static int sum[MAXN<<2],laz[MAXN<<2];
static int ans[MAXN];
inline void pushdown(int h,int l,int r)
{
if(laz[h])
{
static int mid;mid=(l+r)>>1;
sum[h<<1]=(sum[h<<1]
+1ll*laz[h]*(mid-l+1)%mod)%mod;
sum[h<<1|1]=(sum[h<<1|1]
+1ll*laz[h]*(r-mid)%mod)%mod;
laz[h<<1]+=laz[h];laz[h<<1|1]+=laz[h];
laz[h]=0;
}
}
void modify(int h,int l,int r,int x,int y)
{
if(l>=x&&r<=y)
{
sum[h]=(sum[h]+r-l+1)%mod;
++laz[h];return;
}
pushdown(h,l,r);
int mid=(l+r)>>1;
if(x<=mid)modify(h<<1,l,mid,x,y);
if(y>mid)modify(h<<1|1,mid+1,r,x,y);
sum[h]=(sum[h<<1]+sum[h<<1|1])%mod;
}
inline void modi(int x)
{
while(x)
{
modify(1,1,n,dfn[top[x]],dfn[x])
,x=fa[top[x]];}
}
static vector<int>::iterator it;
inline int query(int h,int l,int r,int x,int y)
{
if(l>=x&&r<=y)return sum[h];
pushdown(h,l,r);
int mid=(l+r)>>1,ans=0;
if(x<=mid)ans=query(h<<1,l,mid,x,y);
if(y>mid)ans=(ans+query(h<<1|1,mid+1,r,x,y))%mod;
return ans;
}
inline int qu(int x)
{
static int ans;ans=0;
while(x)
{
ans=(ans+query(1,1,n,dfn[top[x]],dfn[x]));
x=fa[top[x]];
}
return ans;
}
inline void solve()
{
Rep(i,1,n)
{
modi(i);
for(it=pl[i].begin();it!=pl[i].end();++it)
ans[*it]+=qu(ask[*it]);
for(it=mi[i].begin();it!=mi[i].end();++it)
ans[*it]-=qu(ask[*it]);
}
Rep(i,1,m)printf("%d\n",(ans[i]%mod+mod)%mod);
}
int main()
{
init();
solve();
return 0;
}
ST表:
NYG的动态数点
【问题描述】
NYG是一个善于思考的好学生。
于是NYG想往他的背包中放序列。
某一天NYG得到了一个长度为n的序列{ai}。然后NYG说,如果对于一段区间[L, R],存在L ≤ k ≤ R使得∀i∈[L, R]都有ak | ai,我们认为它有价值,价值为R − L(若不满足条件则没有价值)。
现在NYG想知道所有区间中价值最大为多少,最大价值的区间有多少个,以及这些区间分别是什么。
分析:
考虑二分答案,再用ST表维护区间gcd和min即可
只要区间min==gcd说明就满足
code:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 500004;
template<class T>
inline void read(T &ret)
{
T f=1;
char c;
while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1;
ret=c-'0';
while((c=getchar())&&(c>='0'&&c<='9'))ret = (ret<<3)+(ret<<1)+c-'0';
ret *= f;
}
int n, lg[maxn], ans[maxn], val, cnt;
ll a[maxn];
ll gc[25][maxn], mn[25][maxn];
struct pr{
ll fir, sec;
};
inline ll gcd(ll x, ll y)
{
return y == 0? x: gcd(y, x % y);
}
inline pr getpr(int x, int len)
{
pr ret;
ret.fir = gcd(gc[lg[len]][x], gc[lg[len]][x+len-(1<<lg[len])]);
ret.sec = min(mn[lg[len]][x], mn[lg[len]][x+len-(1<<lg[len])]);
return ret;
}
inline bool check(int x)
{
pr y;
for(register int i = 1; i <= n - x + 1; ++i)
{
y = getpr(i, x);
if(y.fir == y.sec)
return 1;
}
return 0;
}
void work()
{
pr y;
for(register int i = 1; i <= n - val + 1; ++i)
{
y = getpr(i, val);
if(y.fir == y.sec)
ans[++cnt] = i;
}
}
int main()
{
read(n);
lg[0] = -1;
for(register int i = 1; i <= n; ++i)
{
read(a[i]);
lg[i] = lg[i>>1] + 1;
gc[0][i]=mn[0][i]=a[i];
}
for(register int j = 1; j <= lg[n]; ++j)
for(register int i = 1; i <= n - (1<<j) + 1; ++i)
{
gc[j][i] = gcd(gc[j-1][i], gc[j-1][i+(1<<(j-1))]);
mn[j][i] = min(mn[j-1][i], mn[j-1][i+(1<<(j-1))]);
}
register int l = 1, r = n, mid;
while(l <= r)
{
mid = (l + r)>>1;
if(check(mid))
val = mid, l = mid + 1;
else
r = mid - 1;
}
work();
printf("%d %d\n", cnt, val - 1);
for(register int i = 1; i <= cnt; ++i)
printf("%d ", ans[i]);
return 0;
}