您的位置:首页 > 理论基础 > 数据结构算法

51nod 1206 1028 1494题解+扫描线模板

2016-09-07 18:41 393 查看

51nod 1206 1028 1494题解+扫描线模板

扫描线是线段树在几何方面的经典应用,主要用来解决线段交点个数,矩形面积并和矩形周长并问题。但是扫面线的应用绝不仅限于此,很多看起来跟几何关系不大的问题,往往通过巧妙的转化可以变成扫描线的经典问题,下面就给出三道扫描线的例题。

题目1:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1206

裸的矩形周长并问题,直接附上代码。

//#include <bits\stdc++.h>
//#pragma comment(linker, "/STACK:102400000,102400000")
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <algorithm>
#include <bitset>

#define sqr(x) ((x)*(x))
#define lson (p<<1)
#define rson (lson | 1)
#define EPS 1e-10
#define PII pair<int,int>
#define PLI pair<LL,int>
#define PLL pair<LL,LL>
#define PIL pair<int,LL>
#define mk(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
using namespace std;
template <class T>
inline void read(T &x){char c = getchar(); x = 0;while(!isdigit(c))c = getchar();while(isdigit(c)) { x = x * 10 + c-'0';c = getchar();}}
template <class T>
inline void rd(T &res) {static char ch; bool sgn = false;while (ch = getchar(), ch < '0' || ch > '9') if (ch == '-') sgn = true;
res = ch - 48;while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48; res = sgn ? -res : res;}
template <class T> void Out(T a) { if(a < 0){putchar('-');a = -a;}if(a >= 10)Out(a / 10);putchar(a % 10 + '0');  }
typedef long long LL;
const int N = 5e4+10;
LL sum[N<<3],seg[N<<3],lbd[N<<3],rbd[N<<3],tree[N<<3],py[N<<1];
// int sum[4*maxn];              ///记录被覆盖区间的长度
// int lbd[4*maxn], rbd[4*maxn]; ///记录左右端点是否被覆盖
// int numseg[4*maxn];            ///记录该区间连续的段数
// int flag[4*maxn];              /// 记录该区间是否被覆盖
struct Node
{
int x,y1,y2,flag;
bool operator <(Node s)
{
if(x == s.x) return flag > s.flag;
return x < s.x;
}
}line[N<<1];
void pushup(int p,int l,int r)
{
if(tree[p])
{
lbd[p] = rbd[p] = 1;
sum[p] = py[r] - py[l-1];
seg[p] = 2;
}else if(l==r) sum[p] = lbd[p] = rbd[p] = seg[p] = 0;
else
{
lbd[p] = lbd[lson];
rbd[p] = rbd[rson];
sum[p] = sum[lson] + sum[rson];
seg[p] = seg[lson] + seg[rson];
if(rbd[lson] && lbd[rson]) seg[p] -= 2;
}
}
void update(int p,int l,int r,int s,int t,int x)
{
if(s<=l && r<=t)
{
tree[p] += x;
pushup(p,l,r);
return;
}
int m = l + r >> 1;
if(s<=m) update(lson,l,m,s,t,x);
if(t>m) update(rson,m+1,r,s,t,x);
pushup(p,l,r);
}
int Hash(int x,int ty)
{
return lower_bound(py,py+ty,x) - py;
}
int main()
{
int n;
while(~scanf("%d",&n) )
{
int x1,x2,y1,y2,top = 0,ty = 0;
for(int i=0;i<n;i++)
{
rd(x1),rd(y1),rd(x2),rd(y2);
py[ty++] = y1;
py[ty++] = y2;
line[top++] = (Node){x1,y1,y2,1};
line[top++] = (Node){x2,y1,y2,-1};
}
sort(line,line+top);
sort(py,py+ty);
ty = unique(py,py+ty) - py;
LL ans=0, last=0;
for(int i=0;i<top;i++)
{
update(1,1,ty,Hash(line[i].y1,ty)+1,Hash(line[i].y2,ty),line[i].flag);
ans += seg[1]*(line[i+1].x - line[i].x);
ans += abs(sum[1]-last);
last = sum[1];
}
Out(ans),puts("");
}
}


题目2:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1208

询问矩形窗口中,可以包含的星星的亮度总值最大为多少。

初看题目以为是暴力,以每个节点为窗口中心或者边缘枚举。后来发现这种贪心思路不对,可能有星星在窗口的边缘,无法枚举到这种情况下的最优解。之后也是看了大神们的解题思路才发现这是扫描线的题目。

思路:将每个星星看成和窗口大小相同的矩形,把窗口看成点,发现只要按照扫描线的做法来,求每条扫描线上被矩形圈住的权值最大的点就行。有点逆向思维的感觉,因为窗口圈住星星事件和以星星为中心的矩形圈住窗口中心点事件等效,所以可以这么转化。具体操作的时候,以星星为中心弄矩形或者以星星为矩形的一个顶点弄矩形都可以,都是等效的,只不过是图形的整体偏移而已,不影响结果。

附上代码:

//#include <bits\stdc++.h>
//#pragma comment(linker, "/STACK:102400000,102400000")
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <algorithm>
#include <bitset>

#define sqr(x) ((x)*(x))
#define lson (p<<1)
#define rson (lson | 1)
#define EPS 1e-10
#define PII pair<int,int>
#define PLI pair<LL,int>
#define PLL pair<LL,LL>
#define PIL pair<int,LL>
#define mk(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
using namespace std;
template <class T>
inline void read(T &x){char c = getchar(); x = 0;while(!isdigit(c))c = getchar(); while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }}
template <class T>
inline void rd(T &res) {static char ch; bool sgn = false;while (ch = getchar(), ch < '0' || ch > '9') if (ch == '-') sgn = true;
res = ch - 48;while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48; res = sgn ? -res : res;}
template <class T> void Out(T a) { if(a < 0){putchar('-');a = -a;}if(a >= 10)Out(a / 10);putchar(a % 10 + '0');  }
typedef long long LL;
const int N = 2e5+10;
const int oo = 0x3f3f3f3f;
PII a
;
int n,w,h,px
,py
,tree[N<<2],add[N<<2];
struct Node
{
int x,l,r,id;
bool operator <(Node oth)
{
return x < oth.x;
}
}f
;
vector<Node>ve
;
map<int,int>ma,mb;
void putdown(int p)
{
tree[lson] += add[p];
tree[rson] += add[p];
add[lson] += add[p];
add[rson] += add[p];
add[p] = 0;
}
void update(int p,int l,int r,int s,int t,int val)
{
if(s<=l && r<=t)
{
add[p] += val;
tree[p] += val;
return;
}
if(add[p]) putdown(p);
int m = r + l >> 1;
if(s<=m) update(lson,l,m,s,t,val);
if(t>m) update(rson,m+1,r,s,t,val);
tree[p] = max(tree[lson],tree[rson]);
}
int main()
{
while(~scanf("%d",&n))
{
rd(w);rd(h);
w++;
int top = 0;
int xx = 0,yy = 0;
for(int i=1;i<=n;i++)
{
int x,y,z;
rd(x); rd(y);rd(z);
px[xx++] = x;
px[xx++] = x+w;
py[yy++] = y;
py[yy++] = y+h;
f[top++] = (Node){x,y,y+h,z};
f[top++] = (Node){x+w,y,y+h,-z};
}
sort(px,px+xx);
xx = unique(px,px+xx) - px;
sort(py,py+yy);
yy = unique(py,py+yy) - py;
ma.clear();
mb.clear();
for(int i=1;i<=xx;i++) ma[px[i-1] ] = i;
for(int i=1;i<=yy;i++) mb[py[i-1] ] = i;
memset(ve,0,sizeof(ve));
for(int i=0;i<top;i++)
{
int x = ma[f[i].x];
ve[x].push_back( (Node){x,mb[f[i].l],mb[f[i].r],f[i].id } );
}
int ans = 0;
memset(tree,0,sizeof(tree));
memset(add,0,sizeof(add));
for(int i=1;i<=xx;i++)
if(ve[i].size() )
{
for(int j=0;j<ve[i].size();j++)
{
update(1,1,yy,ve[i][j].l,ve[i][j].r,ve[i][j].id);
}
ans = max(ans,tree[1]);
}
Out(ans); puts("");
}
return 0;
}


题目3:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1494

上面的两道例题中都涉及了几何,矩形,还比较容易看出来是扫描线的题目。可是题目3就不那么容易看出来了。既然我把这道题目分到扫描线里,那线在哪里呢?

实际上,可以按照被选举人得票的数量从小到大排一下序,然后从小到大枚举自己的得票,所有得票大于自己的被选举人的投票人都需要被收买,剩下还不够的从小于等于自己得票的人中选前K小的(k等于少的票数),前K小可以用线段树来求。这个也算数线段树的巧妙应用了,因为线段树的完全二叉树结构决定了线段树上可以二分找到第K小的数值的下标(前提是线段树的叶子节点区间代表本身的数值,节点值等于这个数值的个数),这个下标以前的和可以直接统计出来。同时枚举过程就先一条扫描线,扫过所有被选举人票数的可能,得到的最优解就是答案。

附上代码:

//#include <bits\stdc++.h>
//#pragma comment(linker, "/STACK:102400000,102400000")
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <vector>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <algorithm>
#include <bitset>

#define sqr(x) (x)*(x)
#define lson (p<<1)
#define rson (lson | 1)
#define EPS 1e-10
#define PII pair<int,int>
#define PLI pair<LL,int>
#define PLL pair<LL,LL>
#define PIL pair<int,LL>
#define mk(x,y) make_pair(x,y)
#define lowbit(x) (x&(-x))
using namespace std;
template <class T>
inline void read(T &x){char c = getchar(); x = 0;while(!isdigit(c))c = getchar(); while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }}
template <class T>
inline void rd(T &res) {static char ch; bool sgn = false;while (ch = getchar(), ch < '0' || ch > '9') if (ch == '-') sgn = true;
res = ch - 48;while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48; res = sgn ? -res : res;}
template <class T> void Out(T a) { if(a < 0){putchar('-');a = -a;}if(a >= 10)Out(a / 10);putchar(a % 10 + '0');  }
typedef long long LL;
const int N = 1e5+10;
const int M = 1e4+10;
vector<int>ve
;
vector<int>f
;
int n,tree[M<<2],num[M<<2];
void update(int p,int l,int r,int x)
{
if(l == r)
{
num[p] ++;
tree[p] += l;
return;
}
int m = l + r >> 1;
if(x<=m) update(lson,l,m,x);
else update(rson,m+1,r,x);
tree[p] = tree[lson] + tree[rson];
num[p] = num[lson] + num[rson];
}
int get(int p,int l,int r,int x)
{
if(l == r)
{
return l*x;
}
int m = l + r >> 1;
if(x == num[lson]) return tree[lson];
if(x>num[lson]) return tree[lson] + get(rson,m+1,r,x-num[lson]);
else return get(lson,l,m,x);
}
int main()
{
while(~scanf("%d",&n) )
{
memset(ve,0,sizeof(ve));
memset(f,0,sizeof(f));
memset(tree,0,sizeof(tree));
memset(num,0,sizeof(num));
int ans = 0,mx = 1;
for(int i=1;i<=n;i++)
{
int x,y;
rd(x),rd(y);
if(y == 0) continue;
ans += y;
mx = max(mx,y);
ve[x].push_back(y);
}
for(int i=1;i<N;i++)
if(ve[i].size())
{
sort(ve[i].begin(),ve[i].end(),greater<int>() );
for(int j=0;j<ve[i].size();j++)
{
f[j].push_back(ve[i][j]);
}
}
int k = n;
int tot = ans;
for(int i=0;i<n;i++)
{
k -= f[i].size();
for(int j=0;j<f[i].size();j++)
update(1,1,mx,f[i][j]),tot-=f[i][j];
int temp = 0;
if(k<=i+1)
{
int tmp = min(i+2-k,n);
temp = get(1,1,mx,tmp);
}
ans = min(ans,tot + temp);
}
printf("%d\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息