D题: Black And White
题意;q次询问:
1 :询问区间内最大连续1的个数。
2:翻转区间的状态 1 -> 0, 0 –> 1;
维护区间 ,包含左端点的 lm, 包含右边端点的 lm, 区间的答案 mx.
那么首先 mx[rt] 应该由左儿子,右儿子,及左右儿子合并得来
mx[1][rt] = max(mx[1][rt << 1], mx[1][rt << 1 | 1]);//mx[1] 代表连续的是 1
mx[1][rt] = max(mx[1][rt], rm[1][rt << 1] + lm[1][rt << 1 | 1]);
lm[rt] ,应该等于 lm[rt<<1], 但是如果lm[rt<<1] == 其区间的长度就要加上 lm[rt<<1|1] 。
求rm[rt] 同 lm[rt]
lm[1][rt] = lm[1][rt << 1];
if (lm[1][rt << 1] == len[rt << 1]) lm[1][rt] += lm[1][rt << 1 | 1];
同理再维护下 连续0即可。
区间翻转的时候讲 lm[0],lm[1] ,rm[0],rm[1], mx[0],rm[1];,交换即可,再打上标记即可。
询问的时候 ,当 mid 位于 ql 和 qr之间的时候,要考虑 lson 和 rson 合并的问题 同时应当考虑到 lson的 rm 的长度不能超过 ql的位置 ,rson的 lm 的长度不能超过 qr的位置 。
return max(query(l, mid, rt << 1, ql, mid), max(query(mid + 1, r, rt << 1 | 1, mid + 1, qr),
min(mid - ql + 1, rm[1][rt << 1]) + min(qr - mid, lm[1][rt << 1 | 1])));
主代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 1010;
using ll = long long;
#define inf 0x7f7f7f7f
int mx[2][maxn << 2], lm[2][maxn << 2], rm[2][maxn << 2], lazy[maxn << 2];
int len[maxn << 2];
inline void maintain(int rt)
{
mx[1][rt] = max(mx[1][rt << 1], mx[1][rt << 1 | 1]);
mx[1][rt] = max(mx[1][rt], rm[1][rt << 1] + lm[1][rt << 1 | 1]);
lm[1][rt] = lm[1][rt << 1];
if (lm[1][rt << 1] == len[rt << 1]) lm[1][rt] += lm[1][rt << 1 | 1];
rm[1][rt] = rm[1][rt << 1 | 1];
if (rm[1][rt << 1 | 1] == len[rt << 1 | 1]) rm[1][rt] += rm[1][rt << 1];
mx[0][rt] = max(mx[0][rt << 1], mx[0][rt << 1 | 1]);
mx[0][rt] = max(mx[0][rt], rm[0][rt << 1] + lm[0][rt << 1 | 1]);
lm[0][rt] = lm[0][rt << 1];
if (lm[0][rt << 1] == len[rt << 1]) lm[0][rt] += lm[0][rt << 1 | 1];
rm[0][rt] = rm[0][rt << 1 | 1];
if (rm[0][rt << 1 | 1] == len[rt << 1 | 1]) rm[0][rt] += rm[0][rt << 1];
}
inline void push(int rt)
{
if (lazy[rt] == 0)return;
swap(mx[1][rt << 1], mx[0][rt << 1]);
swap(lm[1][rt << 1], lm[0][rt << 1]);
swap(rm[1][rt << 1], rm[0][rt << 1]);
swap(mx[1][rt << 1 | 1], mx[0][rt << 1 | 1]);
swap(lm[1][rt << 1 | 1], lm[0][rt << 1 | 1]);
swap(rm[1][rt << 1 | 1], rm[0][rt << 1 | 1]);
lazy[rt << 1] ^= 1;
lazy[rt << 1 | 1] ^= 1;
lazy[rt] = 0;
}
void update(int l, int r, int rt, int ql, int qr)
{
if (ql <= l && qr >= r)
{
lazy[rt] ^= 1;
swap(mx[1][rt], mx[0][rt]);
swap(lm[1][rt], lm[0][rt]);
swap(rm[1][rt], rm[0][rt]);
return;
}
push(rt);
int mid = l + r >> 1;
if (ql <= mid) update(l, mid, rt << 1, ql, qr);
if (qr > mid) update(mid + 1, r, rt << 1 | 1, ql, qr);
maintain(rt);
}
int query(int l, int r, int rt, int ql, int qr)
{
if (ql <= l && qr >= r) return mx[1][rt];
else
{
push(rt);
int mid = l + r >> 1;
if (qr <= mid) return query(l, mid, rt << 1, ql, qr);
else if (ql > mid) return query(mid + 1, r, rt << 1 | 1, ql, qr);
else return max(query(l, mid, rt << 1, ql, mid), max(query(mid + 1, r, rt << 1 | 1, mid + 1, qr),
min(mid - ql + 1, rm[1][rt << 1]) + min(qr - mid, lm[1][rt << 1 | 1])));
}
}
void build(int l, int r, int rt)
{
lazy[rt] = 0;
mx[0][rt] = lm[0][rt] = rm[0][rt] = 0;
mx[1][rt] = lm[1][rt] = rm[1][rt] = 0;
len[rt] = r - l + 1;
if (l == r)
{
scanf("%d", &mx[1][rt]);
mx[0][rt] = !mx[1][rt];
lm[1][rt] = rm[1][rt] = mx[1][rt];
lm[0][rt] = rm[0][rt] = mx[0][rt];
}
else
{
int mid = l + r >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
maintain(rt);
}
}
int main()
{
int n, q;
while (~scanf("%d", &n))
{
build(1, n, 1);
scanf("%d", &q);
while (q--)
{
int op, l, r;
scanf("%d %d %d", &op, &l, &r);
if (op == 0) printf("%d\n", query(1, n, 1, l, r));
else update(1, n, 1, l, r);
}
}
return 0;
}
E题:Mayor’s posters
题意 求覆盖海报后,能看见的海报的种类数
假定,li <= ri < 1e5 那么就是个sb题,用线段树维护下区间的海报的状态,最后计算一下根节点的信息即可。
现在假定在这个数据范围内你们都会做,那么,我们现在考虑li < 1e7的情况,当然会动态开节点的大佬可忽略。
数据范围大怎么办,离散化对吧 ,不会的请自行百度。然后,我们可以将,离散化后上线段树即可,不过离散化会有个问题 ;对于 这组数据:
3
1 10
1 3
6 10
你会发现少算一部分,原因 离散化后 3 ->6 被认为连续,但是并不连续,由于我们写的线段树维护的是点坐标的值,对于连续区间问题,我们发现假如我们在每个离散化后的不连续区间都插入一个点那么,是不是就可以用那个点来表示那段区间的信息。
x += 2; y += 2;
arr[k++] = x; arr[k++] = y;
arr[k++] = x + 1; arr[k++] = x - 1;
arr[k++] = y + 1; arr[k++] = y - 1;
这样就实现了上述功能。
当然,也有其他做法。线段树维护块坐标即可解决离散化后区间连续问题。代码不同之处在于,左儿子 的右端点与右儿子的左端点是一个。
点坐标代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<set>
using namespace std;
const int maxn = 1e5 + 99;
struct WALL {
int l, r, lazy;
}tr[maxn*4],que[maxn];
int arr[maxn*6], vis[maxn];
void build(int l, int r, int pos)
{
tr[pos].l = l; tr[pos].r = r;
tr[pos].lazy = 0;
if (l == r)return;
int mid = l + r >> 1;
build(l, mid, pos <<1);
build(mid + 1, r, pos << 1 | 1);
}
void push_down(int pos)
{
if (tr[pos].lazy == 0)return;
tr[pos << 1].lazy = tr[pos].lazy;
tr[pos << 1 | 1].lazy = tr[pos].lazy;
tr[pos].lazy = 0;
}
void update(int ll, int rr, int pos,int wh)
{
if (tr[pos].l == ll&&tr[pos].r == rr)
{
tr[pos].lazy = wh;
return;
}
push_down(pos);
int mid = tr[pos].l + tr[pos].r >> 1;
if (mid >= rr)update(ll, rr, pos << 1, wh);
else if (mid < ll)update(ll, rr, pos << 1 | 1, wh);
else
{
update(ll, mid, pos << 1, wh);
update(mid + 1, rr, pos << 1 | 1, wh);
}
}
void push_ans(int l,int r,int pos)
{
if (l == r)
{
arr[l] = tr[pos].lazy;
return;
}
push_down(pos);
int mid = l + r >> 1;
push_ans(l, mid, pos << 1);
push_ans(mid + 1, r, pos << 1 | 1);
}
int main()
{
// freopen("D:\\Date\\1.in","r",stdin);
// freopen("D:\\Date\\1.out","w",stdout);
int ca;
scanf("%d", &ca);
while (ca--)
{
int ks;
cin>>ks;
int n,k = 1;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
int x, y;
scanf("%d%d", &x, &y);
x += 2; y += 2;
arr[k++] = x; arr[k++] = y;
arr[k++] = x + 1; arr[k++] = x - 1;
arr[k++] = y + 1; arr[k++] = y - 1;
que[i].l = x; que[i].r = y;
que[i].lazy = i;
}
sort(arr + 1, arr + k);
int len = unique(arr + 1, arr + k) - arr - 1;//去重
build(1, len, 1);
for (int i = 1; i <= n; ++i)
{
int x = lower_bound(arr + 1, arr + len + 1, que[i].l)-arr;//寻找第i次的海报所对应的位置;
int y = lower_bound(arr + 1, arr + len + 1, que[i].r)-arr;
update(x,y, 1, que[i].lazy);
}
memset(arr, 0, sizeof arr);
memset(vis, 0, sizeof vis);
push_ans(1, len, 1);//对tr上的所有标记,push到根节点;
int ans = 0;
for(int i = 1;i<=len;++i)
if (arr[i]!=0&&!vis[arr[i]])
{
ans++;
vis[arr[i]] = 1;
}
printf("%d : %d\n",ks,ans);
}
return 0;
}
块坐标做法
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 2e5+100;
typedef long long ll;
int lazy[maxn<<2];
void push(int rt)
{
if(lazy[rt]==0) return;
lazy[rt<<1] = lazy[rt<<1|1] = lazy[rt];
lazy[rt] = 0;
}
void update(int l,int r,int rt,int ql,int qr,int v)
{
if(ql<=l && qr>=r)lazy[rt] = v;
else
{
if(l==r-1)return;
push(rt);
int mid = l+r>>1;
if(ql<=mid) update(l,mid,rt<<1,ql,qr,v);
if(qr>=mid) update(mid,r,rt<<1|1,ql,qr,v);
}
}
int cnt[maxn];
void getans(int l,int r,int rt)
{
if(l==r-1)cnt[lazy[rt]]++;
else
{
push(rt);
int mid = l+r>>1;
getans(l,mid,rt<<1);
getans(mid,r,rt<<1|1);
}
}
vector<int> ve;
pair<int,int>pos[maxn];
int main()
{
freopen("D:\\Date\\1.in","r",stdin);
freopen("D:\\Date\\2.out","w",stdout);
int t,n,q;
scanf("%d", &t);
while(t--)
{
ve.clear();
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
{
scanf("%d %d", &pos[i].first, &pos[i].second);
pos[i].second++;
ve.push_back(pos[i].first),ve.push_back(pos[i].second);
}
sort(ve.begin(),ve.end());
ve.erase(unique(ve.begin(),ve.end()),ve.end());
for(int i = 1;i <= n; ++i)
{
pos[i].first = lower_bound(ve.begin(), ve.end(), pos[i].first)-ve.begin()+1;
pos[i].second = lower_bound(ve.begin(),ve.end(),pos[i].second)-ve.begin()+1;
update(1,ve.size(),1,pos[i].first,pos[i].second,i);
}
getans(1,ve.size(),1);
int ans = 0;
for(int i = 1;i <= n; ++i)ans += (cnt[i]>=1);
for(int i = 0;i <= n*8; ++i)lazy[i] = 0,cnt[i] = 0;
printf("%d : %d\n",ks,ans);
}
return 0;
}
F Turing Tree
详见 :https://blog.csdn.net/mas3399/article/details/78208289
G Super Mario
题意 q次询问给定 l , r, k 求区间内小于等于 k 的个数。
在线做法需要更高级的结构,所以我们尝试用基本数据结构解决,同样的我们发现,询问的顺序对ans不造成影响,那么在考虑,对于询问 l,r,k 。整个序列中只有小于等于 k 的可能会对ans造成贡献。那么可以对原序列排序,并记录其下标,在对询问排序,对值为 k 的询问,只会有 a[i] <= k的产生贡献,那么将小于等于k的a[i],对应 的下标加入到 用树状数组维护。
询问直接算就ok。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e5+120;
int sum[maxn],n,m,t;
void add(int x,int val)
{
while(x<=n)sum[x] += val,x += (x&(-x));
}
int getsum(int x)
{
int ret = 0;
while(x > 0)ret += sum[x],x -= (x&(-x));
return ret;
}
pair<int,int>a[maxn];
int ans[maxn];
struct node{int l,r,k,id;}que[maxn];
bool cmp(node a,node b) {
return a.k<b.k;
}
int main() {
int ks = 0;
scanf("%d",&t);
while(t--)
{
memset(sum,0,sizeof sum);
scanf("%d%d",&n,&m);
for(int i = 1,x;i <= n; ++i)
{
scanf("%d",&x);
a[i].first = x,a[i].second = i;
}
sort(a+1,a+n+1);
for(int i = 1;i <= m; ++i)
{
scanf("%d %d %d",&que[i].l,&que[i].r,&que[i].k);
que[i].l++,que[i].r++;
que[i].id = i;
}
sort(que+1,que+m+1,cmp);
int p = 1;
for(int i = 1;i <= m; ++i)
{
while(p<=n&&a[p].first<=que[i].k)
{
add(a[p].second,1);
p++;
}
ans[que[i].id] = getsum(que[i].r) - getsum(que[i].l-1);
}
printf("Case %d:\n",++ks);
for(int i = 1;i <= m; ++i)printf("%d\n",ans[i]);
}
return 0;
}
H 题 Seq 维护序列seq
题意:给定一个区间,两种操作,一是区间加,第二是区间乘,然后询问区间和
解法:考虑设置两个lazy标记,add表示加,mul表示乘,由于加法和乘法存在优先级关系,所以
在pushdown的时候一定要考虑到优先级。先假设一个区间add[rt]=x,mul[rt]=y,现在对该区间加z
那么add[rt]=x+y,mul[rt]不变,对该区间乘z,那么add[rt]=xz,mul[rt]=yz。在标记下传的时候
父亲节点的mul标记将会对儿子节点的add和mul标记都产生影响,因此我们先下传mul标记,再下传add标记。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+5;
int n,q;
LL p;
LL tree[maxn<<2],mul[maxn<<2],add[maxn<<2];
inline void pushup(int rt)
{
tree[rt]=tree[rt<<1]+tree[rt<<1|1];
tree[rt]%=p;
}
inline void pushdown(int rt,int L,int R)
{
if(mul[rt]==1&&add[rt]==0) return;
int len=R-L+1;
mul[rt<<1]=mul[rt<<1]*mul[rt]%p;
add[rt<<1]=(add[rt<<1]*mul[rt]%p+add[rt])%p;
tree[rt<<1]=(tree[rt<<1]*mul[rt]%p+add[rt]*(len-len/2)%p)%p;
mul[rt<<1|1]=mul[rt<<1|1]*mul[rt]%p;
add[rt<<1|1]=(add[rt<<1|1]*mul[rt]%p+add[rt])%p;
tree[rt<<1|1]=(tree[rt<<1|1]*mul[rt]%p+add[rt]*(len/2)%p)%p;
mul[rt]=1;
add[rt]=0;
}
inline void build(int rt,int L,int R)
{
mul[rt]=1;
add[rt]=0;
if(L==R){
scanf("%lld",&tree[rt]);
tree[rt]%=p;
return;
}
int mid=L+R>>1;
build(rt<<1,L,mid);
build(rt<<1|1,mid+1,R);
pushup(rt);
}
inline void update(int rt,int L,int R,int l,int r,int op,LL c)
{
if(l<=L&&r>=R){
if(op==1){//mul
mul[rt]=mul[rt]*c%p;
add[rt]=add[rt]*c%p;
tree[rt]=tree[rt]*c%p;
}else{//add
add[rt]=(add[rt]+c)%p;
tree[rt]=(tree[rt]+c*(R-L+1))%p;
}
return;
}
pushdown(rt,L,R);
int mid=L+R>>1;
if(r<=mid) update(rt<<1,L,mid,l,r,op,c);
else if(l>mid) update(rt<<1|1,mid+1,R,l,r,op,c);
else{
update(rt<<1,L,mid,l,mid,op,c);
update(rt<<1|1,mid+1,R,mid+1,r,op,c);
}
pushup(rt);
}
inline LL getans(int rt,int L,int R,int l,int r)
{
if(l<=L&&r>=R) return tree[rt]%p;
int mid=L+R>>1;
pushdown(rt,L,R);
if(r<=mid) return getans(rt<<1,L,mid,l,r);
else if(l>mid) return getans(rt<<1|1,mid+1,R,l,r);
else{
return (getans(rt<<1,L,mid,l,mid)+getans(rt<<1|1,mid+1,R,mid+1,r))%p;
}
}
int main()
{
scanf("%d%lld",&n,&p);
build(1,1,n);
scanf("%d",&q);
while(q--){
int op,l,r;
LL c;
scanf("%d",&op);
if(op==1||op==2){
scanf("%d%d%lld",&l,&r,&c);
update(1,1,n,l,r,op,c%p);
}else{
scanf("%d%d",&l,&r);
LL ans=getans(1,1,n,l,r);
printf("%lld\n",ans);
}
}
return 0;
}
I 题 : Level up 详见:https://blog.csdn.net/mas3399/article/details/78240108
P题 : Tunnel Warfare
题解:
可以用set维护下。
线段树的做法,对每个点维护他的最左边的那个没有被摧毁的村庄 pl[i],和最右边的那个没有被摧毁村庄 pr[i[ 。
假定现在摧毁 第i 个村庄,那将 [pl[i] , i-1] 区间的 pr 值更新为i-1,[ i+1,pr[i] ] 区间的 pl值更新为i-1,并且将pl[i] -> i+1,pr[i] -> i-1;
重建:将 [pl[i-1],i]区间的 pr 的值 -> pr[i+1], [i,pr[i+1]] 区间的 pl 的值 -> ql[i-1]。
答案为 max(0,pr[i] - pl[i]+1)
注意要多组输入
//多组
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e4+1010;
using ll = long long;
int pot[2][maxn<<2]; //0 L, 1 R
int lazy[2][maxn<<2];
void push(int rt,int op)
{
if(lazy[op][rt]==-1) return;
lazy[op][rt<<1] = lazy[op][rt<<1|1] = lazy[op][rt];
pot[op][rt<<1] = pot[op][rt<<1|1] = lazy[op][rt];
lazy[op][rt] = -1;
}
void update(int l,int r,int rt,int ql,int qr,int v,int op)
{
if(ql>qr)return;
if(ql<=l && qr>=r) pot[op][rt] = v,lazy[op][rt] = v;
else
{
push(rt,op);
int mid = l+r>>1;
if(ql<=mid) update(l,mid,rt<<1,ql,qr,v,op);
if(qr>mid) update(mid+1,r,rt<<1|1,ql,qr,v,op);
}
}
pair<int,int> query(int l,int r,int rt,int p)
{
if(l==r)return {pot[0][rt],pot[1][rt]};
else
{
push(rt,0),push(rt,1);
int mid = l+r>>1;
if(p<=mid) return query(l,mid,rt<<1,p);
else return query(mid+1,r,rt<<1|1,p);
}
}
int vig[maxn],top;
int main()
{
int n,m;
while(~scanf("%d %d", &n, &m))
{
memset(lazy,-1,sizeof lazy);
for(int i = 1; i < 4*n; ++i)
pot[0][i] = 1,pot[1][i] = n;
top = 0;
while(m--)
{
char op[10];
int p;
scanf("%s",op);
if(op[0]=='D')
{
scanf("%d",&p);
vig[++top] = p;
pair<int,int>ret = query(1,n,1,p);
update(1,n,1,ret.first,p-1,p-1,1);
update(1,n,1,p+1,ret.second,p+1,0);
update(1,n,1,p,p,p+1,0);
update(1,n,1,p,p,p-1,1);
}
if(op[0]=='Q')
{
scanf("%d",&p);
pair<int,int>ret = query(1,n,1,p);
int len = ret.second - ret.first + 1;
len = max(len,0);
printf("%d\n",len);
}
if(op[0]=='R')
{
if(top==0)continue;
p = vig[top],--top;
pair<int,int> ret = {1,n};
if(p==1)ret.first = 1;
else ret.first = query(1,n,1,p-1).first;
if(p==n) ret.second = n;
else ret.second = query(1,n,1,p+1).second;
update(1,n,1,p,p,ret.first,0);
update(1,n,1,p,p,ret.second,1);
update(1,n,1,ret.first,p-1,ret.second,1);
update(1,n,1,p+1,ret.second,ret.first,0);
ret = query(1,n,1,p);
}
}
}
return 0;
}