线段树详解
一 概述
线段树,类似区间树,它在各个节点保存一条线段(数组中的一个子数组),主要用于高效解决连续区间的动态查询问题,由于线段树是平衡二叉树,基于二叉树的特性,它基本能够保持每个操作的复杂度为O(logn)。
线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。
二 从一个例子理解线段树
下面我们从一个经典的例子来理解线段树,问题描述如下:从数组arr[0…n-1]中查找某个数组某个区间内的最小值,其中数组大小固定,但是数组中的元素的值可以随时更新。
对这个问题一个简单的解法是:遍历数组区间找到最小值,时间复杂度是O(n),额外的空间复杂度O(1)。当数据量特别大,而查询操作很频繁的时候,耗时可能会不满足需求。
另一种解法:使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间为O(n^ 2)查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也很麻烦。
我们可以用线段树来解决这个问题:预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)。根据这个问题我们构造如下的二叉树
叶子节点是原始组数arr中的元素
非叶子节点代表它的所有子孙叶子节点所在区间的最小值
例如对于数组[2, 5, 1, 4, 9, 3]可以构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间arr[0…5]内的最小值是1):
图中所有的根节点的值为对应区间内的最小值,叶子节点是每个数组元素的值。
由于线段树的父节点区间是平均分割左右子树,因此线段树是完全二叉树,对于包含n个叶子节点的完全二叉树,它一定有n-1个非叶子节点,总共2n-1个节点,因此存储线段需要的空间复杂度是O(n)。
创建线段树
对于线段树我们可以选择和普通二叉树一样的链式结构。由于线段树是完全二叉树,我们也可以用数组来存储。下面的讨论及代码都是数组来存储线段树,节点结构如***意到用数组存储时,有效空间为2n-1,实际空间确不止这么多,比如上面的线段树中叶子节点1、3虽然没有左右子树,但是的确占用了数组空间,实际空间是满二叉树的节点数目 ,需要开出4n的空间,但是这个空间复杂度也是O(n)的 )。
const int MAXN=1e5;
int a[MAXN];
struct node{
int sum;//指当前区间的和,也可以是最大值、最小值等
int l,r;
}st[MAXN<<2];//l是区间左端点,r是区间右端点
void build(int k,int l,int r){
st[k].l=l;
st[k].r=r;
if(l==r)st[k].sum=a[l];//当左右端点相同时,即叶子节点,赋值
else{
int mid=(l+r)>>1;
build(k<<1,l,mid);
build((k<<1)|1,mid+1,r);
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum;
}
}
查询线段树
在构建好了线段树之后,我们需要根据题目要求查询线段树中包含的一定信息。查询的思想是一段一段地查询,无交集就返回0,需要查询的区间包含正在搜索的区间时,返回当前搜索区间节点包含的值,若部分交集,则分别搜索左右子树,直至我们需要查询的区间包含子树代表的区间。
int query(int k,int l,int r){//l,r分别代表的是需要查询的区间的左右端点
if(r<st[k].l||l>st[k].r){//无交集,返回0
return 0;
}
if(l<=st[k].l&&r>=st[k].r){//需要查询的区间包含当前搜索的区间
return st[k].sum;
}
int ans=0;
int mid=(st[k].l+st[k].r)>>1;
if(l<=mid)ans+=query(k<<1,l,r);//搜索左子树
if(r>=mid)ans+=query((k<<)1,l,r);//搜索右子树
return ans;
}
单节点更新
单节点更新是指只更新线段树的某个叶子节点的值,但是更新叶子节点会对其父节点的值产生影响,所以需要用叶子节点的值来更新父节点的值。
void update(int k,int ind,int ans){//ind指要更新的叶子节点的左端点,ans指要将叶子节点替换的值
if(st[k].l==ind&&st[k].r==ind){
st[k].sum=ans;
return ;
} //找到叶子节点,修改叶子节点的值
int mid=(l+r)>>1;
if(ind<=mid)update(k<<1,ind,ans);//搜索左子树
else if(ind>=mid)update((k<<1)|1,ind,ans);//搜索右子树
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum; //利用左右子树来更新父节点的值
}
区间更新
区间更新是指更新某个区间内的叶子节点的值。因为涉及到的节点不只一个,所以如果一个个搜索到叶子节点然后修改叶子节点的值的话,毫无疑问会使得操作的复杂度超过logn。这是,我们引入一个**延迟标记(lazytag)**的概念,这也是线段树最重点核心的内容。
延迟标记:每个节点新增一个标记lazy,记录这个节点是否需要进行某种操作,但是并不立刻进行这种操作,而是在查询、更新线段树的时候,遇到该节点需要进行操作,则更新该节点及其儿子的值,并将lazy标记传给其儿子节点,最后将该节点的lazy消去,避免重复操作。这样就保证了线段树的区间更新的操作的复杂度为(logn)。/
const int MAXN=1e5;
int a[MAXN];
struct node{
int sum;//指当前区间的和,也可以是最大值、最小值等
int l,r,lazy;
}st[MAXN<<2];//l是区间左端点,r是区间右端点
void build(int k,int l,int r){//k表示根节点,l,r表示当前节点所代表的区间
st[k].l=l;
st[k].r=r;
lazy=0;
if(l==r)st[k].sum=a[l];//当左右端点相同时,即叶子节点,赋值
else{
int mid=(l+r)>>1;
build(k<<1,l,mid);
build((k<<1)|1,mid+1,r);
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum;
}
}
void pushdown(int k){
if(st[k].lazy){
st[k<<1].lazy=st[k].lazy;//把lazy传给左子树
st[(k<<1)|1].lazy=st[k].lazy;//把lazy传给右子树
st[k<<1].sum+=st[k].lazy*(st[k].r-st[k].l+1);//修改左子树的值
st[(k<<1)|1].sum+=st[k].lazy*(st[(k<<1)|1].r-st[(k<<)|1].l+1);//修改有子树区间的值
st[k].lazy=0;//消去父节点的lazy
}
}
void update_quajian(int k,int l,int r,int ans){//k表示根节点,l,r表示要修改的区间,ans表示要修改的数值
if(st[k].l>r||st[k].r<l)return ;
if(l<=st[k].l&&r>=st[k].sum){
st[k].lazy+=ans;//更新当前节点lazy
st[k].sum+=ans*(st[k].r-st[k].l+1);//更新当前节点的值
return ;
}
pushdown(k);
int mid=(st[k].l+st[k].r)>>1;
if(l<=mid)update(k<<1,l,r,ans);//搜索左子树
if(r>=mid)update((k<<1)|1,l,r,ans);//搜索右子树
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum;
}
int query(int k,int l,int r){//k表示根节点,l,r分别为要查询的区间的左右端点
if(l>st[k].r||r<st[k].l)return 0;//无交集,返回0
if(l<=st[k].l&&r>=st[k].r)return st[k].sum;
int mid=(st[k].l+st[k].r)>>1;
int ans=0;
if(l<=mid)ans+=query(k<<1,l,r);//搜索左子树
if(r>=mid)ans+=query((k<<1)|1,l,r);//搜索右子树
return ans;
}
线段树模板题
HDU1166
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
*情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(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条命令
Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End
#include<bits/stdc++.h>
using namespace std;
const int MAXN=50000;
int a[MAXN+5],st[(MAXN<<2)+5];
void build(int o,int l,int r){
if(l==r){
st[o]=a[l];
}
else {
int m=(l+r)>>1;
build(o<<1,l,m);
build((o<<1)|1,m+1,r);
st[o]=st[o<<1]+st[(o<<1)|1];
}
}
void update1(int o,int l,int r,int ind,int ans){
if(l==r){//ans为增加的值
st[o]+=ans;
return ;
}
int m=(l+r)>>1;
if(ind<=m){
update1(o<<1,l,m,ind,ans);
}
else{
update1((o<<1)|1,m+1,r,ind,ans);
}
st[o]=st[o<<1]+st[(o<<1)|1];
}
void update2(int o,int l,int r,int ind,int ans){
if(l==r){//ind 表示需要修改的叶子节点的左端点,ans为减少的值
st[o]-=ans;
return ;
}
int m=(l+r)>>1;
if(ind<=m){
update2(o<<1,l,m,ind,ans);
}
else{
update2((o<<1)|1,m+1,r,ind,ans);
}
st[o]=st[o<<1]+st[o<<1|1];
}
int query(int o,int l,int r,int ql,int qr){
if(ql>r||qr<l){
return 0;
}
if(ql<=l&&qr>=r){
return st[o];
}
int m=(l+r)>>1;
int p1=query(o<<1,l,m,ql,qr),p2=query(o<<1|1,m+1,r,ql,qr);
// cout<<p1<<" "<<p2<<endl;
return p1+p2;
}
struct node{
string s;
int num;
int chan;
};
node info;
int main(){
int T;
cin>>T;
for(int i=1;i<=T;i++){
printf("Case %d:\n",i);
int n;
cin>>n;
for(int j=1;j<=n;j++){
scanf("%d",&a[j]);
}
build(1,1,n);
while(cin>>info.s){
if(info.s=="End"){
break;
}
else if(info.s=="Add"){
cin>>info.num>>info.chan;
update1(1,1,n,info.num,info.chan);
}
else if(info.s=="Sub"){
cin>>info.num>>info.chan;
update2(1,1,n,info.num,info.chan);
}
else{
cin>>info.num>>info.chan;
printf("%d\n",query(1,1,n,info.num,info.chan));
}
}
}
return 0;
}
HDU 1754
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。 这让很多学生很反感。
不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
Input
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取’Q’或’U’) ,和两个正整数A,B。
当C为’Q’的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为’U’的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
Output
对于每一次询问操作,在一行里面输出最高成绩。
Sample Input
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5
#include<bits/stdc++.h>
using namespace std;
const int MAXN=200000;
int a[MAXN+5],st[(MAXN<<2)+5];
int n,m;
void build(int o,int l,int r){
if(l==r){
st[o]=a[l];
}
else{
int m=(l+r)>>1;
build(o<<1,l,m);
build((o<<1)|1,m+1,r);
st[o]=max(st[o<<1],st[(o<<1)|1]);
}
}
void update(int o,int l,int r,int ind,int ans){
if(l==r){
st[o]=ans;
return ;
}
int m=(l+r)>>1;
if(ind<=m){
update(o<<1,l,m,ind,ans);
}
else{
update((o<<1)|1,m+1,r,ind,ans);
}
st[o]=max(st[o<<1],st[(o<<1)|1]);
}
int query(int o,int l,int r,int ql,int qr){
if(r<ql||l>qr){
return 0;
}
if(ql<=l&&qr>=r){
return st[o];
}
int m=(l+r)>>1;
int p1=query(o<<1,l,m,ql,qr);
int p2=query((o<<1)|1,m+1,r,ql,qr);
return max(p1,p2);
}
int main(){
while(~scanf("%d %d",&n,&m)){
char s;
int num,chan;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
build(1,1,n);
for(int i=1;i<=m;i++){
cin>>s;//scanf("\n%c",&s);
if(s=='Q'){//\n读入上一行的回车; 或者在scanf()之前getchar()把回车消掉
scanf("%d%d",&num,&chan);
printf("%d\n",query(1,1,n,num,chan));
}
else if(s=='U'){
scanf("%d%d",&num,&chan);
update(1,1,n,num,chan);
}
}
}
return 0;
}
HDU 1394
C - 求逆序对
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
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5000;
struct node{
int l,r,sum;
}st[MAXN<<2+5];
int ans,a[MAXN+5],n;
void build(int k,int l,int r){
st[k].l=l;
st[k].r=r;
st[k].sum=0;
if(l==r){
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build((k<<1)|1,mid+1,r);
}
void update(int k,int x){
int l=st[k].l;
int r=st[k].r;
if(x==l&&x==r){
st[k].sum=1;
return ;
}
int mid=(l+r)>>1;
if(x<=mid){
update(k<<1,x);
}
else {
update((k<<1)|1,x);
}
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum;
}
int query(int o,int l,int r,int ql,int qr){
if(ql>r||qr<l){
return 0;
}
if(ql<=l&&qr>=r){
return st[o].sum;
}
int m=(l+r)>>1;
int p1=query(o<<1,l,m,ql,qr),p2=query(o<<1|1,m+1,r,ql,qr);
// cout<<p1<<" "<<p2<<endl;
return p1+p2;
}
int main(){
int sum;
while(~scanf("%d",&n)){
ans=0;
build(1,0,n);//因为后面有一个a[i]+1的操作,所以需要多开一位
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
update(1,a[i]);
ans+=query(1,0,n,a[i]+1,n-1);//计算已经输入的数中间比a[i]大的数有多少个
}
sum=ans;
for(int i=1;i<n-1;i++){
sum=sum-a[i]+(n-a[i]-1);
if(sum<ans){
ans=sum;
}
}
printf("%d\n",ans);
}
return 0;
}
HDU 2795
D - 区间求最大值的位子
At the entrance to the university, there is a huge rectangular billboard of size h*w (h is its height and w is its width). The board is the place where all possible announcements are posted: nearest programming competitions, changes in the dining room menu, and other important information.
On September 1, the billboard was empty. One by one, the announcements started being put on the billboard.
Each announcement is a stripe of paper of unit height. More specifically, the i-th announcement is a rectangle of size 1 * wi.
When someone puts a new announcement on the billboard, she would always choose the topmost possible position for the announcement. Among all possible topmost positions she would always choose the leftmost one.
If there is no valid location for a new announcement, it is not put on the billboard (that’s why some programming contests have no participants from this university).
Given the sizes of the billboard and the announcements, your task is to find the numbers of rows in which the announcements are placed.
Input
There are multiple cases (no more than 40 cases).
The first line of the input file contains three integer numbers, h, w, and n (1 <= h,w <= 10^9; 1 <= n <= 200,000) - the dimensions of the billboard and the number of announcements.
Each of the next n lines contains an integer number wi (1 <= wi <= 10^9) - the width of i-th announcement.
Output
For each announcement (in the order they are given in the input file) output one number - the number of the row in which this announcement is placed. Rows are numbered from 1 to h, starting with the top row. If an announcement can’t be put on the billboard, output “-1” for this announcement.
Sample Input
3 5 5
2
4
3
3
3
Sample Output
1
2
1
3
-1
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6;
int a[MAXN+5];
int h,w,n;
int ans;
struct node{
int sum;
}st[MAXN<<2+5];
void build(int k,int l,int r){
st[k].sum=w;
if(l==r){
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build((k<<1)|1,mid+1,r);
// st[k].sum=max(st[k<<1].sum,st[(k<<1)|1].sum);
}
void update(int k,int l,int r,int ind){
// cout<<l<<" "<<r<<endl;
if(l==r){
st[k].sum-=ind;
ans=l;
return ;
}
int mid=(l+r)>>1;
if(ind<=st[k<<1].sum){
update(k<<1,l,mid,ind);
}
else {
update((k<<1)|1,mid+1,r,ind);
}
st[k].sum=max(st[k<<1].sum,st[(k<<1)|1].sum);
}
int main(){
while(~scanf("%d%d%d",&h,&w,&n)){
int info=min(h,n);
build(1,1,info);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]<=st[1].sum){
update(1,1,info,a[i]);
printf("%d\n",ans);
}
else{
cout<<"-1"<<endl;
}
}
}
return 0;
}
POJ 3468
You have N integers, A1, A2, … , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, … , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
“C a b c” means adding c to each of Aa, Aa+1, … , Ab. -10000 ≤ c ≤ 10000.
“Q a b” means querying the sum of Aa, Aa+1, … , Ab.
Output
You need to answer all Q commands in order. One answer in a line.
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
Hint
The sums may exceed the range of 32-bit integers.
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int MAXN=1e5;
ll a[MAXN+5];
struct node{
int l,r,lazy;
ll sum;
}st[MAXN<<2+5];
struct node1{
char s;
int a;
int b;
int c;
}info;
void build(int k,int l,int r){
st[k].l=l;
st[k].r=r;
st[k].lazy=0;
if(l==r){
st[k].sum=a[l];
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build((k<<1)|1,mid+1,r);
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum;
}
void down(int k){
st[k<<1].lazy+=st[k].lazy;
st[(k<<1)|1].lazy+=st[k].lazy;
st[k<<1].sum+=st[k].lazy*(st[k<<1].r-st[k<<1].l+1);
st[(k<<1)|1].sum+=st[k].lazy*(st[(k<<1)|1].r-st[(k<<1)|1].l+1);
st[k].lazy=0;
}
void update(int k,int l,int r,int ind){
if(l>st[k].r||r<st[k].l){
return ;
}
if(l<=st[k].l&&r>=st[k].r){
st[k].lazy+=ind;
st[k].sum+=ind*(st[k].r-st[k].l+1);
return ;
}
down(k);
int mid=(st[k].l+st[k].r)>>1;
if(l<=mid){
update(k<<1,l,r,ind);
}
if(r>=mid){
update((k<<1)|1,l,r,ind);
}
st[k].sum=st[k<<1].sum+st[(k<<1)|1].sum;
}
ll query(int k,int l,int r){
if(l>st[k].r||r<st[k].l){
return 0;
}
if(l<=st[k].l&&r>=st[k].r){
// cout<<"1"<<endl;
return st[k].sum ;
}
down(k);
int mid=(st[k].l+st[k].r)>>1;
ll ans=0;
if(l<=mid){
ll p1=query(k<<1,l,r);
ans+=p1;
// cout<<st[k].l<<" "<<st[k].r<<endl;
}
if(r>=mid){
ll p2=query((k<<1)|1,l,r);
ans+=p2;
// cout<<st[k].l<<" "<<st[k].r<<endl;
}
return ans;
}
int main(){
int n,q;
while(~scanf("%d%d",&n,&q)){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
build(1,1,n);
while(q--){
scanf("\n%c",&info.s);//注意去掉前面的空格
if(info.s=='Q'){
scanf("%d%d",&info.a,&info.b);
ll ans=query(1,info.a,info.b);
printf("%lld\n",ans);
}
else if(info.s=='C'){
scanf("%d%d%d",&info.a,&info.b,&info.c);
update(1,info.a,info.b,info.c);
// cout<<st[1].sum<<endl;
// for(int i=1;i<=4*n;i++){
// cout<<st[i].sum<<" ";
// }
// cout<<endl;
}
}
}
return 0;
}