目录
题目:敌兵布阵——https://vjudge.net/contest/285952#problem/A
题目:I hate it——https://vjudge.net/contest/285952#problem/B
题目:Balanced Lineup——https://vjudge.net/contest/285952#problem/E
题目:Just a Hook——https://vjudge.net/contest/285952#problem/C
题目:Color the ball——https://vjudge.net/contest/285952#problem/F
题目:Mayor's posters——http://poj.org/problem?id=2528
题目:Can you answer these questions?——https://vjudge.net/contest/290499#problem/H
题目:Assign the task (dfs序)——http://acm.hdu.edu.cn/showproblem.php?pid=3974
题目:Transformation——http://acm.hdu.edu.cn/showproblem.php?pid=4578
题目:Vases and Flowers(Half Search)——http://acm.hdu.edu.cn/showproblem.php?pid=4614
题目:Hotel——https://vjudge.net/contest/285952#problem/D
题目:Tunnel Warfare——http://acm.hdu.edu.cn/showproblem.php?pid=1540
题目:约会安排——http://acm.hdu.edu.cn/showproblem.php?pid=4553
题目:Picture——http://acm.hdu.edu.cn/showproblem.php?pid=1828
题目:Atlantis——http://acm.hdu.edu.cn/showproblem.php?pid=1542
题目:覆盖的面积——http://acm.hdu.edu.cn/showproblem.php?pid=1255
类型:单点更新、区间查询
题目:敌兵布阵——https://vjudge.net/contest/285952#problem/A
题意:
N个初始数据,若干操作。
操作类型:
(1) Add i j,i和j为正整数,表示第i个位置加j(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个位置减j(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个数的总和;
(4)End 表示结束,这条命令在每组数据最后出现;
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;
int a[maxn<<2], n, t;
void pushup(int root){a[root] = a[lson] + a[rson];}
void build(int root, int l, int r)
{
if(l==r) scanf("%d", &a[root]);
else {
int mid = (l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(root);
}
}
int query(int root, int l, int r, int L, int R)
{
if(L <= l && R >= r) return a[root];
else {
int mid = (l + r) >> 1, sum=0;
if(L <= mid) sum += query(lson, l, mid, L, R);
if(R > mid) sum += query(rson, mid+1, r, L, R);
return sum;
}
}
void update(int root, int l, int r, int index, int x)
{
if(l == r) {
a[root] += x;
return ;
}
int mid = (l+r)>>1;
if(index <= mid) update(lson, l, mid, index, x);
else update(rson, mid+1, r, index, x);
pushup(root);
}
int main() {
int Case=0;
scanf("%d", &t);
while(t--){
memset(a, 0, sizeof(a));
scanf("%d", &n);
build(1, 1, n);
//for(int i=1; i<=n*4; i++) cout << a[i] << endl;
printf("Case %d:\n", ++Case);
while(1){
char ch[8]; int x, y;
scanf("%s", ch);
if(ch[0]=='E') break;
else {
scanf("%d%d", &x, &y);
if(ch[0]=='Q'){
printf("%d\n", query(1, 1, n, x, y));
}else if(ch[0]=='A'){
update(1, 1, n, x, y);
}else if(ch[0]=='S'){
update(1, 1, n, x, -1*y);
}
}
}
}
return 0;
}
题目:I hate it——https://vjudge.net/contest/285952#problem/B
题意:
N个数,两种操作:单点更新、查找区间最大值
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 2e5+5;
int a[maxn<<2], n, m;
void pushup(int root){a[root] = max(a[lson], a[rson]);}
void build(int root, int l, int r)
{
if(l==r) scanf("%d", &a[root]);
else {
int mid = (l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(root);
}
}
int query(int root, int l, int r, int L, int R)
{
if(L <= l && R >= r) return a[root];
else {
int mid = (l + r) >> 1, Max=0;
if(L <= mid) Max = max(Max, query(lson, l, mid, L, R));
if(R > mid) Max = max(Max, query(rson, mid+1, r, L, R));
return Max;
}
}
void update(int root, int l, int r, int index, int x)
{
if(l == r) {
a[root] = x;
return ;
}
int mid = (l+r)>>1;
if(index <= mid) update(lson, l, mid, index, x);
else update(rson, mid+1, r, index, x);
pushup(root);
}
int main() {
while(~scanf("%d%d", &n, &m)){
build(1, 1, n);
for(int i=1; i<=m; i++){
char ch[3]; int x, y;
scanf("%s%d%d", ch, &x, &y);
if(ch[0]=='Q'){
printf("%d\n", query(1, 1, n, x, y));
}else {
update(1, 1, n, x, y);
//for(int i=1; i<=n*4; i++) cout << a[i] << endl;
}
}
}
return 0;
}
题目:Balanced Lineup——https://vjudge.net/contest/285952#problem/E
题意:
N个数,M个操作:每次输出区间[x,y]内的最大值和最小值之差。
思路:
同时维护最大值和最小值即可
代码:
#include <iostream>
#include <cstdio>
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1, r
#define INF 1e9
using namespace std;
const int maxn = 5e4+5;
int Max[maxn<<2], Min[maxn<<2], N, M, x, y;
void pushup(int rt)
{
Max[rt] = max(Max[rt<<1], Max[rt<<1|1]);
Min[rt] = min(Min[rt<<1], Min[rt<<1|1]);
}
void build(int rt, int l, int r)
{
if(l == r) {
scanf("%d", &Max[rt]);
Min[rt] = Max[rt];
return ;
}
int mid = (l+r)>>1;
build(lson);
build(rson);
pushup(rt);
}
int query_Max(int rt, int l, int r, int L, int R)
{
if(L<=l && R>=r) return Max[rt];
int mid = (l+r)>>1, ret=0;
if(L <= mid) ret = max(ret, query_Max(lson, L, R));
if(R > mid) ret = max(ret, query_Max(rson, L, R));
return ret;
}
int query_Min(int rt, int l, int r, int L, int R)
{
if(L<=l && R>=r) return Min[rt];
int mid = (l+r)>>1, ret=INF;
if(L <= mid) ret = min(ret, query_Min(lson, L, R));
if(R > mid) ret = min(ret, query_Min(rson, L, R));
return ret;
}
int main()
{
while(~scanf("%d%d", &N, &M)){
build(1, 1, N);
for(int i=1; i<=M; i++){
scanf("%d%d", &x, &y);
printf("%d\n", query_Max(1, 1, N, x, y)-query_Min(1, 1, N, x, y));
}
}
}
类型:区间修改,区间查询
题目:Just a Hook——https://vjudge.net/contest/285952#problem/C
题意:
长为N、初始为1的数列,操作:将[x,y]区间内的数改为z,最后输出数列所有数之和。
思路:
区间修改即可,最后答案在a[1]内。
代码:
#include<iostream>
#include<cstdio>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1|1
using namespace std;
const int maxn = 100005;
int Sum[maxn << 2], lazy[maxn << 2];
void pushUp(int rt)
{
Sum[rt] = Sum[rt << 1]+Sum[rt << 1|1];
}
void pushdown(int rt, int m)
{
if(lazy[rt]){
lazy[rt<<1] = lazy[rt];
lazy[rt<<1|1] = lazy[rt];
Sum[rt<<1] = lazy[rt]*(m-(m>>1));
Sum[rt<<1|1] = lazy[rt]*(m>>1);
lazy[rt] = 0;
}
}
void build(int l, int r, int rt)
{
lazy[rt] = 0;
if(l == r){
Sum[rt] = 1;
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
pushUp(rt);
}
int query(int L, int R, int l, int r, int rt)
{
if(L <= l && R >= r){
return Sum[rt];
}
pushdown(rt, r-l+1);
int m = (l + r) >> 1;
int ret = 0;
if(L <= m) ret += query(L, R, lson);
if(R > m) ret += query(L, R, rson);
// pushUp(rt);
return ret;
}
void update(int L, int R, int x, int l, int r, int rt)
{
if(L <= l && R >= r){
Sum[rt] = (r-l+1)*x;
lazy[rt] = x;
return ;
}
pushdown(rt, r-l+1);
int m = (l + r) >> 1;
if(L <= m) update(L, R, x, lson);
if(R > m) update(L, R, x, rson);
pushUp(rt);
}
int main()
{
int N, M, t, Case=0;
scanf("%d", &t);
while(t--){
scanf("%d%d", &N, &M);
build(1, N, 1);
while(M--){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
update(x, y, z, 1, N, 1);
}
printf("Case %d: The total value of the hook is %d.\n", ++Case, query(1, N, 1, N, 1));
}
}
题目:Color the ball——https://vjudge.net/contest/285952#problem/F
题意:
长度为N初始为0的数列,N个操作,每次给[x,y]范围内的数+1。最后输出整个数列。
思路:
区间修改,单点查询
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 1e5+5;
int a[maxn<<2], lazy[maxn<<2], n, t, cnt, ans[maxn];
void pushup(int root){a[root] = a[lson] + a[rson];}
void pushdown(int root, int len)
{
if(lazy[root]){
lazy[lson] += lazy[root];
lazy[rson] += lazy[root];
a[lson] += lazy[root]*(len-(len>>1));
a[rson] += lazy[root]*(len>>1);
lazy[root] = 0;
}
}
void build(int root, int l, int r)
{
lazy[root] = 0;
if(l==r) a[root] = 0;
else {
int mid = (l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(root);
}
}
int query(int root, int l, int r, int L, int R)
{
if(L <= l && R >= r)
return a[root];
pushdown(root, r-l+1);
int mid = (l + r) >> 1, sum=0;
if(L <= mid) sum += query(lson, l, mid, L, R);
if(R > mid) sum += query(rson, mid+1, r, L, R);
return sum;
}
void update(int root, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r) {
a[root] += x*(r-l+1);
lazy[root] += x;
return ;
}
pushdown(root, r-l+1);
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
pushup(root);
}
int main() {
while(~scanf("%d", &n)){
if(n==0) break;
cnt = 1;
//build(1, 1, n);
memset(a, 0, sizeof(a));
memset(lazy, 0, sizeof(lazy));
for(int i=1; i<=n; i++){
int x, y;
scanf("%d%d", &x, &y);
update(1, 1, n, x, y, 1);
}
for(int i=1; i<=n; i++){
printf("%d%c", query(1, 1, n, i, i), i==n?'\n':' ');
}
}
}
题目:Mayor's posters——http://poj.org/problem?id=2528
题意:
在一段墙上贴海报,后贴的可以覆盖先贴的,问最后能看到多少种海报。
思路:
题目中 l、r 的最大值在1e7内,若直接建树对其操作空间会爆。好在 n 的范围在1e4之内,而且从题意可知,最后的结果和数据的大小关系不大,重要的是各数据间的相对大小,所以要对数据进行离散化。从后往前贴,若要贴的部分已经有海报了,就忽略。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define LL long long
#define lson root << 1
#define rson root << 1|1
using namespace std;
const int maxn = 1e5+5;
struct Node
{
int val, index;
bool flag;
bool operator < (const Node& a)const{
if(val == a.val) return index < a.index;
return val < a.val;
}
}node[maxn];
struct Ques
{
int x, y;
}Q[maxn];
int a[maxn<<2], lazy[maxn<<2], ans;
void pushup(int root){a[root] = a[lson] + a[rson];}
void pushdown(int root, int len)
{
if(lazy[root]){
lazy[lson] = lazy[root];
lazy[rson] = lazy[root];
a[lson] = lazy[root]*(len-(len>>1));
a[rson] = lazy[root]*(len>>1);
lazy[root] = 0;
}
}
void build(int root, int l, int r)
{
lazy[root] = 0;
if(l==r) a[root] = 0;
else {
int mid = (l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(root);
}
}
int query(int root, int l, int r, int L, int R)
{
if(L <= l && R >= r)
return a[root];
pushdown(root, r-l+1);
int mid = (l + r) >> 1, sum=0;
if(L <= mid) sum += query(lson, l, mid, L, R);
if(R > mid) sum += query(rson, mid+1, r, L, R);
return sum;
}
void update(int root, int l, int r, int L, int R)
{
if(a[root] == r-l+1) return ;
if(L <= l && R >= r) {
a[root] = (r-l+1);
lazy[root] = 1;
return ;
}
pushdown(root, r-l+1);
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R);
if(R > mid) update(rson, mid+1, r, L, R);
pushup(root);
}
int main()
{
int N, t;
scanf("%d", &t);
while(t--){
scanf("%d", &N);
int x, y, cnt=0;
for(int i=1; i<=N; i++){
scanf("%d%d", &x, &y);
node[++cnt].flag = 0; node[cnt].val = x; node[cnt].index = i;
node[++cnt].flag = 1; node[cnt].val = y; node[cnt].index = i;
}
sort(node+1, node+cnt+1); //离散化
int tmp = 1, last=node[1].val;
for(int i=1; i<=cnt; i++){
if(node[i].val > last){
tmp ++;
}
if(node[i].flag == 0) Q[node[i].index].x = tmp;
else Q[node[i].index].y = tmp;
last=node[i].val;
}
build(1, 1, maxn);
ans = 0;
for(int i=N; i>=1; i--){
if(query(1, 1, maxn, Q[i].x, Q[i].y) == (Q[i].y-Q[i].x+1)) continue;
ans ++;
update(1, 1, maxn, Q[i].x, Q[i].y);
}
cout << ans << endl;
}
}
题目:Can you answer these questions?——https://vjudge.net/contest/290499#problem/H
题意:
对长度为n的数列进行两种操作:
(1)对 [l,r] 内的数据进行开方
(2)询问 [l.r] 范围内的和
思路:
由于一个很大的数进行不了几次开平方操作就会变为1,所以直接进行修改就行,判断区间内是否全为1,如果是就不用再继续进行开方操作了。坑点:数据用long long,题目中是 between x and y .
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#define LL long long
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1|1
using namespace std;
const int maxn = 100005;
LL Sum[maxn << 2], ans[maxn];
void pushUp(int rt)
{
Sum[rt] = Sum[rt << 1]+Sum[rt << 1|1];
}
void build(int l, int r, int rt)
{
if(l == r){
scanf("%lld", &Sum[rt]);
return ;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
pushUp(rt);
}
LL query(int L, int R, int l, int r, int rt)
{
if(L <= l && R >= r){
return Sum[rt];
}
int m = (l + r) >> 1;
LL ret = 0;
if(L <= m) ret += query(L, R, lson);
if(R > m) ret += query(L, R, rson);
return ret;
}
void update(int L, int R, int l, int r, int rt)
{
if(Sum[rt] == (LL)(r-l+1)) return ; //当范围内全为1时不用操作
if(l == r && L <= l && R >= r){
Sum[rt] = (LL)sqrt(Sum[rt]);
return ;
}
int m = (l+r) >> 1;
if(L <= m) update(L, R, lson);
if(R > m) update(L, R, rson);
pushUp(rt);
}
int main()
{
int N, M, t, Case=0;
while(scanf("%d", &N) != EOF){
int cnt = 0;
build(1, N, 1);
scanf("%d", &M);
while(M--){
int x, y, op;
scanf("%d%d%d", &op, &x, &y);
if(x == 0)
update(min(y, x), max(y, x), 1, N, 1); //between x and y
else if(x == 1)
ans[++cnt] = query(min(y,x), max(y,x), 1, N, 1);
//for(int i=1; i<=N; i++){cout << query(i, i, 1, N, 1) << ' ';} cout << endl;
}
printf("Case #%d:\n", ++Case);
for(int i=1; i<=cnt; i++){
printf("%lld\n", ans[i]);
}
cout << endl;
}
}
题目:Assign the task (dfs序)——http://acm.hdu.edu.cn/showproblem.php?pid=3974
思路:
dfs序+区间修改+单点查询。刚开始打了 init()竟然忘了调用 —_—!(无语……)
代码:
#include <iostream>
#include <cstdio>
#include <stack>
#include <vector>
#include <cstring>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 5e4+5;
int t, n, m, in[maxn], out[maxn], a[maxn<<3], cnt; bool vis_son[maxn];
vector<int>vec[maxn];
void init()
{
cnt = 0;
memset(vis_son, false, sizeof(vis_son));
for(int i=0; i<maxn; i++) vec[i].clear();
}
void dfs(int rt)
{
in[rt] = ++cnt;
for(int i=0; i<vec[rt].size(); i++){
dfs(vec[rt][i]);
}
out[rt] = ++cnt;
}
void pushdown(int rt)
{
if(a[rt] != -1){
a[lson] = a[rson] = a[rt];
a[rt] = -1;
}
}
void build(int rt, int l, int r)
{
a[rt] = -1;
if(l==r) return ;
int mid = (l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
}
void update(int rt, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r){
a[rt] = x;
return ;
}
pushdown(rt);
int mid = (l+r) >> 1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
}
int query(int rt, int l, int r, int x)
{
if(l==r) return a[rt];
pushdown(rt);
int mid = (l+r)>>1;
if(x <= mid) query(lson, l, mid, x);
else query(rson, mid+1, r, x);
}
int main()
{
int Case=0;
cin >>t ;
while(t--){
cin >> n;
init();
for(int i=1; i<=n-1; i++){ //储存树
int son , fa;
cin >> son >> fa;
vec[fa].push_back(son);
vis_son[son] = true;
}
int fa;
for(int i=1; i<=n; i++) //找大boss
if(!vis_son[i]){
fa = i; break;
}
dfs(fa);
build(1, 1, cnt);
char ch[2]; int x, y;
cin >> m;
printf("Case #%d:\n", ++Case);
for(int i=1; i<=m; i++){
scanf("%s", ch);
if(ch[0] == 'C'){
cin >> x;
cout << query(1, 1, cnt, in[x]) << endl;
}else {
cin >> x >> y;
update(1, 1, cnt, in[x], out[x], y);
}
}
}
}
题目:Transformation——http://acm.hdu.edu.cn/showproblem.php?pid=4578
题意:
四种操作:1、[l,r]内各数+c
2、[l,r]内各数*c
3、[l,r]内各数变为c
4、[l,r]内各数的p次方之和
思路:
网上有很多直接刚的代码,可怕之极,找到了一种简单思路,但应该不是什么正经解法,可能是这题的数据比较友好。由于是对范围进行操作,所以如果数据不是太刁钻的话会存在很多区间内的数都相等的情况。用flag[]数组代表区间内的数是否相等,a[]数组中保存范围内的数。
代码:
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int mod = 10007;
const int maxn=1e5+5;
int a[maxn<<2];
bool flag[maxn<<2];
void pushup(int rt)
{
if(!flag[lson] || !flag[rson] || (a[lson] != a[rson])) flag[rt] = false;
else flag[rt] = true, a[rt] = a[lson] = a[rson];
}
void pushdown(int rt)
{
if(flag[rt]){
flag[lson] = flag[rson] = flag[rt];
a[lson] = a[rson] = a[rt];
flag[rt] = false;
}
}
void update(int rt, int l, int r, int L, int R, int x, int op)
{
if(L <= l && R >= r && flag[rt]) {
if(op == 1) a[rt] = (a[rt]+x)%mod;
else if(op == 2) a[rt] = (a[rt]*x)%mod;
else a[rt] = x;
return ;
}
pushdown(rt);
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x, op);
if(R > mid) update(rson, mid+1, r, L, R, x, op);
pushup(rt);
}
int query(int rt, int l, int r, int L, int R, int k)
{
if(L <= l && R >= r && flag[rt]) {
int ans = 1;
for(int i=1; i<=k; i++) ans = (a[rt]*ans)%mod;
return (ans*(r-l+1))%mod;
}
pushdown(rt);
int mid = (l+r) >> 1, ans = 0;
if(L <= mid) ans = (ans+query(lson, l, mid, L, R, k))%mod;
if(R > mid) ans = (ans+query(rson, mid+1, r, L, R, k))%mod;
return ans%mod;
}
int main()
{
int n,m;
while(scanf("%d%d", &n, &m) && (n||m)){
fill(a, a+(maxn<<2), 0);
fill(flag, flag+(maxn<<2), true);
int op, x, y, z;
while(m--){
scanf("%d%d%d%d",&op,&x,&y,&z);
if(op>=1 && op<=3) update(1, 1, n, x, y, z, op);
else printf("%d\n", query(1, 1, n, x, y, z));
}
}
}
题目:Vases and Flowers(Half Search)——http://acm.hdu.edu.cn/showproblem.php?pid=4614
题意:
n个空花瓶,两种操作:
1、每次从 x 位置向后插 y 个花,若已占用就继续向后找空的,直到花被用完或瓶被插完。
2、清空 [x,y] 内的花瓶。
思路:
在1操作中,如果x位置已定,很明显可以用二分处右边位置保证满足花被用完或瓶被插完。刚开始想的二分有点复杂,虽然x的位置已经确定,但可能已经被占用,输出结果要求是开始插的起始位置和结束位置。所以先找到x右边的第一个空的花瓶位置x0,作为起始位置,然后找从 x0 开始插 y 个花的末尾点,若空位置<y,返回最后一个空位置即可。二分和查找都是log(n),m次操作O(m*log(n)*log(n))。额……思路很生硬,用了三个二分。后来看了别人的代码,只用一个二分即可,在x开始插y个,起始位置其实就是y=1的情况,结束位置是 y=min(y, x后所有的空位置),做的题少果然思路受限。
除了二分之外,因为我刚开始自作聪明导致时间超限。因为数组中只有0和1两种状态,其实只下推延迟标记也能记录正确的变化,但查询时只有区间内全是0或1时可以直接返回r-l+1,否则就要向下查找。如果串是0101010……每次都要叶推到子节点,相当于遍历了整棵树,效率极低。本来还想一个数组就能维护何必用两个呢,结果却是聪明反被聪明误……
代码:
一个数组维护的超时代码,虽然写了三个二分,但好在思路是对的,超时原因是因为线段树维护时的问题,只把线段树改成正常两个数组维护的就行了。聪明反被聪明误,贴以警示……
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson rt<<1
#define rson rt<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;
int t, n, m, x, y, op;
int lazy[maxn<<2];
void pushdown(int rt)
{
if(lazy[rt]!=-1){
lazy[lson] = lazy[rson] = lazy[rt];
lazy[rt] = -1;
}
}
void update(int rt, int l, int r, int L, int R, int op)
{
if(L <= l && R >= r) {
lazy[rt] = (op==1);
return ;
}
pushdown(rt);
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, op);
if(R > mid) update(rson, mid+1, r, L, R, op);
}
int query(int rt, int l, int r, int L, int R)
{
if(l == r) return (lazy[rt]==-1) ? 0 : lazy[rt];
if(L <= l && R >= r && lazy[rt]!=-1) { //这里要范围内全是0或1时才能返回
return lazy[rt]*(r-l+1);
}
pushdown(rt);
int mid = (l+r)>>1, ans = 0;
if(L <= mid) ans += query(lson, l, mid, L, R);
if(R > mid) ans += query(rson, mid+1, r, L, R);
return ans;
}
int erfen1(int x) //在 index=x 右边的第一个0 的下标
{
int l=x, r=n;
while(l<=r){
int mid = (l+r)>>1;
if(query(1, 1, n, x, mid) == mid-x+1) l=mid+1;
else r=mid-1;
}
return l>n ? -1 : l;
}
int erfen3() //最右边的一个0的下标
{
int l=0, r=n;
while(l<=r){
int mid=(l+r)>>1;
if(query(1, 1, n, mid, n) == n-mid+1) r=mid-1;
else l=mid+1;
}
return r;
}
int erfen2(int x, int y) //刚好填满y个的最右端下标
{
int l=x, r=n;
if(r-l+1-query(1, 1, n, l, r) < y) { // x 后的位置全填满
int t = erfen3(); //最后填的位置
if(t < x) return -1;
return t;
}
while(l<=r){
int mid=(l+r)>>1;
if(mid-x+1-query(1, 1, n, x, mid) < y) l=mid+1;
else r=mid-1;
}
return l;
}
int main(){
cin >> t;
while(t--){
cin >> n >> m;
memset(lazy, -1, sizeof(lazy));
for(int i=1; i<=m; i++){
cin >> op >> x >> y;
x++, y++;
if(op == 1){
y--;
int t = erfen2(x, y);
if(t==-1) cout << "Can not put any one." << endl;
else cout << erfen1(x)-1 << " " << t-1 << endl;
update(1, 1, n, x, t, op);
}else {
cout << query(1, 1, n, x, y) << endl;
update(1, 1, n, x, y, op);
}
}
}
}
然后是一个二分的,刚开始build最后忘了pushup(可以直接用memset清空)结果导致第一组的数据对第二组有影响,又盯了好长时间,真是一步一个坑啊。。。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson rt<<1
#define rson rt<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;
int t, n, m, x, y, op;
int lazy[maxn<<2], sum[maxn<<2];
void pushup(int rt)
{
sum[rt] = sum[lson] + sum[rson];
}
void pushdown(int rt, int len)
{
if(lazy[rt]!=-1){
lazy[lson] = lazy[rson] = lazy[rt];
sum[lson] = lazy[rt]*(len-(len>>1));
sum[rson] = lazy[rt]*(len>>1);
lazy[rt] = -1;
}
}
void build(int rt, int l, int r)
{
lazy[rt] = -1;
if(l==r) {
sum[rt] = 0;
return ;
}
int mid=(l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(rt);
}
void update(int rt, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r) {
sum[rt]=x*(r-l+1);
lazy[rt]=x;
return ;
}
pushdown(rt, r-l+1);
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
pushup(rt);
}
int query(int rt, int l, int r, int L, int R)
{
if(L <= l && R >= r) return sum[rt];
pushdown(rt, r-l+1);
int mid = (l+r)>>1, ans = 0;
if(L <= mid) ans += query(lson, l, mid, L, R);
if(R > mid) ans += query(rson, mid+1, r, L, R);
return ans;
}
int erfen(int x, int y)
{
int num = query(1, 1, n, x, n);
if(num == n-x+1) return -1; // x 后面没有空位置
if(n-x+1-num < y) y=n-x+1-num;
int l=x, r=n, index=-1;
while(l<=r){
int mid=(l+r)>>1, temp = mid-x+1-query(1, 1, n, x, mid);
if(temp < y) l=mid+1;
else if(temp > y) r=mid-1;
else index=mid, r=mid-1;
}
return index;
}
int main(){
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
build(1, 1, n);
for(int i=1; i<=m; i++){
scanf("%d%d%d", &op, &x, &y);
x++, y++; //代码是1~n,题目是0~n-1
if(op == 1){
y--;
int l = erfen(x, 1);
if(l==-1) printf("Can not put any one.\n");
else {
int r = erfen(x, y);
printf("%d %d\n", l-1, r-1);
update(1, 1, n, l, r, 1);
}
update(1, 1, n, x, y, 1);
}else {
printf("%d\n", query(1, 1, n, x, y));
update(1, 1, n, x, y, 0);
}
}
printf("\n");
}
}
题目:Party(线段树+二分)——http://acm.hdu.edu.cn/showproblem.php?pid=6521
题意:
起初所有人互不认识,问每次让[l , r]内的人参加聚会之前[l, r]中互不认识的人的对数
思路:
本题最主要的一点是要把区间化成点。当给出更新 [l, r] 后,表示 [l, r] 内的人都相互认识,可以用 a[i] = j 表示从 i 到 j 的人都相互认识,初始 a[i] = i。可以发现任意时刻的a[]数组都是非减的。
更新时,将 [l, r]中小于 r 的数全更新为 r,由于数组是有序的,可以二分出第一个小于 r 的位置。
查询时,对于a[l..r] 中 的 a[i] >= r,对答案不产生贡献,对于a[i] < r 的,产生 r - a[i] 的贡献,同样而分出第一个小于 r 的位置。
也可用吉司机线段树——https://blog.youkuaiyun.com/LSD20164388/article/details/89413657,思想和上述相同,实现更简单,但每次区间更新时要跑到叶子节点。
对于下列样例:
6 3---1 2 3 4 5 6 初始. n = 6, m = 3
3 5---1 2 5 5 5 6
2 4---1 4 5 5 5 6
5 6---1 4 5 5 6 6
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#define lson rt<<1
#define rson rt<<1|1
#define LL long long
using namespace std;
const int maxn=5e5+5;
LL n, m, x, y, tr[maxn<<2], maxx[maxn<<2], sum[maxn<<2], lazy[maxn<<2];
void pushup(int rt){
sum[rt] = sum[lson] + sum[rson];
maxx[rt] = max(maxx[lson], maxx[rson]);
}
void pushdown(int rt, int len){
if(lazy[rt]) {
lazy[lson] = max(lazy[lson], lazy[rt]);
lazy[rson] = max(lazy[rson], lazy[rt]);
maxx[lson] = max(maxx[lson], maxx[rt]);
maxx[rson] = max(maxx[rson], maxx[rt]);
sum[lson] = maxx[lson] * (len-(len>>1));
sum[rson] = maxx[rson] * (len>>1);
lazy[rt] = 0;
}
}
void build(int rt, int l, int r){
lazy[rt] = 0;
if(l == r) {
sum[rt] = maxx[rt] = l;
return ;
}
int mid = l + r >> 1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(rt);
}
int getpos(int rt, int l, int r, int p){
pushdown(rt, r-l+1);
if(l == r) return maxx[rt];
int mid = l+r >> 1;
if(p <= mid) return getpos(lson, l, mid, p);
else return getpos(rson, mid+1, r, p);
}
void update(int rt, int l, int r, int L, int R, LL val){
if(L <= l && R >= r) {
maxx[rt] = max(maxx[rt], val);
sum[rt] = 1LL*(r-l+1)*maxx[rt];
lazy[rt] = max(lazy[rt], val);
return ;
}
pushdown(rt, r-l+1);
int mid = l+r >> 1;
if(L <= mid) update(lson, l, mid, L, R, val);
if(R > mid) update(rson, mid+1, r, L, R, val);
pushup(rt);
}
LL query(int rt, int l, int r, int L, int R){
if(L <= l && R >= r) return sum[rt];
pushdown(rt, r-l+1);
int mid = l+r >> 1; LL ret = 0;
if(L <= mid) ret += query(lson, l, mid, L, R);
if(R > mid) ret += query(rson, mid+1, r, L, R);
pushup(rt);
return ret;
}
int main()
{
while(~scanf("%lld%lld", &n, &m)){
build(1, 1, n);
for(int i=1; i<=m; i++){
scanf("%lld%lld", &x, &y);
LL l=1, r=n, p=0;
while(l <= r){
int mid = l+r >> 1;
if(getpos(1, 1, n, mid) < y)
p = mid, l = mid+1;
else r = mid - 1;
}
p = min(p, y);
if(x <= p){
printf("%lld\n", 1LL * (p - x + 1)*y - query(1, 1, n, x, p));
update(1, 1, n, x, p, y);
}else printf("0\n");
}
}
}
类型:线段树合并
题目:Hotel——https://vjudge.net/contest/285952#problem/D
题意:
N个房间1到N,M个操作。操作分两种,第一种是查询是否有足够的连续房间,若存在则返回最左边房间编号,并占用房间,否则返回0;第二种操作是清楚以x为起点长度为y的房间。
思路:
线段树的合并。最长连续区间长度问题,L[root]表示区间内从左到右的最长连续序列(本题是最长连续空房间)的长度,R[root]表示区间内从右到左的最长连续序列(最长连续空房间)的长度,a[root]表示区间内最长连续序列(最长连续空房间)的长度。更新时用到lazy标记,lazy=1表示空房间,lazy=0表示房间被占用,lazy=-1表示未被标记。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 5e4+5;
int a[maxn<<2], lazy[maxn<<2], L[maxn<<2], R[maxn<<2];
void pushup(int root, int l, int r)
{
L[root] = L[lson];
R[root] = R[rson];
a[root] = max(R[lson] + L[rson], max(a[lson], a[rson]));
int mid = (l+r)>>1;
if(a[lson] == mid-l+1) L[root] += L[rson];
if(a[rson] == r-(mid+1)+1) R[root] += R[lson];
}
void pushdown(int root, int l, int r)
{
if(lazy[root]!=-1){
lazy[lson] = lazy[rson] = lazy[root];
int mid = (l+r)>>1;
L[lson] = R[lson] = a[lson] = lazy[root]*(mid-l+1);
L[rson] = R[rson] = a[rson] = lazy[root]*(r-(mid+1)+1);
lazy[root] = -1;
}
}
void build(int root, int l, int r)
{
L[root] = R[root] = a[root] = (r-l+1);
lazy[root] = 1;
if(l==r) return ;
int mid = (l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
}
int query(int root, int l, int r, int len)
{
if(L[root] >= len) return l;
int mid = (l + r) >> 1;
if(a[lson] >= len) query(lson, l, mid, len);
else if(R[lson] + L[rson] >= len) return mid-R[lson]+1;
else query(rson, mid+1, r, len);
}
void update(int root, int l, int r, int L_, int R_, bool isclear)
{
if(L_ <= l && R_ >= r) {
if(isclear){
L[root] = R[root] = a[root] = (r-l+1);
lazy[root] = 1;
}else {
L[root] = R[root] = a[root] = 0;
lazy[root] = 0;
}
return ;
}
pushdown(root, l, r);
int mid = (l+r)>>1;
if(L_ <= mid) update(lson, l, mid, L_, R_, isclear);
if(R_ > mid) update(rson, mid+1, r, L_, R_, isclear);
pushup(root, l, r);
}
int main() {
int n, m, x, y, op;
scanf("%d%d", &n, &m);
build(1, 1, n);
for(int i=1; i<=m; i++){
scanf("%d", &op);
if(op==1) {
scanf("%d", &x);
if(a[1] < x) {printf("0\n"); continue;};
int ans=query(1, 1, n, x);
printf("%d\n", ans);
update(1, 1, n, ans, ans+x-1, 0);
}else {
scanf("%d%d", &x, &y);
update(1, 1, n, x, x+y-1, 1);
}
}
}
题目:Tunnel Warfare——http://acm.hdu.edu.cn/showproblem.php?pid=1540
题意:
n个点,每次摧毁一个或恢复上一个被摧毁的点,询问与一个点直接和间接相邻的点的个数(包括自身)。
思路:
单点修改,区间合并。对模板的修改主要在 query() 函数。询问位置x时,若x在node[rt].lm区域内,直接返回node[rt].lm,右侧同理,否则就处于中间,再进入左子树或右子树判断,若此时x在node[lson].rm区域内,就和node[rson].lm区域连上了,要返回两者之和;右子树的判断同理。判断 x 的位置时要注意。
题目有问题,数据是多组输入。注意每个村庄摧毁多次也要压栈。
代码:
#include <iostream>
#include <cstdio>
#include <stack>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;
struct Node
{
int lm, rm, mm;
}node[maxn << 2];
int n, m;
void pushup(int rt, int l, int r)
{
node[rt].lm = node[lson].lm;
node[rt].rm = node[rson].rm;
node[rt].mm = max(node[lson].rm+node[rson].lm, max(node[lson].mm, node[rson].mm));
int mid = (l+r)>>1;
if(node[rt].lm==mid-l+1) node[rt].lm += node[rson].lm;
if(node[rt].rm==r-(mid+1)+1) node[rt].rm += node[lson].rm;
}
void build(int rt, int l, int r)
{
node[rt].lm = node[rt].rm = node[rt].mm = r-l+1;
if(l == r) return ;
int mid = (l+r) >> 1;
build(lson, l, mid);
build(rson, mid+1, r);
}
void update(int rt, int l, int r, int x, bool flag)
{
if(l == r) {
if(flag) node[rt].lm = node[rt].rm = node[rt].mm = 1;
else node[rt].lm = node[rt].rm = node[rt].mm = 0;
return ;
}
int mid = (l+r) >> 1;
if(x <= mid) update(lson, l, mid, x, flag);
else update(rson, mid+1, r, x, flag);
pushup(rt, l, r);
}
int query(int rt, int l, int r, int x)
{
if(l==r) return node[rt].mm;
if(x <= node[rt].lm+l-1) return node[rt].lm;
if(x >= r-node[rt].rm+1) return node[rt].rm;
int mid = (l+r)>>1;
if(x <= mid){
if(x >= mid-node[lson].rm+1) return node[lson].rm+node[rson].lm;
return query(lson, l, mid, x);
}else {
if(x <= (mid+1)+node[rson].lm-1) return node[lson].rm+node[rson].lm; //注意是mid+1
return query(rson, mid+1, r, x);
}
}
stack<int>sta;
int main()
{
while(cin >> n >> m){
while(!sta.empty()) sta.pop();
build(1, 1, n);
char ch[2]; int x;
for(int i=1; i<=m; i++){
scanf("%s", ch);
if(ch[0] == 'D'){
scanf("%d", &x);
sta.push(x); //无论是否已被摧毁都要压栈
update(1, 1, n, x, false);
}else if(ch[0] == 'Q'){
scanf("%d", &x);
cout << query(1, 1, n, x) << endl;
}else {
if(!sta.empty()){
update(1, 1, n, sta.top(), true);
sta.pop();
}
}
}
}
}
题目:约会安排——http://acm.hdu.edu.cn/showproblem.php?pid=4553
题意:
为两个人寻找连续的 y 个空位,且两人的优先级不同。
思路:
和Hotel——https://vjudge.net/contest/285952#problem/D差不多,相当于有两个人且优先级不同,建两棵树即可,一棵DS树,一棵NS树,当DS时,在DS树中正常查找;当NS时,先在DS树中找,没找到再去NS树中找,更新时要同时更新两棵。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#define lson root<<1
#define rson root<<1|1
#define LL long long
using namespace std;
const int maxn = 1e5+5;
int a[maxn<<2][2], lazy[maxn<<2][2], L[maxn<<2][2], R[maxn<<2][2];
void pushup(int root, int l, int r, int flag)
{
L[root][flag] = L[lson][flag];
R[root][flag] = R[rson][flag];
a[root][flag] = max(R[lson][flag] + L[rson][flag], max(a[lson][flag], a[rson][flag]));
int mid = (l+r)>>1;
if(a[lson][flag] == mid-l+1) L[root][flag] += L[rson][flag];
if(a[rson][flag] == r-(mid+1)+1) R[root][flag] += R[lson][flag];
}
void pushdown(int root, int l, int r, int flag)
{
if(lazy[root][flag]!=-1){
lazy[lson][flag] = lazy[rson][flag] = lazy[root][flag];
int mid = (l+r)>>1;
L[lson][flag] = R[lson][flag] = a[lson][flag] = lazy[root][flag]*(mid-l+1);
L[rson][flag] = R[rson][flag] = a[rson][flag] = lazy[root][flag]*(r-(mid+1)+1);
lazy[root][flag] = -1;
}
}
void build(int root, int l, int r, int flag)
{
L[root][flag] = R[root][flag] = a[root][flag] = (r-l+1);
lazy[root][flag] = 1;
if(l==r) return ;
int mid = (l+r)>>1;
build(lson, l, mid, flag);
build(rson, mid+1, r, flag);
}
int query(int root, int l, int r, int len, int flag)
{
if(L[root][flag] >= len) return l;
int mid = (l + r) >> 1;
if(a[lson][flag] >= len) query(lson, l, mid, len, flag);
else if(R[lson][flag] + L[rson][flag] >= len) return mid-R[lson][flag]+1;
else query(rson, mid+1, r, len, flag);
}
void update(int root, int l, int r, int L_, int R_, bool isclear, int flag)
{
if(L_ <= l && R_ >= r) {
if(isclear){
L[root][flag] = R[root][flag] = a[root][flag] = (r-l+1);
lazy[root][flag] = 1;
}else {
L[root][flag] = R[root][flag] = a[root][flag] = 0;
lazy[root][flag] = 0;
}
return ;
}
pushdown(root, l, r, flag);
int mid = (l+r)>>1;
if(L_ <= mid) update(lson, l, mid, L_, R_, isclear, flag);
if(R_ > mid) update(rson, mid+1, r, L_, R_, isclear, flag);
pushup(root, l, r, flag);
}
int main() {
int t, n, m, Case=0;
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
build(1, 1, n, 0); build(1, 1, n, 1);
int x, y; char op[10];
printf("Case %d:\n", ++Case);
for(int i=1; i<=m; i++){
scanf("%s", op);
if(op[0] == 'D'){
scanf("%d", &x);
if(a[1][0] < x) {printf("fly with yourself\n"); continue;}
int l = query(1, 1, n, x, 0);
printf("%d,let's fly\n", l);
update(1, 1, n, l, l+x-1, 0, 0);
}else if(op[0] == 'N'){
scanf("%d", &x);
if(a[1][1]<x) {printf("wait for me\n"); continue;}
int l = query(1, 1, n, x, (a[1][0]<x));
printf("%d,don't put my gezi\n", l);
update(1, 1, n, l, l+x-1, 0, 1);
update(1, 1, n, l, l+x-1, 0, 0);
}else {
scanf("%d%d", &x, &y);
printf("I am the hope of chinese chengxuyuan!!\n");
update(1, 1, n, x, y, 1, 0);
update(1, 1, n, x, y, 1, 1);
}
}
}
}
类型:扫描线
题目:Picture——http://acm.hdu.edu.cn/showproblem.php?pid=1828
题意:
给出n个矩形的左下和右上点的坐标,求覆盖图形的周长
思路:
扫描线。第一种比较好理解,可以保存纵边和横边,上下扫一下再左右扫一下求和。第二种可以自己画图思考以下,可以只保存纵边或横边,维护区间内的不相交线段个数num,每次ans+=tree[1].num*2*(edge[i+1].h-edge[i].h)。
代码:
扫两次的:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;
struct Edge
{
int l, r, h, f;
bool operator < (const Edge& e) const {
return h==e.h ? f>e.f : h<e.h; //先排入边可以防止上边与下边重合时出错
}
}edge[2][maxn]; //0为横边,1为纵边
struct Tree
{
int sum, len;
}tree[maxn<<2];
int n, num_edge;
void addedge(int l, int r, int h, int f, int flag)
{
if(!flag) ++num_edge;
edge[flag][num_edge].l = l, edge[flag][num_edge].r = r;
edge[flag][num_edge].h = h; edge[flag][num_edge].f = f;
}
void pushup(int rt, int l, int r)
{
if(tree[rt].sum) tree[rt].len = r-l+1;
else if(l == r) tree[rt].len = 0;
else tree[rt].len = tree[lson].len + tree[rson].len;
/*意义相同,个人感觉更易理解和推广*/
// if(l == r){
// if(tree[rt].sum>=1) tree[rt].len = r-l+1;
// else tree[rt].len = 0;
// return ;
// }
//
// if(tree[rt].sum>=1) tree[rt].len = r-l+1;
// else tree[rt].len = tree[lson].len + tree[rson].len;
}
void update(int rt, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r){
tree[rt].sum += x;
pushup(rt, l, r);
return ;
}
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
pushup(rt, l, r);
}
int cal(int maxx, int minn, int flag)
{
int ans = 0, last = 0;
memset(tree, 0, sizeof(tree));
if(minn <= 0){
for(int i=1; i<=num_edge; i++){
edge[flag][i].l += -minn+1;
edge[flag][i].r += -minn+1;
}
maxx -= minn;
}
sort(edge[flag]+1, edge[flag]+num_edge+1);
for(int i=1; i<=num_edge; i++){
update(1, 1, maxx, edge[flag][i].l, edge[flag][i].r-1, edge[flag][i].f); //用点代表的边,所以要-1
while(edge[flag][i].h == edge[flag][i+1].h && edge[flag][i].f==edge[flag][i+1].f){ //重边
i++;
update(1, 1, maxx, edge[flag][i].l, edge[flag][i].r-1, edge[flag][i].f);
}
ans += abs(tree[1].len-last);
last = tree[1].len;
}
return ans;
}
int main()
{
while(~scanf("%d", &n)){
num_edge=0;
int maxx1 = -1e9, maxx2 = -1e9, minn1 = 1e9, minn2 = 1e9;
for(int i=1; i<=n; i++){
int x1, x2, y1, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
maxx1 = max(maxx1, x2); minn1 = min(minn1, x1);
maxx2 = max(maxx2, y2); minn2 = min(minn2, y1);
addedge(x1, x2, y1, 1, 0); //横边入
addedge(y1, y2, x1, 1, 1); //纵边入
addedge(x1, x2, y2, -1, 0); //横边出
addedge(y1, y2, x2, -1, 1); //纵边出
}
cout << cal(maxx1, minn1, 0)+cal(maxx2, minn2, 1) << endl;
}
}
扫一次的:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;
struct Edge
{
int l, r, h, f;
bool operator < (const Edge& e) const {
return h==e.h ? f>e.f : h<e.h; //先排入边可以防止上边与下边重合时出错
}
}edge[maxn];
struct Tree
{
int sum, num, len;
bool l, r;
}tree[maxn<<2];
int n, num_edge, ans, last;
void addedge(int l, int r, int h, int f)
{
edge[++num_edge].l = l, edge[num_edge].r = r;
edge[num_edge].h = h; edge[num_edge].f = f;
}
void pushup(int rt, int l, int r)
{
if(tree[rt].sum){
tree[rt].num = 1;
tree[rt].len = r-l+1;
tree[rt].l = tree[rt].r = true;
}else if(l == r){
tree[rt].num = 0;
tree[rt].len = 0;
tree[rt].l = tree[rt].r = false;
}else {
tree[rt].len = tree[lson].len + tree[rson].len;
tree[rt].num = tree[lson].num + tree[rson].num;
if(tree[lson].r && tree[rson].l) tree[rt].num--;
tree[rt].l = tree[lson].l;
tree[rt].r = tree[rson].r;
}
}
void update(int rt, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r){
tree[rt].sum += x;
pushup(rt, l, r);
return ;
}
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
pushup(rt, l, r);
}
int main()
{
while(~scanf("%d", &n)){
int maxx = -1e9, minn = 1e9;
num_edge=0, ans=0, last=0;
memset(tree, 0, sizeof(tree));
for(int i=1; i<=n; i++){
int x1, x2, y1, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
maxx = max(maxx, x2);
minn = min(minn, x1);
addedge(x1, x2, y1, 1);
addedge(x1, x2, y2, -1);
}
if(minn <= 0){ //调整坐标系,最左边调到 x=1
for(int i=1; i<=num_edge; i++) {
edge[i].l += -minn+1;
edge[i].r += -minn+1;
}
maxx -= minn;
}
sort(edge+1, edge+num_edge+1);
for(int i=1; i<=num_edge; i++){
update(1, 1, maxx, edge[i].l, edge[i].r-1, edge[i].f); //用点代表的边,所以要-1
while(edge[i].h == edge[i+1].h && edge[i].f==edge[i+1].f){ //重边
i++;
update(1, 1, maxx, edge[i].l, edge[i].r-1, edge[i].f);
}
ans += abs(tree[1].len-last);
last = tree[1].len;
ans += tree[1].num*2 * (edge[i+1].h-edge[i].h);
}
cout << ans << endl;
}
}
题目:Atlantis——http://acm.hdu.edu.cn/showproblem.php?pid=1542
题意:
给n个矩形的左上和右下点的坐标,求覆盖图形的面积。
思路:
若每个点都取整数的话和上一题一样,最后求答案的时候加的是tree[1].len*(edge[i+1].h-edge[i].h)。但题目给的是浮点数,所以需要进行离散化处理,本题的坐标点的取值还不算大,如果坐标点的取值很大但N的值不大也是用离散化处理。其余的就和上一题类似了。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e3+5;
struct Edge
{
double l, r, h; int f;
bool operator < (const Edge& e) const {
return h==e.h ? f>e.f : h<e.h; //先排入边可以防止上边与下边重合时出错
}
}edge[maxn];
struct Tree
{
int sum;
double len;
}tree[maxn<<2];
int n, num_edge;
double ans, x[maxn];
void addedge(double l, double r, double h, int f)
{
edge[++num_edge].l = l, edge[num_edge].r = r;
edge[num_edge].h = h; edge[num_edge].f = f;
}
void pushup(int rt, int l, int r)
{
if(tree[rt].sum) tree[rt].len = x[r+1]-x[l]; //点表示边
else if(l == r) tree[rt].len = 0;
else tree[rt].len = tree[lson].len + tree[rson].len;
}
void build(int rt, int l, int r)
{
tree[rt].len=tree[rt].sum=0;
if(l==r) return;
int mid=(l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(rt, l, r);
}
void update(int rt, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r){
tree[rt].sum += x;
pushup(rt, l, r);
return ;
}
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
pushup(rt, l, r);
}
int main()
{
int Case=0;
while(scanf("%d", &n) && n){
int cnt = 0; ans = 0, num_edge=0;
for(int i=1; i<=n; i++){
double x1, x2, y1, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
addedge(x1, x2, y2, 1);
addedge(x1, x2, y1, -1);
x[++cnt] = x1, x[++cnt] = x2;
}
sort(x+1, x+cnt+1), cnt = unique(x+1, x+cnt+1)-x-1; //去重+离散化
sort(edge+1, edge+num_edge+1);
build(1, 1, cnt);
for(int i=1; i<=num_edge; i++){
int l = lower_bound(x+1, x+cnt+1, edge[i].l)-x;
int r = lower_bound(x+1, x+cnt+1, edge[i].r)-x;
update(1, 1, cnt, l, r-1, edge[i].f); //用点表示边要-1
ans += tree[1].len * (edge[i+1].h-edge[i].h);
}
printf("Test case #%d\nTotal explored area: %.2f\n\n", ++Case, ans);
}
}
题目:覆盖的面积——http://acm.hdu.edu.cn/showproblem.php?pid=1255
题意:
给n个矩形的左下和右上点的坐标,求覆盖过两次的图形的面积
思路:
几乎和上题一样,上题是求覆盖的面积,变成覆盖两次或以上的。判断sum的值,如果>1就直接更新覆盖两次的长度,要注意的是当sum==1时,说明区间已经被覆盖,这时加上左右儿子被覆盖过一次的长度就是被覆盖两次的长度,其他维护和上题一样。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL long long
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
const int maxn = 1e5+5;
struct Edge
{
double l, r, h; int f;
bool operator < (const Edge& e) const {
return h==e.h ? f>e.f : h<e.h; //先排入边可以防止上边与下边重合时出错
}
}edge[maxn];
struct Tree
{
int sum;
double len, len2;
}tree[maxn<<2];
int n, num_edge;
double ans, x[maxn];
void addedge(double l, double r, double h, int f)
{
edge[++num_edge].l = l, edge[num_edge].r = r;
edge[num_edge].h = h; edge[num_edge].f = f;
}
void pushup(int rt, int l, int r)
{
if(tree[rt].sum) tree[rt].len = x[r+1]-x[l];
else if(l == r) tree[rt].len = 0;
else tree[rt].len = tree[lson].len + tree[rson].len;
if(tree[rt].sum>1) tree[rt].len2 = x[r+1]-x[l]; //维护覆盖两次或以上的
else if(l == r) tree[rt].len2 = 0;
else if(tree[rt].sum == 1) tree[rt].len2 = tree[lson].len + tree[rson].len;
else tree[rt].len2 = tree[lson].len2 + tree[rson].len2;
/*下方代码和上方意思一样,但我觉得更容易理解,向上推覆盖三次或更多次的思路比较清晰*/
// if(l == r){ //叶子节点
// if(tree[rt].sum >= 2){
// tree[rt].len2 = x[r+1]-x[l];
// tree[rt].len = 0;
// }else if(tree[rt].sum >= 1){
// tree[rt].len = x[r+1]-x[l];
// tree[rt].len2 = 0;
// }else tree[rt].len2 = tree[rt].len = 0;
// return ;
// }
//
// if(tree[rt].sum >= 2){ //非叶子节点
// tree[rt].len = 0;
// tree[rt].len2 = x[r+1]-x[l];
// }else if(tree[rt].sum >= 1){
// tree[rt].len2 = 0;
// tree[rt].len2 += (tree[lson].len2 + tree[rson].len2); //2+2>=2
// tree[rt].len2 += (tree[lson].len + tree[rson].len); //1+1>=2
// tree[rt].len = x[r+1]-x[l] - tree[rt].len2;
// }else {
// tree[rt].len2 = tree[lson].len2 + tree[rson].len2;
// tree[rt].len = tree[lson].len + tree[rson].len;
// }
}
void build(int rt, int l, int r)
{
tree[rt].len=tree[rt].len2=tree[rt].sum=0;
if(l==r) return;
int mid=(l+r)>>1;
build(lson, l, mid);
build(rson, mid+1, r);
pushup(rt, l, r);
}
void update(int rt, int l, int r, int L, int R, int x)
{
if(L <= l && R >= r){
tree[rt].sum += x;
pushup(rt, l, r);
return ;
}
int mid = (l+r)>>1;
if(L <= mid) update(lson, l, mid, L, R, x);
if(R > mid) update(rson, mid+1, r, L, R, x);
pushup(rt, l, r);
}
int main()
{
int t;
scanf("%d", &t);
while(t--){
scanf("%d", &n);
int cnt = 0; ans = 0, num_edge=0;
for(int i=1; i<=n; i++){
double x1, x2, y1, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
addedge(x1, x2, y1, 1);
addedge(x1, x2, y2, -1);
x[++cnt] = x1, x[++cnt] = x2;
}
sort(x+1, x+cnt+1), cnt = unique(x+1, x+cnt+1)-x-1;
sort(edge+1, edge+num_edge+1);
build(1, 1, cnt);
for(int i=1; i<=num_edge; i++){
int l = lower_bound(x+1, x+cnt+1, edge[i].l)-x;
int r = lower_bound(x+1, x+cnt+1, edge[i].r)-x;
update(1, 1, cnt, l, r-1, edge[i].f);
ans += tree[1].len2 * (edge[i+1].h-edge[i].h);
}
printf("%.2f\n", ans);
}
}
线段树动态开点:
题目:Physical Education Lessons——http://codeforces.com/problemset/problem/915/E
题意:长度为n(1e9)的数组,两种 q(3e5)个操作把区间 [l, r] 的所有数变成0或1,输出每次修改后数组中0的个数
思路:直接开1e9肯定是不行的,q在3e5之内,可以考虑把所有操作离散化。这里采用动态开点方式,即 即用即开,直接看代码也比较容易理解。
#include <bits/stdc++.h>
#define M(x) memset(x, 0, sizeof(x))
using namespace std;
const int maxn = 3e5+4;
int n, m, id, rt;
int lson[maxn*50], rson[maxn*50], sum[maxn*50], lazy[maxn*50];
void pushup(int rt){sum[rt] = sum[lson[rt]] + sum[rson[rt]];}
void pushdown(int rt, int l, int r)
{
if(lazy[rt] != -1){
int mid = l+r>>1;
if(l != r){
if(lson[rt]==0) lson[rt] = ++ id;
if(rson[rt]==0) rson[rt] = ++ id;
lazy[lson[rt]] = lazy[rt];
lazy[rson[rt]] = lazy[rt];
sum[lson[rt]] = (mid-l+1)*lazy[rt];
sum[rson[rt]] = (r-mid)*lazy[rt];
}
lazy[rt] = -1;
}
}
void update(int &rt, int l, int r, int L, int R, int val)
{
if(!rt) rt = ++ id; //即用即开
if(L <= l && R >= r){
sum[rt] = (r-l+1)*val;
lazy[rt] = val;
return ;
}
pushdown(rt, l, r);
int mid = l+r>>1;
if(L <= mid) update(lson[rt], l, mid, L, R, val);
if(R > mid) update(rson[rt], mid+1, r, L, R, val);
pushup(rt);
}
int main()
{
cin >> n >> m;
id = rt = 0;
for(int i=1; i<=m; i++){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
update(rt, 1, n, x, y, z==1);
printf("%d\n", n-sum[1]);
}
}