1、对点操作->查询区间
Description
Lily 特别喜欢养花,但是由于她的花特别多,所以照料这些花就变得不太容易。她把她的花依次排成一行,每盆花都有一个美观值。如果Lily把某盆花照料的好的话,这盆花的美观值就会上升,如果照料的不好的话,这盆花的美观值就会下降。有时,Lily想知道某段连续的花的美观值之和是多少,但是,Lily的算术不是很好,你能快速地告诉她结果吗?
Input
第一行一个整数T,表示有T组测试数据。
每组测试数据的第一行为一个正整数N(N<=50000),表示Lily有N盆花。接下来有N个正整数,第i个正整数ai表示第i盆花的初始美观值(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(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,表示结束,这条命令在每组数据最后出现
每组数据的命令不超过40000条
对于第i组数据,首先输出"Case i:"和回车。
对于每个"Query i j"命令,输出第i盆花到第j盆花的美观值之和。
Sample Input
1
9
7 9 8 4 4 5 4 2 7
Query 7 9
Add 4 9
Query 3 6
Sub 9 6
Sub 3 3
Query 1 9
End
Sample Output
Case 1:
133050
#include
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define UP(i,a,b) for(int i=a;i<=b;i++)
#define DN(i,a,b) for(int i=a;i>=b;i--)
struct node
{
int l, m, r, sum;
} nd[4 * 50005];
void build(int i, int l, int r)
{
nd[i].l = l;
nd[i].r = r;
nd[i].m = (l + r) / 2;
if(l == r)
{
scanf("%d", &nd[i].sum);
return;
}
build(i * 2, l, nd[i].m);
build(i * 2 + 1, nd[i].m + 1, r);
nd[i].sum = nd[i * 2].sum + nd[i * 2 + 1].sum;//At this point, two child trees have already been built up whose top nodes already have sum value
}
void update(int i, int n, int x)
{
nd[i].sum += x;
if(nd[i].l == nd[i].r) return;
if(n <= nd[i].m)
update(i * 2, n, x);
else
update(i * 2 + 1, n, x);
}
int query(int i, int a, int b)
{
if(nd[i].l == a && nd[i].r == b)
return nd[i].sum;
if(b <= nd[i].m)
return query(i * 2, a, b);
if(nd[i].m < a)
return query(i * 2 + 1, a, b);
return query(i * 2, a, nd[i].m) + query(i * 2 + 1, nd[i].m + 1, b);
}
int main()
{
char s[20];
int t, n, x, a, b;
scanf("%d", &t);
UP(i, 1, t)
{
printf("Case %d:\n", i);
scanf("%d", &n);
build(1, 1, n);
while(1)
{
scanf("%s", s);
if(s[0] == 'A')
{
scanf("%d%d", &n, &x);
update(1, n, x);
}
else if(s[0] == 'S')
{
scanf("%d%d", &n, &x);
update(1, n, -x);
}
else if(s[0] == 'Q')
{
scanf("%d%d", &a, &b);
printf("%d\n", query(1, a, b));
}
else break;
}
}
}
2、对区间操作->查询区间
Description
给出了一个序列,你需要处理如下两种询问。
"C a b c"表示给[a, b]区间中的值全部增加c (-10000 ≤ c ≤ 10000)。
"Q a b" 询问[a, b]区间中所有值的和。
Input
第一行包含两个整数N, Q。1 ≤ N,Q ≤ 100000.
第二行包含n个整数,表示初始的序列A (-1000000000 ≤ Ai ≤ 1000000000)。
接下来Q行询问,格式如题目描述。
Output
对于每一个Q开头的询问,你需要输出相应的答案,每个答案一行。
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
LTE代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AC代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
3、对区间操作->查询点
Description
N个气球排成一排,从左到右依次编号为1,2,3....N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽"牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?
Input
每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。
当N = 0,输入结束。
Output
每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。
Sample Input
3
1 1
2 2
3 3
3
1 1
1 2
1 3
0
Sample Output
1 1 1
3 2 1
注意!这里如果每一次操作的时候都把区间下推更新的话会超时,所以记住了一定不能无脑套模板,任何题到手都应该先算复杂度。正确方法是先用 t[i].n++ 存起来,最后再从头往下处理。
代码:
//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
简单做法O(n)
//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
4、最小逆序数
Description
The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.
For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:
a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)
You are asked to write a program to find the minimum inversion number out of the above sequences.
Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.
Output
For each case, output the minimum inversion number on a single line.
Sample Input
10 1 3 6 9 0 8 5 7 4 2
Sample Output
16
这道题有多种做法:http://www.verydemo.com/demo_c180_i41274.html
代码1:暴力(也不是无脑暴力,有个递推公式)
#include//code source:hnust_xiehonghao
int a[5005];
int main()
{
int n,i,j,ans=999999999;
while(~scanf("%d",&n))
{
ans=999999999;
for(i=0; ia[j])
cnt++;
}
if(ans>cnt) ans=cnt;
for(i=0; i0这个逆序对就会减少,2>1,3>1,4>1这些逆序对就会增加。*/
if(ans>cnt)
ans=cnt;
}
printf("%d\n",ans);
}
return 0;
}
代码2:由于这道题是线段树专题里面的,我还以为会把每一种情况都构造一次线段树来做,结果就只是把求原序列逆序数那里改成线段树,后来仍然用递推公式来做,真是醉了。求原序列逆序数的线段树就是之前提到过的对点进行操作的类型。输入一个a[i]就query(1 ,a [i]+1 , n-1)(思路是这样,但操作要变通一下),找到已存在的序列中比它大的元素的个数。
#include
int a[5005];
struct haha
{
int left;
int right;
int num;
} node[5005*4];
void build(int left,int right,int nd)
{
node[nd].left=left;
node[nd].right=right;
node[nd].num=0;
if(left==right)
return ;
int mid=(left+right)/2;
build(left,mid,nd*2);
build(mid+1,right,nd*2+1);
}
int query(int left,int right,int nd)
{
int mid=(node[nd].left+node[nd].right)/2;
if(node[nd].left==left&&node[nd].right==right)
return node[nd].num;
if(right<=mid)
return query(left,right,nd*2);
else if(left>mid)
return query(left,right,nd*2+1);
else
return query(left,mid,nd*2)+query(mid+1,right,nd*2+1);
}
void update(int pos,int nd)
{
if(node[nd].left==node[nd].right)
{
node[nd].num++;
return ;
}
int mid=(node[nd].left+node[nd].right)/2;
if(pos<=mid)
update(pos,nd*2);
else
update(pos,nd*2+1);
node[nd].num=node[nd*2].num+node[nd*2+1].num;
}
int main()
{
int n,i,j;
while(scanf("%d",&n)!=EOF)
{
for(i=0; isum) ans=sum;
for(i=0; isum) ans=sum;
}
printf("%d\n",ans);
}
return 0;
}
5、插队问题(模板题)
Description
Railway tickets were difficult to buy around the Lunar New Year in China, so we must get up early and join a long queue…
The Lunar New Year was approaching, but unluckily the Little Cat still had schedules going here and there. Now, he had to travel by train to Mianyang, Sichuan Province for the winter camp selection of the national team of Olympiad in Informatics.
It was one o’clock a.m. and dark outside. Chill wind from the northwest did not scare off the people in the queue. The cold night gave the Little Cat a shiver. Why not find a problem to think about? That was none the less better than freezing to death!
People kept jumping the queue. Since it was too dark around, such moves would not be discovered even by the people adjacent to the queue-jumpers. “If every person in the queue is assigned an integral value and all the information about those who have jumped the queue and where they stand after queue-jumping is given, can I find out the final order of people in the queue?” Thought the Little Cat.
Input
There will be several test cases in the input. Each test case consists of N + 1 lines where N (1 ≤ N ≤ 200,000) is given in the first line of the test case. The next N lines contain the pairs of values Posi and Vali in the increasing order of i (1 ≤ i ≤ N). For each i, the ranges and meanings of Posiand Vali are as follows:
There no blank lines between test cases. Proceed to the end of input.
Output
For each test cases, output a single line of space-separated integers which are the values of people in the order they stand in the queue.
Sample Input
4
0 77
1 51
1 33
2 69
4
0 20523
1 19243
1 3890
0 31492
Sample Output
77 33 69 51
31492 20523 3890 19243
题意:有n个数,进行n次操作,每次操作有两个数pos, ans。pos的意思是把ans放到第pos 位置的后面,pos后面的数就往后推一位。最后输出每个位置的ans。
思路(来自青山绿水之辈):根据题 目可知,最后插入的位置的数才是最终不变的数,所以可以从最后的输入作第1个放入,依此类推,倒插入。在插入时,要注意,一个数放入之后那么这个位置就不用管了,那么树中所有的空位置就是余下的数所对应的位置,也就是把余下的数又可以看作是一个新的集合。那么每次插入都是当前集合的第1次放。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MaxSize 200005
int pos[MaxSize];
int val[MaxSize];
int A[MaxSize];
struct node
{
int l,r;
int space;
};
node tree[MaxSize*4];
void build(int a,int b,int k)
{
tree[k].l = a;
tree[k].r = b;
if(a==b)
{
tree[k].space = 1;
return ;
}
int mid = (a+b)/2;
build(a,mid,k*2);
build(mid+1,b,k*2+1);
tree[k].space = tree[k*2].space + tree[k*2+1].space;
}
void insert(int p,int val,int k)
{
if(tree[k].l == tree[k].r)
{
tree[k].space--;
A[tree[k].l] = val;
return ;
}
if(p <= tree[k*2].space)
insert(p,val,k*2);
else
insert(p-tree[k*2].space,val,k*2+1);
tree[k].space = tree[k*2].space + tree[k*2+1].space;
}
int main()
{
int N;
while(~scanf("%d",&N))
{
for(int i=1; i<= N; i++)
scanf("%d%d",&pos[i],&val[i]);
build(0,N-1,1);
for(int i=N; i>=1; i--)
insert(pos[i]+1,val[i],1);
for(int i=0; i
6、连续区间
总时间限制:
1000ms
内存限制:
65535kB
描述
有一个苦逼的种树人XX,需要种一排树,为什么说苦逼呢?因为总有一些熊孩子去把树苗拔掉(具体拔掉做什么,你们可以YY一下)。为了简化问题,把可以种树的位置编号为1-n。XX每次选择一个位置种树(如果已经有树了,则忽略),而熊孩子每次也选择一个位置拔树(如果没有树,则忽略)。现在XX想知道每次种树或者拔树后的连续有树的区间的最长长度。
输入
多组数据(大约10组)。对于每组数据:
第一行n,m;
接下来m行,每行q,a;
其中,q为1时表示XX种树,q为2时表示熊孩子拔树,a表示位置。
数据范围:1<=n,m<=10000,1<=q<=2,1<=a<=n。
输出
对于每组数据,输出m行,每次操作后的结果。
样例输入
4 5
1 1
1 3
1 4
2 4
1 2
样例输出
1
1
2
1
3
提示
开始状态都没有种树
=========================================================================================
思路:
我们要从线段树的所有区间中往上推,找出所有区间中最大的连续长度。看似tree[i].max_lenth=max( tree[i*2].max_length, tree[i*2+1].max_legnth)就行了。假如第1棵树到第7棵树1111011,第8棵树到第14棵树1110001。如果用这样的思路,父节点的最大连续就应该是max(4 , 3),为4。但是我们可以明显看出中间连接部分可以形成新的连续,而这里这个新连续恰好比左右儿子的max_length都大。所以这个思路明显是不对的。我们也从这个例子中知道,我们应该保留每个区间的左连续长度和右连续长度,在合并的时候,在连接部分进行相加,再来和左右儿子原来的max_length进行比较。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MaxSize 10005
#define inf 0x3f3f3f3f
#define LL long long int
struct node
{
int l,r,mid,l_max,r_max,t_max;//l_max:从最左边开始的最长连续个数 r_max:从最右边开始的最长连续个数 t_max:整个区间中的最长连续个数
} tree[MaxSize*4];
void build(int i, int l, int r)
{
tree[i].l=l;
tree[i].r=r;
tree[i].mid=(l+r)/2;
tree[i].l_max=tree[i].r_max=tree[i].t_max=0;//一开始没有树,长度都是0
if(l==r) return;
build(i*2,tree[i].l,tree[i].mid);
build(i*2+1,tree[i].mid+1,tree[i].r);
}
void operate(int i, int num, int x)
{
if(tree[i].l==tree[i].r)
{
tree[i].l_max=tree[i].r_max=tree[i].t_max=num;//对于子节点,这三者是一样的
return;
}
if(x<=tree[i].mid)
operate(i*2,num,x);
else
operate(i*2+1,num,x);
////////////////////////////////////操作完拔树种树之后,俩儿子合并操作///////////////////////////////////////
tree[i].t_max = max( max(tree[i*2].t_max,tree[i*2+1].t_max) , tree[i*2].r_max+tree[i*2+1].l_max);
/*两个儿子合并的时候,如果左儿子有右连续,右儿子有左连续,中间一合并,新连续的长度就可能超过左右儿子的t_max,从而成为新的t_max*/
/*举个例子:左儿子111000011,右儿子10001111,我们在合并的时候,他们的父节点的左连续长度就应该是左儿子的左连续长度,
右连续长度就应该是右儿子的右连续长度。但是有另外一种情况,左儿子11111111,右儿子11100011.这样左儿子是满的情况下,
父节点的左连续长度就应该是左儿子连续长度加上它与右儿子连接的那部分的长度*/
if(tree[i*2].l_max == tree[i*2].r-tree[i*2].l+1)//如果左儿子满,父节点的左连续就应该是左连续长度加它与右儿子连接的那部分的长度
tree[i].l_max=tree[i*2].l_max+tree[i*2+1].l_max;//如果不是满的(即从左尽头到与右儿子连接部分之间有间隔),父节点的左连续长度就应该左儿子的左连续长度
else
tree[i].l_max=tree[i*2].l_max;
if(tree[i*2+1].r_max == tree[i*2+1].r-tree[i*2+1].l+1)
tree[i].r_max=tree[i*2+1].r_max+tree[i*2].r_max;
else
tree[i].r_max=tree[i*2+1].r_max;
}
int main()
{
int n,m,q,a;
while(~scanf("%d%d",&n,&m))
{
build(1,1,n);
while(m--)
{
scanf("%d%d",&q,&a);
operate(1,q%2,a);//q为1时,q%2==1,让那个位置变为1,表示有树。q为2时,q%2==0,让那个位置变为0,表示没有有树。
printf("%d\n",tree[1].t_max);
}
}
return 0;
}//FROM CJZ