|
题目链接:http://poj.org/problem?id=2777
/* 题意: 给定一个长度为N(N <= 100000)的数列Si,紧接着Q(Q <= 100000)条操作,操作 形式有两种: 1. "C A B C" 将A到B的数都染成C这种颜色。 2. "P A B" 输出A和B之间不同颜色的数目。
解法: 线段树(染色问题)
思路: 一看到数据量就可以首先确定是线段树了,经典的区间染色问题,涉及到区间的 更新和询问,和pku 3468 类似,巧妙运用lazy思想。就是每次更新区间段的时候延迟 更新,只是在完全覆盖的区间打上一个lazy标记。这题的询问是求区间段中不同颜色的 数量,因为颜色数不多只有30种,可以巧妙运用二进制位运算,用一个int就可以表示 当前区间段的颜色情况。比如1001表示有两种颜色,如果左子树的当前颜色情况是101 ,而右子树的颜色情况是011,那么父亲的颜色情况就是两者的位或,这样就可以避免 掉重复的情况。 再来谈谈lazy思想。做了这么多的线段树,应该总结一下,lazy是一个很经典的思 想。所谓lazy,就是懒惰,每次不想做太多,只要插入的区间完全覆盖了当前结点所管 理的区间就不再往下做了,在当前结点上打上一个lazy标记,然后直接返回。下次如果 遇到当前结点有lazy标记的话,直接传递给两个儿子,自己的标记清空。这样做肯定是 正确的。我们以染色为例,可以这样想,如果当前结点和它的子孙都有lazy标记的话, 必定是子孙的先标记,因为如果是自己先标记,那么在访问子孙的时候,必定会将自己 的标记下传给儿子,而自己的标记必定会清空,那么lazy标记也就不存在了。所以可以 肯定,当前的lazy标记必定覆盖了子孙的,所以直接下传即可,不需要做任何判断。当 然,这是染色问题,是直接赋值的,如果像pku 3468那样,每次是区间加和,则传递标 记的时候不能简单的赋值,必须累加,这是显而易见的。 *//* lazy思想 染色模型 适合颜色数目较少(64以内)的区间染色问题。 具体操作: 1、对某个连续区间进行染色。 2、询问某个连续区间的颜色情况(种类、数目等等)。 适用题型: poj 2777 Count Color hdu 5023 A Corrupt Mayor's Performance Art 结点存储 颜色值的位或colorBit:每个颜色用2的幂来表示,颜色值表示分别为1、2、4、8,该区间有哪些颜色就可以用他们的或来表示 延迟标记lazy:该段区间完全被染色成了lazy这种颜色,这里的lazy要么是2的幂,要么是0
接口说明 giveLazyToSon 传递延迟标记给两个子结点(调用子结点的updateByValue,并且lazy重置) updateByValue 通过某个颜色值更新当前结点信息(更新colorBit、lazy) updateFromSon 通过两个子结点更新当前结点信息(更新colorBit) mergeQuery 询问时用于对分割后的子结点进行合并用,不同情况实现不同
调用说明 建树: 调用静态函数 treeNode::segtree_build(1, 1, n); 插入([x, y], val): 调用静态函数 treeNode::segtree_insert(1, 1, n, x, y, val); 询问([x, y]): 调用静态函数 treeNode::segtree_query(1, 1, n, x, y, ans);
*/ #include <iostream>using namespace std;#define MAXN 131072typedef int ValueType;// 返回[l, r]和[x, y]两条线段是否相交 bool is_intersect(int l, int r, int x, int y) { return !(r < x || l > y);}// 返回[x, y]是否完全包含[l, r] bool is_contain(int l, int r, int x, int y) { return x <= l && r <= y;}struct treeNode { ValueType lazy; ValueType colorBit; int pid; int len; treeNode() { reset(-1, 0); } void reset(int p, int _len) { pid = p; colorBit = 0; lazy = 0; len = _len; } int lson() { return pid << 1; } int rson() { return pid<<1|1; } void updateByValue(ValueType _val); void giveLazyToSon(); void updateFromSon(); // 询问的时候将结点合并后计入答案 void mergeQuery(int p); // 建树 static void segtree_build(int p, int l, int r); // 插入线段[x, y]到[l, r] static void segtree_insert(int p, int l, int r, int x, int y, ValueType val); // 区间询问[x, y]上的信息 static void segtree_query(int p, int l, int r, int x, int y, treeNode& ans);};/* 全局变量 nodes[MAXN*2] 存储所有静态线段树结点(动态开内存太费时间) totalNodes 存储结点数目 */treeNode nodes[MAXN*2];void treeNode::updateByValue(ValueType _val) { lazy = _val; colorBit = _val;}void treeNode::giveLazyToSon() { if(lazy) { nodes[ lson() ].updateByValue(lazy); nodes[ rson() ].updateByValue(lazy); lazy = 0; }}void treeNode::updateFromSon() { colorBit = nodes[ lson() ].colorBit | nodes[ rson() ].colorBit;}void treeNode::mergeQuery(int p) { colorBit |= nodes[p].colorBit;}void treeNode::segtree_build(int p, int l, int r) { // 创建线段树结点的时候只需要知道该线段树结点管辖区间的长度,区间端点不用存,可以在递归的时候作为函数传参 nodes[p].reset(p, r-l+1); if (l < r) { int mid = (l + r) >> 1; // 递归创建左右儿子结点 treeNode::segtree_build(p<<1, l, mid); treeNode::segtree_build(p<<1|1, mid+1, r); nodes[p].updateFromSon(); }}void treeNode::segtree_insert(int p, int l, int r, int x, int y, ValueType val) { if( !is_intersect(l, r, x, y) ) { return ; } if( is_contain(l, r, x, y) ) { nodes[p].updateByValue(val); return ; } nodes[p].giveLazyToSon(); int mid = (l + r) >> 1; treeNode::segtree_insert(p<<1, l, mid, x, y, val); treeNode::segtree_insert(p<<1|1, mid+1, r, x, y, val); nodes[p].updateFromSon();}void treeNode::segtree_query(int p, int l, int r, int x, int y, treeNode& ans) { if( !is_intersect(l, r, x, y) ) { return ; } if( is_contain(l, r, x, y) ) { ans.mergeQuery(p); return; } nodes[p].giveLazyToSon(); int mid = (l + r) >> 1; treeNode::segtree_query(p<<1, l, mid, x, y, ans); treeNode::segtree_query(p<<1|1, mid+1, r, x, y, ans); nodes[p].updateFromSon();} int n, t, o;int main() { int i; while( scanf("%d %d %d", &n, &t, &o) != EOF ) { treeNode::segtree_build(1, 1, n); for(i = 1; i <= n; i++) { treeNode::segtree_insert(1, 1, n, i, i, 1<<0); } while(o--) { char str[10]; int x, y, z; scanf("%s", str); if(str[0] == 'C') { scanf("%d %d %d", &x, &y, &z); if(x > y) swap(x, y); treeNode::segtree_insert(1, 1, n, x, y, 1<<(z-1) ); }else { scanf("%d %d", &x, &y); if(x > y) swap(x, y); treeNode ans; treeNode::segtree_query(1, 1, n, x, y, ans); z = 0; for(i = 0; i < t; i++) { if( (1<<i) & ans.colorBit ) { z ++; } } printf("%d\n", z); } } } return 0;}/* 2 2 4 C 1 1 2 P 1 2 C 2 2 2 P 1 2 */
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3349
/**//* 题意: 给定一个d(0 <= d <= 10^8)和(N <= 10^5)的数列,求最长的特殊子序列, 所谓特殊子序列就是相邻元素之间的绝对值之差小于等于d。
解法: 动态规划+线段树
思路: 这题又是一个动态规划,状态转移方程很容易想到: dp[ val[i] ] = 1 + max( dp[ val[i] - d ] dp[ val[i] + d ] ) dp[j] 表示以j为止的最长特殊子序列的值,这样就可以维护一个区间,每次 查询和当前数绝对值差小于等于d的数组成的区间,将最大值+1更新到当前数 对应的位置上,利用线段树每次更新和查询都是log(n)。 */
#include <iostream> #include <cstdio> #include <cstring> using namespace std;
#define maxn 600010
int n, d; int val[maxn];
struct Tree { int Max; int son[2];
void clear() { son[0] = son[1] = -1; Max = 0; } }T[maxn*4]; int tot;
int Max(int a, int b) { return a > b ? a : b; } int Min(int a, int b) { return a < b ? a : b; }
void Query(int root, int nx, int ny, int l, int r, int& ans) { if(nx > r || ny < l || root == -1 || T[root].Max <= ans) return ; if(nx <= l && r <= ny) { ans = Max(ans, T[root].Max); return ; } int mid = (l + r) >> 1; Query(T[root].son[0], nx, ny, l, mid, ans); Query(T[root].son[1], nx, ny, mid+1, r, ans); }
void Insert(int& root, int nPos, int l, int r, int val) { if(nPos < l || nPos > r) return ; if(root == -1) { root = tot++; T[root].clear(); } T[root].Max = Max(val, T[root].Max);
if(l == nPos && nPos == r) { return ; }
int mid = (l + r) >> 1; Insert(T[root].son[0], nPos, l, mid, val); Insert(T[root].son[1], nPos, mid+1, r, val); }
int main() { int i; int MMin, MMax; while(scanf("%d %d", &n, &d) != EOF) { for(i = 0; i < n; i++) { scanf("%d", &val[i]); if(i) { MMin = Min(MMin, val[i]); MMax = Max(MMax, val[i]); }else { MMin = val[i]; MMax = val[i]; } } tot = 0; int root = -1; int ans = 1;
for(i = 0; i < n; i++) { int l = (val[i] - d) < MMin ? MMin : (val[i] - d); int r = (val[i] + d) > MMax ? MMax : (val[i] + d); int MM = 0; Query(root, l, r, MMin, MMax, MM); Insert(root, val[i], MMin, MMax, MM + 1); if(MM + 1 > ans) ans = MM + 1; }
printf("%d\n", ans); } return 0; }
摘要: 题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1451
/**//*题意: 有这样一个机器Maximizer,它的输入是N(N <= 50000)个1到N的数,输出最大的数。这个机器的工作原理是通过读入M(M <= 5... 阅读全文
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2852
/**//* 题意: 给出三种操作, 0 在容器中插入一个数。 1 在容器中删除一个数。 2 求出容器中大于a的第k大元素。
解法: 二分+树状数组
思路: 树状数组的特点就是对点更新,成段求和,而且常数非常小。原始 的树状数组只有两种操作,在某点插入一个数 和 求1到i的所有数的和。 这道题目一共有三种操作,但是实质上其实只有两种:插入和询问。插入 操作和删除操作可以视为一种,只不过一个是将标记+1,另一个是-1,而 插入的数对应于树状数组的下标,这样就可以在log(n)的时间内完成插入 和删除。求大于a的k大元素,可以通过二分枚举答案来完成,枚举的是当 前答案在树状数组中的位置,设为m,然后对v[a+1] v[m]求和就是小 于等于m的数的个数,这一步可以用树状数组的求和操作来完成,然后根据 和k的比较来调整m的位置。询问的复杂度也是log(n)的。 */
#include <iostream>
using namespace std;
#define maxn 100002 int C[maxn], n;
int lowbit(int x) { return x & (-x); }
void Add(int pos, int val) { while(pos < maxn) { C[pos] += val; pos += lowbit(pos); } }
int Sum(int pos) { int S = 0; while(pos >= 1) { S += C[pos]; pos -= lowbit(pos); } return S; }
int find(int a, int k) { int l = a + 1; int r = maxn - 1; int S = Sum(a); int ans = maxn;
while(l <= r) { int m = (l + r) >> 1; int nS = Sum(m); if(nS - S >= k) { r = m - 1; if(m < ans) ans = m; }else l = m + 1; }
return ans; }
int main() { int n; int i; while(scanf("%d", &n) != EOF) { for(i = 1; i < maxn; i++) C[i] = 0; while(n--) { int id, e, a, k; scanf("%d", &id); if(id == 0) { scanf("%d", &e); Add(e, 1); }else if(id == 1) { scanf("%d", &e); if(Sum(e) - Sum(e-1) == 0) printf("No Elment!\n"); else Add(e, -1); }else { scanf("%d %d", &a, &k); int num = find(a, k); if(num == maxn) { printf("Not Find!\n"); }else printf("%d\n", num); } } } return 0; }
摘要: 题目链接:http://poj.org/problem?id=2104
/**//*题意: 给出一个长度为N(N <= 100000)的数列,然后是一连串询问,询问数<= 50000,询问的格式是a, b, k,问[a, b]区间中的k小数。解法:二分+归并树+线段树思路:&nb... 阅读全文
摘要: 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3333
/**//*题意: 给出一个长度为N(N <= 30000)的数列,然后是一连串询问,询问数<= 100000,问给定区间内不同数字的和。解法:离线算法+离散化+线段树思路: &nbs... 阅读全文
摘要: 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1255
/**//*题意: 给出N(N <= 1000)个矩形,求被覆盖2次以上的矩形面积并。解法:离散化+线段树思路: 类似于覆盖一次的矩形面积并问题,还是用线段树求解,首先我们将每... 阅读全文
摘要: 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3458
/**//*题意: 给定n(1<= n <= 100000)个矩形,问最长的递增矩形序列。矩形A和BA = ((xA1 , yA1 ), (xA2... 阅读全文
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2859
/**//* 题意: 给定一个n*n(n <= 300)的矩阵,然后是(T <= 10^6)次询问,每次询问某个子矩 阵中的最小值。
解法: 二维线段树 或者 二维RMQ
思路: 一维线段树是一颗完全二叉树,那么二维线段树无疑就是一颗完全四叉树,换言 之,每个结点有四个儿子,这里为了空间不浪费,将所有结点记录在一个一维数组中 ,每个结点的四个儿子采用编号的方式存储,在建树之前将每个结点的信息全部初始 化,初始化的时候需要注意的是每次将当前结点的四个儿子清空,然后判断它本身是 否是叶子结点,可以通过x和y区间端点是否重合来判断,最后再来生成四个儿子编号 ,然后往下递归,递归结束后根据四个儿子的最小值更新当前的最小值。再来看询问 ,和一维的情况类似,一维是对区间交,而二维则是对矩形交,如果询问的二维区间 和当前结点管理的二维区间没有交集,显然不可能有最小值,直接返回inf,否则如果 询问的二维区间完全包含了当前结点管理的二维区间,那么返回结点最小值。否则递 归当前结点的四个儿子,取最小值,回归到根节点就得到了询问区间的最值了。 需要注意的是在建树的时候不要在叶子结点生成多余的儿子结点,这样内存会多 一倍,如果开得不够大有可能下标越界,开得太大有可能超内存。还有就是在二维线 段树的结点上信息会多了不少,能节省空间尽量节省,比如每个结点管理的区间端点 不可能很大,所以不需要int,short就足够了。 */
#include <iostream> #include <cstring> #include <cstdio>
using namespace std;
#define maxn 310 #define inf (1<<30)-1
struct Tree { int Min; // 当前结点区间最小值 int son[4]; // 四个儿子在数组中的编号 short x[2], y[2]; // 当前结点管理的二维区间 }T[maxn*maxn*5];
int Tree_Id;
int n; int mat[maxn][maxn];
void Build(int fat, int x0, int x1, int y0, int y1) { int i; for(i = 0; i < 4; i++) { T[fat].son[i] = 0; } T[fat].x[0] = x0; T[fat].x[1] = x1; T[fat].y[0] = y0; T[fat].y[1] = y1;
if(x0 == x1 && y0 == y1) { T[fat].Min = mat[x0][y0]; return ; } for(i = 0; i < 4; i++) { T[fat].son[i] = Tree_Id++; }
int xmid = (x0 + x1) >> 1; int ymid = (y0 + y1) >> 1; Build(T[fat].son[0], x0, xmid, y0, ymid); Build(T[fat].son[1], x0, xmid, (ymid<y1) ? ymid+1 : ymid, y1); Build(T[fat].son[2], (xmid<x1) ? xmid+1 : xmid, x1, y0, ymid); Build(T[fat].son[3], (xmid<x1) ? xmid+1 : xmid, x1, (ymid<y1) ? ymid+1 : ymid, y1);
T[fat].Min = T[T[fat].son[0]].Min; for(i = 1; i < 4; i++) { if(T[T[fat].son[i]].Min < T[fat].Min) T[fat].Min = T[T[fat].son[i]].Min; } }
int Query(int fat, int x0, int x1, int y0, int y1) { if(x0 > T[fat].x[1] || x1 < T[fat].x[0] || y0 > T[fat].y[1] || y1 < T[fat].y[0]) { return inf; }
if(x0 <= T[fat].x[0] && T[fat].x[1] <= x1 && y0 <= T[fat].y[0] && T[fat].y[1] <= y1) { return T[fat].Min; } int i; int Min = inf; for(i = 0; i < 4; i++) { int v = Query(T[fat].son[i], x0, x1, y0, y1); if(v < Min) Min = v; } return Min; }
int main() { int t; int i, j;
scanf("%d", &t); while(t--) { scanf("%d", &n); Tree_Id = 0; for(i = 1; i <= n; i++) { for(j = 1; j <= n; j++) { scanf("%d", &mat[i][j]); } } Tree_Id = 2; Build(1, 1, n, 1, n);
int m; scanf("%d", &m);
while(m--) { int r[2], c[2]; scanf("%d %d %d %d", &r[0], &c[0], &r[1], &c[1]); printf("%d\n", Query(1, r[0], r[1], c[0], c[1])); } } return 0; }
摘要: 题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2706
/**//*题意: 给定一个长度为N(N <= 30000)的数列Si,紧接着Q条区间处理,每一条处理的要求是将给定的区间内的数字替换成他们的平均值,替换时如果当前数列的总和比原先数列... 阅读全文
摘要: 题目链接:http://poj.org/problem?id=3468
/**//*题意: 给定一个长度为N(N <= 100000)的数列Si,紧接着Q条询问,询问格式如下:"C a b c" 表示对 Aa, Aa+1, , Ab&... 阅读全文
摘要: 题目链接:http://poj.org/problem?id=1151
/**//*题意: 给定n(n <= 100)个矩形,求它们的面积并。解法:离散化+线段树 或者 离散化+FloodFill思路: 数据量很小,直接把浮点数离散成整点,然后暴力FloodF... 阅读全文
摘要: 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3265
/**//*题意: 给定N(N <= 50000)个中空的矩形纸片,求它们面积并。解法:离散化+线段树思路: 2010年宁波区域赛的题,就是矩形面积并的一个变形,转化很容易想到... 阅读全文
摘要: 题目链接:http://poj.org/problem?id=3368
/**//*题意: 给定一个长度为N(N <= 100000)的单调不降数列Si,然后是Q(Q <= 100000)条询问,问给定区间出现最多的数的次数。解法:离散化+线段树 或者 离散化+RMQ... 阅读全文
题目链接:http://poj.org/problem?id=3264
/**//* 题意: 给定一个长度为N(N <= 50000)的数列Si,紧接着Q(1 <= Q <= 200000)条询问, 每次询问给出两个数字表示数列的区间下标,问该区间中最大数和最小数的差。
解法: 线段树 或者 RMQ
思路: 线段树区间最值,维护一颗完全二叉树,每个结点保存两个值,表示该结点管理 的区间的最大值和最小值,比如1号为根结点,管理区间[1, n],每个结点p有左儿子 2*p和右儿子2*p+1,当区间两端点相同时为叶子结点,如果p管理的是[a,b]那么2*p则 管理区间[a, (a+b)/2],2*p+1管理区间[(a+b)/2+1, b],如此一来就可以通过递归, 将儿子的信息传递给父亲,直至根节点。 */
#include <iostream>
using namespace std;
#define maxn 50010
struct Tree { int Min, Max; }T[maxn*4];
int val[maxn]; typedef int Tree_Index;
void Build(int p, int l, int r) { if(l == r) { T[p].Max = T[p].Min = val[l]; return ; } int mid = (l + r) >> 1; Build(p<<1, l, mid); Build(p<<1|1, mid+1, r); T[p].Max = T[p<<1].Max > T[p<<1|1].Max ? T[p<<1].Max : T[p<<1|1].Max; T[p].Min = T[p<<1].Min < T[p<<1|1].Min ? T[p<<1].Min : T[p<<1|1].Min; }
Tree_Index Query(int p, int l, int r, int a, int b, bool bMin) { if(l == a && b == r) return p; int mid = (l + r) >> 1; if(b <= mid) { return Query(p<<1, l, mid, a, b, bMin); }else if(mid + 1 <= a) { return Query(p<<1|1, mid+1, r, a, b, bMin); }else { Tree_Index p1 = Query(p<<1, l, mid, a, mid, bMin); Tree_Index p2 = Query(p<<1|1, mid+1, r, mid+1, b, bMin); if(bMin) { return T[p1].Min < T[p2].Min ? p1 : p2; }else { return T[p1].Max > T[p2].Max ? p1 : p2; } } }
int main() { int n, m; int i; while(scanf("%d %d", &n, &m) != EOF) { for(i = 1; i <= n; i++) { scanf("%d", &val[i]); } Build(1, 1, n); while(m--){ int x, y; scanf("%d %d", &x, &y); Tree_Index pMax = Query(1, 1, n, x, y, false); Tree_Index pMin = Query(1, 1, n, x, y, true); printf("%d\n", T[pMax].Max - T[pMin].Min); } } return 0; }
摘要: 题目链接:http://poj.org/problem?id=2452
/**//*题意: 给定一个长度为N(N <= 50000)的数列Si,要求找到Si和Sj(1 <= i < j <= N)使得所有的Sk(i < k... 阅读全文
|