對《挑戰程序設計競賽》的一個記錄
第三章 出類拔萃——中級篇
3.3活用各種數據結構——線段樹篇
下一篇:3.3活用各種數據結構——RMQ/樹狀數組/分桶法和平方分割
線段樹
主要還是看胡浩的文章 (完全版線段樹)
- 單點更新
以下代碼塊的頭文件”head.h” 的代碼如下,不再重複黏貼
#include "cstdlib"
#include "cctype"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "algorithm"
#include "vector"
#include "string"
#include "iostream"
#include "sstream"
#include "set"
#include "queue"
#include "stack"
#include "fstream"
#include "iomanip"
#include "bitset"
#include "list"
#include "strstream"
#include "ctime"
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
#define CC(m,what) memset(m,what,sizeof(m))
#define FOR(i,a,b) for( int i = (a) ; i < (b) ; i ++ )
#define FF(i,a) for( int i = 0 ; i < (a) ; i ++ )
#define FFD(i,a) for( int i = (a)-1 ; i >= 0 ; i --)
#define SS(a) scanf("%d",&a)
#define LL(a) ((a)<<1)
#define RR(a) (((a)<<1)+1)
#define SZ(a) ((int)a.size())
#define PP(n,m,a) puts("---");FF(i,n){FF(j,m)cout << a[i][j] << ' ';puts("");}
#define sf scanf
#define pf printf
const double eps = 1e-11;
const double Pi = acos(-1.0);
#define read freopen("in.txt","r",stdin)
#define write freopen("out.txt","w",stdout)
#define two(x) ((LL)1<<(x))
#define include(a,b) (((a)&(b))==(b))
template<class T> inline T countbit(T n) {return n?1+countbit(n&(n-1)):0;}
template<class T> inline T sqr(T a) {return a*a;}
template<class T> inline void checkmin(T &a,T b) {if(a == -1 || a > b)a = b;}
template<class T> inline void checkmax(T &a,T b) {if(a < b) a = b;}
int dx[] = {-1,0,1,0};//up Right down Left
int dy[] = {0,1,0,-1};
#include "head.h"
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1 | 1
const int Maxn = 50010;
int sum[Maxn<<2];
void PushUp(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];
}
void Build(int l,int r,int rt)
{
if(l == r)
{
sf("%d",&sum[rt]);
return;
}
int m = (l + r) / 2;
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];
}
int m = (l + r) / 2;
int ret = 0;
if(L <= m) ret += Query(L,R,lson);
if(m < R) ret += Query(L,R,rson);
return ret;
}
void Update(int p,int k,int l,int r,int rt)
{
if(l == r)
{
sum[rt] += k;
return;
}
int m = (l + r) / 2;
if(p <= m) Update(p,k,lson);
if(m < p) Update(p,k,rson);
PushUp(rt);
}
int main()
{
int T,n,l,r;
char s[10];
sf("%d",&T);
FOR(cas,1,T+1)
{
sf("%d",&n);
Build(1,n,1);
pf("Case %d:\n",cas);
while(sf("%s",s) && s[0] != 'E')
{
sf("%d%d",&l,&r);
if(s[0] == 'Q') pf("%d\n",Query(l,r,1,n,1));
else if(s[0] == 'A') Update(l,r,1,n,1);
else Update(l,-r,1,n,1);
}
}
return 0;
}
#include "head.h"
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1 | 1
const int Maxn = 200000;
int sum[Maxn<<2];
void PushUp(int rt)
{
sum[rt] = max(sum[rt<<1] ,sum[rt<<1 | 1]);
}
void Build(int l,int r,int rt)
{
if(l == r)
{
sf("%d",&sum[rt]);
return;
}
int m = (l + r) / 2;
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];
}
int m = (l + r) / 2;
int ret = 0;
if(L <= m) ret = max(ret,Query(L,R,lson));
if(m < R) ret = max(ret,Query(L,R,rson));
return ret;
}
void Update(int p,int k,int l,int r,int rt)
{
if(l == r)
{
sum[rt] = k;
return;
}
int m = (l + r) / 2;
if(p <= m) Update(p,k,lson);
if(m < p) Update(p,k,rson);
PushUp(rt);
}
int main()
{
int n,m,l,r;
char s[10];
while(~sf("%d%d",&n,&m))
{
Build(1,n,1);
FF(i,m)
{
sf("%s%d%d",s,&l,&r);
if(s[0] == 'Q') pf("%d\n",Query(l,r,1,n,1));
else Update(l,r,1,n,1);
}
}
return 0;
}
hdu 1394 Minimum Inversion Number
只要用線段樹先求出最初數列的最小逆序數,之後數列的逆序數可根據前一個數列的逆序數得到。
怎麼用線段樹求最小逆序數呢?
給出數列:1 3 6 9 0 8 5 7 4 2
線段樹的區間存儲的是位於該區間內的數出現過幾個。例如,當遍歷到0時,先檢查比0大的數1-9已經有幾個出現過了,Query(L,R,l,r,rt)即爲Query(1,9,0,9,1)。得到的個數即爲增加的逆序數個數。因此,每遍歷一個數,都先查一下比它大的數有幾個出現過了,然後更新當前數到對應的區間中。
初始數列的逆序數(ans)得到後,後續數列的逆序數比較好求了,例如樣例中後一個數列爲
3 6 9 0 8 5 7 4 2 1
此數列的逆序數個數爲:
ans = ans - 原來是逆序的(比x[i]小的) + 原來不是逆序(比x[i]大的)
= ans - x[ i ] + (n - x[ i ] - 1)
#include "head.h"
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1 | 1
const int Maxn = 5010;
int sum[Maxn<<2];
int a[Maxn];
void Build(int l,int r,int rt)
{
sum[rt] = 0;
if(l == r)
return ;
int m = (l + r) / 2;
Build(lson);
Build(rson);
}
int Query(int L,int R,int l,int r,int rt)
{
if(L <= l && r <= R)
{
return sum[rt];
}
int ret = 0;
int m = (l + r) / 2;
if(L <= m) ret += Query(L,R,lson);
if(m < R) ret += Query(L,R,rson);
return ret;
}
void Update(int p,int l,int r,int rt)
{
if(l == r)
{
sum[rt] ++;
return ;
}
int m = (l + r) / 2;
if(p <= m) Update(p,lson);
if(m < p) Update(p,rson);
sum[rt] ++;
}
int main()
{
int n;
while(~sf("%d",&n))
{
Build(0,n - 1 ,1);
int ans = 0;
FF(i,n)
{
sf("%d",&a[i]);
ans += Query(a[i] + 1,n - 1,0, n - 1, 1);//查找比當前值大的
Update(a[i],0, n - 1,1);//更新當前值到對應的區間
}
int Min = ans;
FF(i,n)
{
ans += n - 1 - 2 * a[i];
Min = min(Min,ans);
}
pf("%d\n",Min);
}
return 0;
}
hdu 2795 billboard
h*w的木板,每次放入
思路:求區間最大值,每次找到能放入木板的最左區間即可。
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn =200010;
int sum[Maxn<<2],h,w,n;
void PushUp(int rt)
{
sum[rt] = max(sum[rt << 1] , sum[rt << 1 | 1]);
}
void Build(int l,int r,int rt)
{
sum[rt] = w;
if(l == r) return;
int m = (l +r) / 2;
Build(lson);
Build(rson);
}
void Query(int key,int l,int r, int rt,int &flag)
{
if(key > sum[rt])
{
flag = -1;
return;
}
if(l == r)
{
flag = l;
sum[rt] -= key;
return;
}
int m = (l + r) / 2;
if(sum[rt<<1] >= key)
Query(key,lson,flag);
else if(sum[rt<<1 | 1] >= key)
Query(key,rson,flag);
PushUp(rt);
}
int main()
{
int key,flag;
while(~sf("%d%d%d",&h,&w,&n))
{
Build(1,n,1);
FF(i,n)
{
sf("%d",&key);
flag = 0;
Query(key,1,min(h,n),1,flag);
pf("%d\n",flag);
}
}
return 0;
}
POJ 2828 Buy Tickets
題意:排隊買票,買票的過程中一直有人在插隊,給出每個人要插入的位置和val值,求最終的隊伍中從前往後每個人的val。
思路:區間保存剩餘的空位置數。如果空位置數
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 200010;
int sum[Maxn << 2];
int val[Maxn];
int pos[Maxn][2];
void PushUp(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<< 1 | 1];
}
void Build(int l,int r,int rt)
{
if(l == r)
{
sum[rt] = 1;
return ;
}
int m = (l + r) / 2;
Build(lson);
Build(rson);
PushUp(rt);
}
void Update(int key,int p,int l,int r,int rt)
{
if(l == r)
{
sum[rt] --;
val[l] = p;
return;
}
int m = (l + r) / 2;
if(sum[rt<<1]>=key)
Update(key,p,lson);
else
Update(key - sum[rt<<1] ,p,rson);
PushUp(rt);
}
int main()
{
int n;
while(~sf("%d",&n))
{
Build(0,n - 1,1);
FF(i,n)
sf("%d%d",&pos[i][0],&pos[i][1]);
FFD(i,n)
Update(pos[i][0] + 1,pos[i][1],0,n - 1, 1);
FF(i,n)
pf("%d%c",val[i],i + 1 == n?'\n':' ');
}
return 0;
}
poj 2886 Who Gets the Most Candies?
題意:一羣小朋友順時針方向圍城一圈,每個人手裏都有一個值a,一開始從第k個人開始出局,下一個出局的由當前出局人手裏的值來決定。如果a爲正數,則順時針數a個數,第a個人出局,如果是負數,則逆時針數,直到遊戲結束。已知,當某人是第p個出局時,他將會達到val[p]的值,val[p]爲p的因子個數。求獲得因子個數最大的值是哪個人,並且這個人獲得的val值。
思路:線段樹區間保存該區間內未出局的人的個數。即求區間的和,一開始每個位置都爲1,出局一個人則爲0。
如果第k個人出局,他手上的值爲a。
當
當
因此,主要做法就是區間查詢加單點更新。
我是直接暴力求因子個數,來一個數求一次。也可以考慮用反素數的算法首先將n範圍內因子個數最大的那個值求出來,這樣就不用每次都算因子個數。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 500010;
int sum[Maxn << 2];
char s[Maxn][15];
int num[Maxn];
int prime[Maxn],flag[Maxn];
int cnt;
//初始化得到素數
void getPrime()
{
for(int i = 2;i < Maxn;i ++)
{
if(flag[i] == 0)
{
prime[cnt ++] = i;
for(int j = i * 2;j < Maxn;j += i)
flag[j] = 1;
}
}
}
//求因子個數
int getDivisor(int n)
{
int ans = 1;
for(int i = 0;prime[i] * prime[i] <= n;i ++)
{
int tmp = 0;
if(n % prime[i] == 0)
{
while(n % prime[i] == 0)
{
tmp ++;
n /= prime[i];
}
ans = ans * (tmp + 1);
}
}
if(n != 1)
ans *= 2;
return ans;
}
void PushUp(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<< 1 | 1];
}
void Build(int l,int r,int rt)
{
if(l == r)
{
sum[rt] = 1;
return ;
}
int m = (l + r) / 2;
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];
}
int m = (l + r) / 2;
int ret = 0;
if(L <= m) ret += Query(L,R,lson);
if(m < R) ret += Query(L,R,rson);
return ret;
}
int Update(int p,int l,int r,int rt)
{
if(l == r)
{
sum[rt] --;
return l;
}
int m = (l + r) / 2;
int ret ;
if(sum[rt<<1] >= p) ret = Update(p,lson);
else ret = Update(p - sum[rt<<1],rson);
PushUp(rt);
return ret;
}
int main()
{
getPrime();
int n,k;
while(~sf("%d%d",&n,&k))
{
Build(1,n ,1);
for(int i = 1;i <= n;i ++)
sf("%s%d",s[i],&num[i]);
int total = 0,Max,pos=k;
Update(k,1,n,1);
Max = getDivisor(1);
for(int i = n - 1;i > 0;i --)
{
if(num[k] < 0)//逆時針轉
{
total = (k <=1)?0:Query(1,k - 1,1,n,1);
if(total >= -num[k])k = total + num[k] + 1;//[1,k -1]中的個數>= |num[k]|,所以要出局的數在[1,k-1]中,具體位置爲k的值
else k =total + 1 + i + num[k];//[1,k -1]中的個數< |num[k]|,需要更新的位置爲k,在[k+1,n]區間中
}
else//順時針轉
{
total = (k >= n)?0:Query(k + 1,n,1,n,1);
if(total >= num[k]) k = i - total + num[k];
else k = num[k] - total;
}
k = (k % i) ;
if(k <= 0) k += i;
k = Update(k,1,n,1);//k位置的人出局,更新區間和
int tmp = getDivisor(n - i + 1);
if(tmp > Max)
Max = tmp,pos = k;
}
pf("%s %d\n",s[pos],Max);
}
return 0;
}
- 成段更新
hdu 1698 Just a Hook
題意:給定n,初始是1-n位置的值全爲1,給出Q個操作,每個操作x,y,z,表示將區間[x,y]的值全設爲z,z的取值爲1,2,3。求最後區間[1,n]的和。
思路:線段樹成段更新,區間保存和。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 100010;
int sum[Maxn << 2];
int col[Maxn << 2];
void PushUp(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];
}
void PushDown(int rt,int m)
{
if(col[rt])
{
col[rt<<1] = col[rt<<1 | 1] = col[rt];
sum[rt<<1] = (m - (m>>1)) * col[rt];
sum[rt<<1 | 1] = (m >> 1) * col[rt];
col[rt] = 0;
}
}
void Build(int l,int r,int rt)
{
col[rt] = 0;
if(l == r)
{
sum[rt] = 1;
return ;
}
int m = (l + r) / 2;
Build(lson);
Build(rson);
PushUp(rt);
}
void Update(int L,int R,int k,int l,int r,int rt)
{
if(L <= l && r <= R)
{
col[rt] = k;
sum[rt] = (r - l + 1) * k;
return;
}
PushDown(rt,r - l + 1);
int m = (l + r) / 2;
if(L <= m) Update(L,R,k,lson);
if(m < R) Update(L,R,k,rson);
PushUp(rt);
}
int main()
{
int T,n,Q,x,y,z;
sf("%d",&T);
FOR(cas,1,T+1)
{
sf("%d",&n);
Build(1,n,1);
sf("%d",&Q);
FF(i,Q)
{
sf("%d%d%d",&x,&y,&z);
Update(x,y,z,1,n,1);
}
pf("Case %d: The total value of the hook is %d.\n",cas,sum[1]);
}
return 0;
}
poj 3468 A Simple Problem with Integers
題意:給出n個數和Q個操作,操作如下:
C a b c:將[a, b]區間中的每個數加上c。
Q a b: 計算[a, b ]區間內的數值之和。
思路:線段樹區間更新,區間求和。col[]保存當前區間中每個數需要加的值。當遍歷到區間[a, b]時,如果還需往下遍歷,則先把[a,(a+b)/2]和[(a + b) / 2+1,b]區間中的col更新(加上[a,b]區間中的col)。由於是懶更新,因此在查詢時也需要更新col的值。
代碼如下:
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 100010;
LL sum[Maxn << 2];
LL col[Maxn << 2];
void PushUp(int rt)
{
sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];
}
void PushDown(int rt,int m)
{
if(col[rt])
{
col[rt<<1] += col[rt];
col[rt<<1 | 1] += col[rt];
sum[rt<<1] += (m - (m>>1)) * col[rt];
sum[rt<<1 | 1] += (m >> 1) * col[rt];
col[rt] = 0;
}
}
void Build(int l,int r,int rt)
{
col[rt] = 0;
if(l == r)
{
sf("%lld",&sum[rt]);
return ;
}
int m = (l + r) / 2;
Build(lson);
Build(rson);
PushUp(rt);
}
void Update(int L,int R,int k,int l,int r,int rt)
{
if(L <= l && r <= R)
{
col[rt] += k;
sum[rt] += (LL)(r - l + 1) * k;
return;
}
PushDown(rt,r - l + 1);
int m = (l + r) / 2;
if(L <= m) Update(L,R,k,lson);
if(m < R) Update(L,R,k,rson);
PushUp(rt);
}
LL Query(int L,int R,int l,int r,int rt)
{
if(L <= l && r <= R)
{
return sum[rt];
}
PushDown(rt,r - l + 1);
LL ret = 0;
int m = (l + r) >> 1;
if(L <= m) ret += Query(L,R,lson);
if(m < R) ret += Query(L,R,rson);
return ret;
}
int main()
{
int n,Q,x,y,z;
char s[5];
while(~sf("%d%d",&n,&Q))
{
Build(1,n,1);
FF(i,Q)
{
sf("%s%d%d",s,&x,&y);
if(s[0] == 'Q')
pf("%lld\n",Query(x,y,1,n,1));
else
{
sf("%d",&z);
Update(x,y,z,1,n,1);
}
}
}
return 0;
}
題意:在牆上貼海報,海報之間可以相互重疊,問最後能看到幾張海報。
思路:海報的區間範圍很大,但是海報總數不大,如果之間用海報的區間範圍會超時,超內存,所以進行離散化。離散化的時候要注意:
由於給定的是線段而不是點,因而需要注意重疊部分,普通的離散化會有問題:
例子1:[1,10],[1,4],[6,10]
例子2:[1,10],[1,4],[5,10]
例子1,2的離散化都爲[1,4],[1,2],[3,4],但是,例子1中線段1不會被完全覆蓋,例子2中線段1被完全覆蓋,兩者結果是不一樣的,所以要對離散化進行調整。在相鄰兩個數間距大於1時添加一個數。例如[1,2,6,10],調整成[1,2,5,6,9,10]。然後再用線段樹進行處理。這裏線段樹的區間只要保存最近更新的數是多少就好了。
代碼如下:
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 500010;
int col[Maxn << 2];
int s[Maxn],e[Maxn],x[Maxn];
set<int> res;
void PushDown(int rt,int m)
{
if(col[rt])
{
col[rt << 1] = col[rt << 1 | 1] = col[rt];
col[rt] = 0;
}
}
void Build(int l,int r,int rt)
{
col[rt] = 0;
if(l == r) return;
int m = (l +r) / 2;
Build(lson);
Build(rson);
}
void Update(int L,int R,int c,int l,int r,int rt)
{
if(L <= l && r <= R)
{
col[rt] = c;
return ;
}
PushDown(rt,col[rt]);
int m = (l + r) >> 1;
if(L <= m) Update(L,R,c,lson);
if(m < R) Update(L,R,c,rson);
}
void Query(int l,int r,int rt)
{
if(l == r)
{
if(col[rt] != 0)
res.insert(col[rt]);
return;
}
PushDown(rt,col[rt]);
int m = (l + r) >> 1;
Query(lson);
Query(rson);
}
int binarySort(int l,int r,int k)
{
while(l <= r)
{
int mid = (l + r) >> 1;
if(x[mid] < k)
l = mid + 1;
else if(x[mid] > k)
r = mid - 1;
else
return mid;
}
}
int main()
{
int T,n;
sf("%d",&T);
while(T --)
{
sf("%d",&n);
int cnt = 0,m = 1;
for(int i = 0;i < n;i ++)
{
sf("%d%d",&s[i],&e[i]);
x[cnt ++] = s[i];
x[cnt ++] = e[i];
}
sort(x,x + cnt);
for(int i = 1;i < cnt;i ++)
if(x[i] != x[i - 1]) x[m ++] = x[i];
for(int i = m - 1; i > 0;i --)
if(x[i] != x[i - 1] + 1) x[m ++] = x[i] - 1;
sort(x,x + m);
Build(0,m - 1,1);
for(int i = 0;i < n;i ++)
{
int l = binarySort(0,m - 1,s[i]);
int r = binarySort(0,m - 1,e[i]);
Update(l,r,i + 1,0,m - 1,1);
}
Query(0,m - 1, 1);
pf("%d\n",res.size());
res.clear();
}
return 0;
}
poj 3225 Help with Intervals
題意:給定一個空集,經過一系列集合操作後,求最後剩下的集合。注意集合裏的數是有理數,不是整數。因此[1,5]減去[3,3]後剩下的是[1,3)
集合操作如下:
U [a,b]:[a,b]之間的數都置1
D [a,b]:[a,b]之間的數都置0
S [a,b]:[a,b]之間的數取反,即異或1
C [a,b]:[a,b]之間的數取反,[
I [a,b]:保留[a,b]之間的數,[
思路:因爲這裏涉及到開區間和閉區間,所以可以把範圍擴大兩倍,例如[2,4]對應[4,8],(2,4]對應[5,8],[2,4)對應[4,7],(2,4)對應[3,7]。最後的結果也可根據位置的奇偶性來判斷是開區間還是閉區間。
該題注意a,b的範圍是包含0的,不要漏了這個點,還有update時L>R時不要再往下update,會RE。
由於給定的區間數據範圍不是很大,因此沒有去做離散化或者求出給定數據中的最大值來初始化線段樹,而是直接用了Maxn,這一部分繼續優化。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 200010;
int col[Maxn << 2];
int XOR[Maxn << 2];
int ans[Maxn];
void PushDown(int rt)
{
if(col[rt] != -1)
{
col[rt << 1] = col[rt << 1 | 1] = col[rt];
XOR[rt << 1] = XOR[rt << 1 | 1] = XOR[rt];
col[rt] = -1;
XOR[rt] = 0;
}
if(XOR[rt] == 1)
{
XOR[rt << 1] ^= XOR[rt];
XOR[rt << 1 | 1] ^= XOR[rt];
XOR[rt] = 0;
}
}
void Build(int l,int r,int rt)
{
col[rt] = -1;
XOR[rt] = 0;
if(l == r) return;
int m = (l + r) >> 1;
Build(lson);
Build(rson);
}
void Update(char op,int L,int R,int l,int r,int rt)
{
if(L > R) return;
if(L <= l && r <= R)
{
if(op == 'U')
col[rt] = 1,XOR[rt] = 0;
else if(op == 'D')
col[rt] = 0,XOR[rt] = 0;
else
XOR[rt] ^= 1;
return;
}
PushDown(rt);
int m = (l + r) >> 1;
if(L <= m) Update(op,L,R,lson);
if(m < R) Update(op,L,R,rson);
}
void Query(int l,int r,int rt)
{
if(col[rt] != -1 || l == r)
{
for(int i = l;i <= r;i ++)
{
ans[i] = col[rt];
if(XOR[rt] != 0)
ans[i] = ans[i] == -1?1:ans[i]^XOR[rt];
}
return;
}
PushDown(rt);
int m = (l + r) >> 1;
Query(lson);
Query(rson);
}
int main()
{
int a,b;
char op,l,r;
Build(0,Maxn,1);
while(~sf("%c %c%d,%d%c\n",&op,&l,&a,&b,&r))
{
a <<= 1;
b <<= 1;
if(l == '(')
a ++;
if(r == ')')
b --;
if(op == 'U' || op == 'D' || op == 'S')
Update(op,a,b,0,Maxn,1);
else
{
if(op == 'C')
Update('S',a,b,0,Maxn,1);
Update('D',0,a - 1,0,Maxn,1);
Update('D',b + 1,Maxn,0,Maxn,1);
}
}
Query(0,Maxn,1);
int f = 0;
for(int i = 0;i <Maxn;i ++)
{
if(ans[i] == 1)
{
f ++;
if(i & 1) l = '(';
else l = '[';
a = i >> 1;
for(int j = i + 1;j < Maxn;j ++)
{
if(ans[j] != 1)
{
i = j - 1;
if(i & 1) r = ')';
else r = ']';
b = (i + 1) >> 1;
if(f > 1) pf(" ");
pf("%c%d,%d%c",l,a,b,r);
break;
}
}
}
}
if(f == 0)
pf("empty set");
pf("\n");
return 0;
}
poj 1436 Horizontally Visible Segments
這題讓我掛了一天,原因是看錯題意了!!!!實在是對自己無語啊~~題意中兩條豎線是能被一條水平線段連接且改水平線段不經過其他豎線。注意這條水平選段是如下圖上半部分所示,而我之前理解的是水平直線,無限長,orz,真是智商捉急。。。
總之一句話:一定要看清題意啊。
題意:在一個平面內,有一些豎直的線段,若兩條豎直線段之間可以連一條水平線段,這條水平線段不與其他豎直線段相交,稱這兩條豎直線段爲“相互可見”的。若存在三條豎直線段,兩兩“相互可見”,則構成“線段三角形”。給出一些豎直的線段,問一共有多少“線段三角形”。
思路:區間覆蓋,線段樹區間保存該區間最近被哪條豎直線段覆蓋。將豎直線段按x值從小到大排序,在遍歷到一條豎直線段時,先查找一遍當前線段的區間範圍內有哪些線段存在。存在的那些線段與當前線段之間都符合題意,hash一下。然後再將當前線段更新上去。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 16010;
int col[Maxn << 2];
bool Hash[8010][8010];
struct node
{
int y1,y2,x;
};
node line[Maxn];
void PushDown(int rt)
{
if(col[rt])
{
col[rt << 1] = col[rt << 1 | 1] = col[rt];
col[rt] = 0;
}
}
void Build(int l,int r,int rt)
{
col[rt] = 0;
if(l == r) return ;
int m = (l + r) >> 1;
Build(lson);
Build(rson);
}
void Update(int L,int R,int l,int r,int rt,int cur)
{
if(L <= l && r <= R)
{
col[rt] = cur;
return;
}
PushDown(rt);
int m = (l + r) >> 1;
if(L <= m) Update(L,R,lson,cur);
if(m < R) Update(L,R,rson,cur);
}
void Query(int L,int R,int l,int r,int rt,int cur)
{
if(L <= l && r <= R)
{
if(col[rt] != 0)
{
Hash[col[rt]][cur] = true;
return;
}
}
if(l == r)
{
return;
}
PushDown(rt);
int m = (l + r) >> 1;
if(L <= m) Query(L,R,lson,cur);
if(m < R) Query(L,R,rson,cur);
}
int cmp(node a,node b)
{
return a.x < b.x;
}
int main()
{
int T,n,m;
sf("%d",&T);
while(T--)
{
sf("%d",&n);
m = 0;
for(int i = 1;i <= n;i ++)
{
sf("%d%d%d",&line[i].y1,&line[i].y2,&line[i].x);
line[i].y1 <<= 1;
line[i].y2 <<= 1;
m = max(m,line[i].y2);
}
Build(0,m,1);
sort(line + 1,line + n + 1,cmp);
for(int i = 1;i <= n;i ++)
{
Query(line[i].y1,line[i].y2,0,m,1,i);
Update(line[i].y1,line[i].y2,0,m,1,i);
}
int ans = 0;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(Hash[i][j])
for(int k=1;k<=n;++k){
if(Hash[i][k] && Hash[j][k])++ans;
}
}
}
pf("%d\n",ans);
memset(Hash,0,sizeof(Hash));
}
return 0;
}
題意:有一臺起重機。我們把起重機看成由N條線段依次首尾相接而成。第i條線段的長度是
有C條操縱起重機的指令。指令i給出兩個整數
按順序執行這C條指令。在每條指令執行之後,輸出起重機的前端(第N條線段的端點)的座標。假設起重機的支點座標是(0,0).
已知:
思路: 這題是看了題解才知道該怎麼解的。我們把每一小段都當成一個向量,端點的座標就是向量之和。
首先來看旋轉變化中點的座標變換:
線段樹區間需要保存角度的變換。每次PushDown時,先修正子區間的x,y的值,然後再加上變化的角度值。每次PushUp時,跟新根區間的x,y的值。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1 | 1
const int Maxn = 10010;
typedef struct Node
{
double x,y,col;
};
double ang[Maxn];
Node node[Maxn<<2];
void PushUp(int rt)
{
node[rt].x = node[rt << 1].x + node[rt << 1 | 1].x ;
node[rt].y = node[rt << 1].y + node[rt << 1 | 1].y ;
}
void Update_xy(int rt,double ang)
{
double d = ang * pi / 180;
double tmp_x = node[rt].x * cos(d) - node[rt].y * sin(d);
double tmp_y = node[rt].x * sin(d) + node[rt].y * cos(d);
node[rt].x = tmp_x;
node[rt].y = tmp_y;
}
void PushDown(int rt)
{
if(node[rt].col)
{
node[rt<<1].col += node[rt].col;
node[rt<<1 | 1].col += node[rt].col;
Update_xy(rt<<1,node[rt].col);
Update_xy(rt<<1|1,node[rt].col);
node[rt].col = 0;
}
}
void Build(int l,int r,int rt)
{
node[rt].col =0;
if(l == r)
{
sf("%lf",&node[rt].y);
node[rt].x = 0;
ang[l] = 180;
return ;
}
int m = (l + r) >> 1;
Build(lson);
Build(rson);
PushUp(rt);
}
void Update(int L,int R,int l,int r,int rt,double ang)
{
if(L <= l && r <= R)
{
node[rt].col += ang;
Update_xy(rt,ang);
return ;
}
PushDown(rt);
int m = (l + r) / 2;
if(L <= m) Update(L,R,lson,ang);
if(m < R) Update(L,R,rson,ang);
PushUp(rt);
}
int main()
{
int n,m;
int s,f = 0;
double t;
while(~sf("%d%d",&n,&m))
{
Build(1,n,1);
if(f > 0)
pf("\n");
f++;
ang[0] = 180;
for(int i = 0;i < m;i ++)
{
sf("%d%lf",&s,&t);
Update(s + 1,n,1,n,1,t - ang[s]);
ang[s] = t;
pf("%.2lf %.2lf\n",node[1].x,node[1].y);
}
}
return 0;
}
- 區間合併
poj 3667 Hotel
題意:訂房間。每次訂房間,若有x個人,就要分連續的x間房給他,如果有多種分法,就要從序號最小的開始分。
1 a:詢問是不是有連續長度爲a的空房間,若有,則住進最左邊。
2 a b:將[a,a+b-1]的房間清空。
思路:由於要查詢連續最長空位,因此區間需要記錄這個區間中的連續最長空位是(msum[rt])多少。這個最長空位怎麼計算?很明顯,一是來自左節點的最長空位(msum[rt<<1]),另外一個來自右節點的最長空位(msum[rt<<1|1]),還有一部分來自左節點的從右端開始最長可用空位(rsum[rt<<1])+右節點的從左端開始的最長可用空位(lsum[rt<<1|1])。
具體例子來說,例如
區間[1,5]對應1,0,0,01,則lsum[] = 0,rsum[] = 0 ,msum[]= 3
區間[1,5]對應1,0,0,0,0,則lsum[] = 0,rsum[] = 4 ,msum[]= 4
由上面的分析,可知區間中需要保存3個值,一個是從最左端開始可用的連續最長空位 lsum,一個是從右端開始可用的連續最長空位 rsum,還有一個區間中連續最長空位。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int Maxn = 50010;
int lsum[Maxn<<2],msum[Maxn<<2],rsum[Maxn<<2];
int col[Maxn<<2];
void PushUp(int rt,int m)
{
lsum[rt] = lsum[rt<<1] + ((lsum[rt<<1] == (m - (m>>1)))?lsum[rt<<1|1]:0);
rsum[rt] = rsum[rt<<1|1] + ((rsum[rt<<1|1] == (m>>1))?rsum[rt<<1]:0);
msum[rt] = max(rsum[rt<<1] + lsum[rt<<1|1],max(msum[rt<<1],msum[rt<<1|1]));
}
void PushDown(int rt)
{
if(col[rt])
{
lsum[rt<<1] = rsum[rt<<1] = msum[rt<<1] = lsum[rt] - (lsum[rt]>>1);
lsum[rt<<1|1] = rsum[rt<<1|1] = msum[rt<<1|1] = (lsum[rt]>>1);
col[rt<<1] = col[rt<<1|1] = col[rt];
col[rt] = 0;
}
}
void Build(int l,int r,int rt)
{
col[rt] = 0;
if(l == r)
{
lsum[rt] = rsum[rt] = msum[rt] = 1;
return;
}
int m = (l + r) >> 1;
Build(lson);
Build(rson);
PushUp(rt,r - l + 1);
}
void Update(int L,int R,int c,int l,int r,int rt)
{
if(L <= l && r <= R)
{
lsum[rt] = rsum[rt] = msum[rt] = (c == 0)?r-l+1:0;
col[rt] = 1;
return ;
}
PushDown(rt);
int m = (l + r) >> 1;
if(L <= m) Update(L,R,c,lson);
if(m < R) Update(L,R,c,rson);
PushUp(rt,r-l+1);
}
int Query(int c,int l,int r,int rt)
{
int m = (l + r) >> 1;
if(msum[rt] >= c)
{
if(lsum[rt] >= c) return l;
if(msum[rt<<1] >= c) Query(c,lson);
else if(rsum[rt<<1] + lsum[rt<<1|1] >=c && rsum[rt<<1] != 0) return m - rsum[rt<<1] + 1;
else if(msum[rt<<1|1] >=c) Query(c,rson);
}
else
return 0;
}
int main()
{
int n,m;
int c,x,y;
while(~sf("%d%d",&n,&m))
{
Build(1,n,1);
for(int i = 0;i < m;i ++)
{
sf("%d%d",&c,&x);
if(c == 1)
{
int ans = Query(x,1,n,1);
pf("%d\n",ans);
if(ans != 0) Update(ans,ans + x - 1,1,1,n,1);
}
else
{
sf("%d",&y);
Update(x,x + y - 1,0,1,n,1);
}
}
}
return 0;
}
hdu 3308 LCIS
題意:求給定區間中的最長連續上升子序列的長度。
思路:這題跟hotel有點相似,lsum[]保存區間從左到右最長上升子序列長度,rsum[]保存區間從右到左最長上升子序列長度,msum[]保存區間中最長上升子序列長度。
當左子樹的最右邊的值>=右子樹最左邊的的值時,
msum[rt] = max(msum[rt<<1],msum[rt<<1|1])
否則,兩邊還可以進行合併
msum[rt] = max(rsum[rt<<1]+lsum[rt<<1|1],max(msum[rt<<1],msum[rt<<1|1])
代碼如下:
#define "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1|1
const int Maxn = 100010;
int lsum[Maxn<<2],rsum[Maxn<<2],msum[Maxn<<2];
int x[Maxn];
int ans;
void PushUp(int rt,int l,int r)
{
lsum[rt] = lsum[rt<<1];
rsum[rt] = rsum[rt<<1|1];
msum[rt] = max(msum[rt<<1],msum[rt<<1|1]);
int m = (l + r) >> 1;
if(x[m] < x[m + 1])
{
if(lsum[rt<<1] == m - l + 1)
lsum[rt] += lsum[rt<<1|1];
if(rsum[rt<<1|1] == r - m)
rsum[rt] += rsum[rt<<1];
msum[rt] = max(msum[rt],rsum[rt<<1] + lsum[rt<<1|1]);
}
}
void Build(int l,int r,int rt)
{
if(l == r)
{
sf("%d",&x[l]);
lsum[rt] = rsum[rt] = msum[rt] = 1;
return;
}
int m = (l + r) >> 1;
Build(lson);
Build(rson);
PushUp(rt,l,r);
}
void Update(int p,int c,int l,int r,int rt)
{
if(l == r)
{
x[p] = c;
return ;
}
int m = (l + r) >> 1;
if(p<=m) Update(p,c,lson);
if(p>m) Update(p,c,rson);
PushUp(rt,l,r);
}
int Query(int L,int R,int l,int r,int rt)
{
if(L<= l && r<= R)
{
return msum[rt];
}
int ret= 0;
int m = (l + r) >> 1;
if(L <= m) ret = max(ret,Query(L,R,lson));
if(m < R) ret = max(ret,Query(L,R,rson));
if(x[m] < x[m + 1])
ret = max( ret, min( m - L + 1, rsum[rt<<1]) + min(R - m, lsum[rt<<1|1]) );
return ret;
}
int main()
{
int T,n,m,x,y;
char s[5];
sf("%d",&T);
while(T--)
{
sf("%d%d",&n,&m);
Build(0,n - 1,1);
while(m --)
{
sf("%s%d%d",s,&x,&y);
if(s[0]=='Q')
{
ans = Query(x,y,0,n - 1, 1);
pf("%d\n",ans);
}
else
Update(x,y,0,n - 1,1);
}
}
return 0;
}
hdu 3397 Sequence operation
這題就是前兩題的綜合。寫的我要噁心了。。。
題意:中文題。。
思路:關於區間置1和區間置0跟hotel那題差不多,主要考慮區間置異或。例如[0 0 1 0 0],此時lsum[] = 0,rsum[] = 0,異或的時候各個數取反,爲[1 1 0 1 1],lsum[] = 2,rsum[] = 2。因此一開始只保留區間內連續爲1的子串的長度是不夠的,還要保留區間內連續爲0的子串的長度。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1|1
#define mid int m = (l + r) >> 1
const int Maxn = 100010;
int sum[Maxn<<2],lsum[Maxn<<2],rsum[Maxn<<2],msum[Maxn<<2],col[Maxn<<2];//維護1
int revlsum[Maxn<<2],revrsum[Maxn<<2],revmsum[Maxn<<2];//維護0
void PushUp(int rt,int l,int r)
{
sum[rt] = sum[rt<<1] + sum[rt<<1|1];
lsum[rt] = lsum[rt<<1];
rsum[rt] = rsum[rt<<1|1];
msum[rt] = max(msum[rt<<1],msum[rt<<1|1]);
revlsum[rt] = revlsum[rt<<1];
revrsum[rt] = revrsum[rt<<1|1];
revmsum[rt] = max(revmsum[rt<<1],revmsum[rt<<1|1]);
mid;
if(lsum[rt<<1] == m - l + 1 && lsum[rt<<1|1] !=0) lsum[rt] += lsum[rt<<1|1];
if(rsum[rt<<1|1] == r - m && rsum[rt<<1] != 0) rsum[rt] += rsum[rt<<1];
if(rsum[rt<<1] > 0 && lsum[rt<<1|1] > 0)
msum[rt] = max(msum[rt],rsum[rt<<1] + lsum[rt<<1|1]);
if(revlsum[rt<<1] == m - l + 1 && revlsum[rt<<1|1] != 0) revlsum[rt] += revlsum[rt<<1|1];
if(revrsum[rt<<1|1] == r - m && revrsum[rt<<1] != 0) revrsum[rt] += revrsum[rt<<1];
if(revrsum[rt<<1] > 0 && revlsum[rt<<1|1] > 0)
revmsum[rt] = max(revmsum[rt],revrsum[rt<<1] + revlsum[rt<<1|1]);
}
void solved(int rt,int c,int m) //如果是異或操作
{
if(c == 1)//如果原來區間col[rt]= 1,則異或操作後原來區間內的值全置0
{
lsum[rt] = rsum[rt] = msum[rt] = sum[rt] = 0;
revlsum[rt] = revrsum[rt] = revmsum[rt] = m;
col[rt] = 0;
}
else if (c == 0)//如果原來區間col[rt]= 0,則異或操作後原來區間內的值全置1
{
lsum[rt] = rsum[rt] = msum[rt] = sum[rt] = m;
revlsum[rt] = revrsum[rt] = revmsum[rt] = 0;
col[rt] = 1;
}
else//如果原來區間並非全置0或置1過,則各個數取反,維護1的最長子串跟維護2的最長子串需要調換
{
col[rt] = col[rt] == 2?-1:2;
sum[rt] = m - sum[rt];
swap(lsum[rt],revlsum[rt]);
swap(rsum[rt],revrsum[rt]);
swap(msum[rt],revmsum[rt]);
}
}
void PushDown(int rt,int l,int r)
{
if(col[rt] != -1)
{
mid;
if(col[rt] != 2)
{
sum[rt<<1] = col[rt]==1?(m - l + 1):0;
sum[rt<<1|1] = col[rt] == 1? (r - m):0;
lsum[rt<<1] = rsum[rt<<1] = msum[rt<<1] = sum[rt<<1];
lsum[rt<<1|1] = rsum[rt<<1|1] = msum[rt<<1|1] = sum[rt<<1|1];
revlsum[rt<<1] = revrsum[rt<<1] = revmsum[rt<<1] = m - l + 1 -sum[rt<<1];
revlsum[rt<<1|1] = revrsum[rt<<1|1] = revmsum[rt<<1|1] = r - m - sum[rt<<1|1];
col[rt<<1] = col[rt<<1|1] = col[rt];
}
else
{
solved(rt<<1,col[rt<<1],m - l + 1);
solved(rt<<1|1,col[rt<<1|1],r - m);
}
col[rt] = -1;
}
}
void Build(int l,int r,int rt)
{
sum[rt] = 0;
col[rt] = -1;
if(l == r)
{
sf("%d",&sum[rt]);
lsum[rt] = rsum[rt] = msum[rt] = sum[rt];
revlsum[rt] = revrsum[rt] = revmsum[rt] = sum[rt] ^ 1;
return ;
}
mid;
Build(lson);
Build(rson);
PushUp(rt,l,r);
}
void Update(int L,int R,int c,int l,int r,int rt)
{
if(L <= l && r <= R)
{
if(c != 2)
{
sum[rt] = c == 1?r - l + 1:0;
lsum[rt] = rsum[rt] = msum[rt] = sum[rt];
revlsum[rt] = revrsum[rt] = revmsum[rt] = r - l + 1 - sum[rt];
col[rt] = c;
}
else
{
solved(rt,col[rt],r - l + 1);
}
return ;
}
mid;
PushDown(rt,l,r);
if(L <= m) Update(L,R,c,lson);
if(m < R) Update(L,R,c,rson);
PushUp(rt,l,r);
}
int Query(int L,int R,int l,int r,int rt)
{
if(L <= l && r <= R)
{
return sum[rt];
}
PushDown(rt,l,r);
int ret = 0;
mid;
if(L <= m) ret += Query(L,R,lson);
if(m < R) ret += Query(L,R,rson);
return ret;
}
int Query1(int L,int R,int l,int r,int rt)
{
if(L <= l && r <= R)
{
return msum[rt];
}
mid;
PushDown(rt,l,r);
int ret = 0;
if(L <= m) ret = max(ret,Query1(L,R,lson));
if(m < R) ret = max(ret,Query1(L,R,rson));
if(rsum[rt<<1] > 0 && lsum[rt<<1|1] > 0)
ret = max(ret,min(rsum[rt<<1],m - L + 1) + min(lsum[rt<<1|1],R-m));
return ret;
}
int main()
{
int T,n,m,c,x,y;
sf("%d",&T);
while(T--)
{
sf("%d%d",&n,&m);
Build(0,n - 1, 1);
for(int i = 0;i < m;i ++)
{
sf("%d%d%d",&c,&x,&y);
if(c <= 2) Update(x,y,c,0,n - 1,1);
else if(c == 3) pf("%d\n",Query(x,y,0,n - 1,1));
else pf("%d\n",Query1(x,y,0,n - 1,1));
}
}
return 0;
}
- 掃描線
hdu 1542 Atlantis
題意:有一些長方形(可重疊),計算被長方形覆蓋到的面積。
思路:掃描線,可參考這個博客
要注意的是線段樹中的節點不是指某個點的值,而是一段線段的長度。比如區間[0,2]表示線段端點爲0到3的線段長度。
因爲長方形個數不多,而頂點值的範圍比較大,可以考慮離散化,節省很多空間。
代碼如下:
#include "head.h"
#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1|1
#define mid int m = (l + r) >> 1
const int Maxn =250;
struct seg
{
double l,r,h;
int f;
seg(){}
seg(double a,double b,double c,int d):l(a),r(b),h(c),f(d){}
bool operator < (const seg &cmp) const {
return h < cmp.h;
}
}ss[Maxn];
int col[Maxn<<2];
double sum[Maxn<<2], p[Maxn];//p用來存頂點出現過的值,用來離散化的時候查找
int Binary(double key,int l,int r)//查找值所在位置,代表離散化的值
{
while(l <= r)
{
int m = (l + r) >> 1;
if(key == p[m]) return m;
else if(key < p[m]) r = m - 1;
else l = m + 1;
}
return -1;
}
void PushUp(int rt,int l,int r)
{
if(col[rt]) sum[rt] = p[r + 1] - p[l];
else if(l == r) sum[rt] = 0;
else sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];
}
void Update(int L,int R,int c,int l,int r,int rt)
{
if(L <= l && r <= R)
{
col[rt] += c;
PushUp(rt,l,r);
return ;
}
mid;
if(L <= m) Update(L,R,c,lson);
if(m < R) Update(L,R,c,rson);
PushUp(rt,l,r);
}
int main()
{
int n,m;
double x1,x2,y1,y2;
int cas = 1;
while(~sf("%d",&n) && n)
{
m = 0;
for(int i = 0;i < n;i ++)
{
sf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
p[m] = x1;
ss[m++] = seg(x1,x2,y1,1);
p[m] = x2;
ss[m++] = seg(x1,x2,y2,-1);
}
sort(p,p+m);
sort(ss,ss + m);
int k = 1;
for(int i = 1;i < m;i ++)//去重
if(p[i] != p[i - 1]) p[k++] = p[i];
memset(sum,0,sizeof(sum));
memset(col,0,sizeof(col));
double ans = 0;
for(int i = 0;i < m - 1;i ++)
{
int l = Binary(ss[i].l,0,k - 1);
int r = Binary(ss[i].r,0,k - 1) - 1;//因爲每個點代表的是一段距離,因此[l,r]線段長度可以用線段樹[l,r-1]區間去存
if(l <= r) Update(l,r,ss[i].f,0,k - 1,1);
ans += (ss[i + 1].h - ss[i].h) * sum[1];
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++ , ans);
}
return 0;
}