南阳acm士兵杀敌系列

来源:互联网 发布:如何利用网络做直销 编辑:程序博客网 时间:2024/06/07 05:06

士兵杀敌(一)

#include <stdio.h>
int main()
{
int M,N;
scanf("%d%d",&N,&M);
int *a;
a=new int[1000000];
int i,j;int start,end,sum;
for(i=0;i<N;i++)
{scanf("%d",&a[i]);}
for(i=0;i<M;i++)
{
sum=0;
scanf("%d%d",&start,&end);
for(j=start-1;j<end;j++)
{
sum=sum+a[j];
}
printf("%d\n",sum);
}
delete []a;
return 0;
}


很不幸的是 TimeLimitExceeded


#include <stdio.h>
int main()
{
int M,N;
scanf("%d%d",&N,&M);
int *a,*sum;
a=new int[1000000];
sum=new int [1000000];
int i,j;int start,end;
sum[0]=0;
for(i=0;i<N;i++)
{scanf("%d",&a[i]);
if(i==0) sum[i+1]=a[i];else sum[i+1]=sum[i]+a[i];}
for(i=0;i<M;i++)
{
scanf("%d%d",&start,&end);
printf("%d\n",sum[end]-sum[start-1]);
}
delete []a;
delete []sum;
return 0;
}


这里涉及时间复杂度的问题 没输入一个就先计算 

最后相减计算出答案 但是脚标要注意 每一个都是加上自己的 如果直接end-start就会少了start的那一个 所以要start-1


很好的答案

01.#include<cstdio>
02.const int MAX=1000010;
03.int sum[MAX];
04.int main()
05.{
06.int N,q,m,n;
07.scanf("%d%d",&N,&q);
08.for(int i=1;i<=N;++i)
09.{
10.scanf("%d",&sum[i]);
11.sum[i]+=sum[i-1];
12.}
13.for(int i=0;i!=q;++i)
14.{
15.scanf("%d%d",&m,&n);
16.printf("%d\n",sum[n]-sum[m-1]);
17.}
18.}
没有记录输入的a[i] 直接一输入就计算 最后得出结果


士兵杀敌(二)


前面数组的数是固定的 所以可以直接构造一个sum的数组 但是这个是动态的 不可以这样做

在原来的基础上添加增加杀敌数的功能 添加后的要覆盖原来的

所以必须要把原来的更新 先用最先的方法 看会不会超时

事实证明 timelimmitexceeded

#include <iostream>
#include <string>
using namespace std;
int main()
{
int N,M;
cin>>N>>M;
int a[1000000];
int i,j,sum;
for(i=0;i<N;i++)
{cin>>a[i];}
int x,y;
string str;
for(i=0;i<M;i++)
{
cin>>str;
if(str=="ADD")
{cin>>x>>y;
a[x-1]=a[x-1]+y;}
else if(str=="QUERY")
{cin>>x>>y;sum=0;
for(j=x-1;j<y;j++)
{sum=sum+a[j];}
cout<<sum<<endl;}
}
return 0;
}


这里涉及到树状数组的应用

所谓的树状数组是对一个数组改变某个元素和求和比较实用的数据结构 达到减少时间的效果


另外 lowbit(k)即求最低位1的值

如图 例如我想要改变其中一个值

  1. void add(int k,int num)  
  2. {  
  3.     while(k<=n)  
  4.     {  
  5.         tree[k]+=num;  
  6.         k+=k&-k;  
  7.     }  
  8. }  
比如我想在A【3】中加上数值num 直接影响的是c【3】 c【4】 c【8】

也就是 3的二进制011+最低位001=4 

4的二进制0100+最低位0100=8

所以改变的就是  a【k+k&-k】

普及一下lowbit(k)的算法就是把k的二进制的高位1全部清空,只留下最低位的1

比较普遍的方法lowbit(k)=k&-k

然后如果想要求和 

  1. int read(int k)//1~k的区间和  
  2. {  
  3.     int sum=0;  
  4.     while(k)  
  5.     {  
  6.         sum+=tree[k];  
  7.         k-=k&-k;  
  8.     }  
  9.     return sum;  
  10. }  

比如想要求c【7】 

=c【7】+c【7-最低位】为c【6】

+c【6-最低位】为c【4】+c【4-最低位】为c【0】

所以每次加都是a【k-k&-k】


#include <stdio.h>
#include <string.h>
int a[1000005],c[1000005];
inline void change(int i,int num,int N)
{
int j;
for(j=i;j<=N;j=j+j&(j^(j-1)))
{
c[j]=c[j]+num;
}
}
inline int Sum(int u)
{
int s=0;
while(u>0)
{
s=s+c[u];
u=u-u&(u^(u-1));
}
return s;
}
int main()
{
int N,M;
scanf("%d%d",&N,&M);
int i,j;
for(i=0;i<N;i++)
{
  scanf("%d",&a[i+1]);
change(i+1,a[i+1],N);
}
char s[10];
int a,b;
for(i=0;i<M;i++)
{
scanf("%d",&s);
scanf("%d%d",&a,&b);
if(strcmp(s,"QUERY")==0)
{
printf("%d\n",Sum(b)-Sum(a-1));
}
else if(strcmp(s,"ADD")==0)
{change(a,b,N);}
}
return 0;
}

上面的过程 逐过程显示在change的时候是出不来循环的

而改成lowbit函数则可以正常运行 原因是什么还没有清楚 上面求lowbit的时候用的方法是j&(j^(j-1)) 如果改成x&(-x)也是可以的


ac答案

#include<stdio.h>  
#include<string.h>  
int a[1000005],c[1000005];  
inline int lowbit(int x)//树状数组下标的确定  
{  
    return x&(x^(x-1));  
}  
int Sum(int u)//求和  
{  
    int s=0;  
    while(u>0)  
    {  
        s+=c[u];  
        u-=lowbit(u);  
    }  
    return s;  
}  
inline void change(int x,int m,int n)//改变值  
{  
    for(int i=x;i<=n;i+=lowbit(i))  
        c[i]+=m;  
}  
int main()  
{  
    int i,N,M;  
    scanf("%d %d",&N,&M);  
    for(i=1;i<=N;i++)  
    {  
        scanf("%d",&a[i]);  
        change(i,a[i],N);  
    }  
    char s[20];  
    int a,b;  
    for(i=0;i<M;i++)  
    {  
        scanf("%s",s);  
        scanf("%d %d",&a,&b);  
        if(strcmp(s,"QUERY")==0)  
            printf("%d\n",Sum(b)-Sum(a-1));  
        else if(strcmp(s,"ADD")==0)  
            change(a,b,N);  
    }  
    return 0;  
}

士兵杀敌(三)

杀敌数最高的人与杀敌数最低的人之间军功差值是多少。

这里用到RMQ算法,区间最值查询,普通算法时间复杂度o(n),当数据数量很大的时候,那么,普通的方法效率很低,这个时候使用RMQ算法来处理,那么,这个算法分两个步骤,第一步,预处理部分,因为,其问题满足最优化原理,使用动态规划,得到区间的最值,其中,时间复杂度o(nlogn),第二部分,也就是查找部分,其中,时间复杂度达到o(1)

在线算法(ST算法),所谓在线算法,是指用户每输入一个查询便马上处理一个查询。该算法一般用较长的时间做预处理,待信息充足以后便可以用较少的时间回答每个查询。ST(Sparse Table)算法是一个非常有名的在线处理RMQ问题的算法,它可以在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。

这里引用http://blog.csdn.net/liang5630/article/details/7917702

注意要熟悉动态规划的使用

ac答案http://blog.csdn.net/sevenmit/article/details/8857035

士兵杀敌(四)

区间更新 这里用到区间树 是在红黑树的基础上

所以先了解红黑树

基础知识 二叉树 链表

树和二叉树的区别 树的度没有限制 但是二叉树最多只能有两个 2.树的子树没有左右划分 

例如


一些术语 

父节点 子节点 兄弟节点 节点的度

树的度:树中最大的结点度

叶节点 和分支节点

树的表示方法

例如上面的树就表示成(A((B(D),(E)),(C(F),(G)))

二叉树的类型

比较完美的两种类型 

完全二叉树 除了最后一层之外 就是满二叉树 最后一层的叶节点必须是从左都右一字排开

满二叉树:全部都是满的  然后叶子节点都在最后一层

二叉树的存储和遍历类型

有两种存储方式 

第一种是数组


但是这样会浪费空间 比较适合满二叉树和完全二叉树

第二种是链表存储方式 

其中有二叉链表结构和三叉链表结构

这里拓展链表

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表双向链表以及循环链表

先看单向链表的建立 插入 删除 查找

#include <iostream>
using namespace std;
void creat();
void insert();
void del();
void print();
struct data
{
int num;
struct data *next;
};
data *head,*temp,*now;
void creat()
{
cout<<"input n:"<<endl;
int n;
cin>>n;
head=new data;
now=head;int i=0;
cout<<"input n numbers:"<<endl;
cin>>head->num;
while(i<(n-1))
{
temp=new data;
cin>>temp->num;
now->next=temp;
now=temp;
i++;
}
//finally put the Null to the end
now->next=NULL;
}
void insert()
{
cout<<"input the position that you want to insert:"<<endl;
int pos;
cin>>pos;
now=head;
while(pos-->1)
{
now=now->next;
}//insert behind the pos one
data *insert;
insert=new data;
cout<<"input the number you want to insert:"<<endl;
cin>>insert->num;
insert->next=now->next;//if the pos is the tail ,that's the same;
now->next=insert;
}
void print()
{
now=head;
while(now!=NULL)
{cout<<now->num<<" ";now=now->next;}
cout<<endl;
}
void delnum()
{
cout<<"input the pos that you want to delete:"<<endl;
int pos;
cin>>pos;
now=head;
if(pos==1)
{head=head->next;delete now;}
else{
while(pos-->2)
{
now=now->next;
}
temp=now->next;
now->next=now->next->next;
delete temp;
}
}
int main()
{
     creat();
print();
insert();
print();
delnum();
print();
system("pause");
return 0;
}

要注意那些控制循环的地方

现在了解二叉链表表示二叉树

通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。其结点结构为:

详细看http://www.cnblogs.com/fthjane/p/4746186.html

再了解二叉树的遍历

http://blog.csdn.net/simplehap/article/details/63259845

二叉树的一些操作

http://blog.csdn.net/sysu_arui/article/details/7865876

了解红黑树

http://blog.csdn.net/sysu_arui/article/details/7865876

了解区间树(线段树)

https://www.douban.com/note/211921266/

答案

http://www.cnblogs.com/javawebsoa/archive/2013/04/23/3038614.html

解释:

#include <iostream>
using namespace std;
const int N=100002;
struct TREE
{
int LEFT;
int RIGHT;
int num;
};
TREE tree[N<<2];//乘以2的平方?为什么? 然后是一个数组的大小


void Build(int root,int l,int r)//构建区间树
{
tree[root].LEFT=l;//搞清楚区间树用数组是怎样存储的 就是这里跟区间树的那些max以及结构有什么关系
tree[root].RIGHT=r;
tree[root].num=0;
if(l<r)
{
int m=(l+r)>>1;//除以2的意思
Build(root<<1,l,m);
Build(root<<1|1,m+1,r);//root<<1|1与1进行或运算 10变成11 巧妙


}
}//起始数据都是0 而且没有区间数据的情况下怎么构建??
void Insert(int root,int b,int e,int v)
{
//覆盖了表示的区间
if(b<=tree[root].LEFT&&tree[root].RIGHT<=e)
{
tree[root].num+=v;//如果覆盖了这个区间的话 就把这个区间的加上v
return ;
}
int m=(tree[root].LEFT+tree[root].RIGHT)>>1;//?这是右移运算符 尖尖向哪一边就是什么运算符
//[b,e]位于root的左半区间
if(e<=m)
Insert(root<<1,b,e,v);
else if(b>m)
    Insert(root<<1|1,b,e,v);//位于右区间 所以m是用来区别左右区间的
else //与两个区间都相交
{
Insert(root<<1,b,m,v);
Insert(root<<1|1,m+1,e,v);
}
}
int Query (int root,int x)
{
if(tree[root].LEFT==tree[root].RIGHT)
return tree[root].num;
int m=(tree[root].LEFT+tree[root].RIGHT)>>1;
int sum=tree[root].num;
if(x<=m)
sum+=Query(root<<1,x);
else sum+=Query(root<<1|1,x);
return sum;
}//从最开始开始这棵树就按照二分有很多个区间 然后如果有这个区间的就直接在这个区间上加 如果没有就分开然后继续中区间 所以最后要累加
int main()
{
int n,m;//n条命令 m个士兵
int b,e,v;
char str[10];
int i;
while(cin>>n>>m)
{
Build(1,1,m);//root开始是1 从1到m
for(i=0;i<n;++i)
{
cin>>str;
if(str[0]=='A')
{
cin>>b>>e>>v;
Insert(1,b,e,v);//从b到e都加v
}
else 
{
cin>>b;
cout<<Query(1,b)<<endl;
}
}
}
  return 0;
}

注意 这里有几个技巧的东西:树是怎么用数组来存储的 root<<1以及root<<1|1的使用

在增加的时候 是遇到对的区间就加

最后查询的时候是找到这个数字在的区间就累加

南阳的最佳答案

 
#include<cstdio>
#include<cstring>
const int M=1000010;
int data[M];
int Max;
inline int LowBit(int n)
{
return n&(-n);
}
void Plus(int n,int value) //前n项每项增加value
{
while(n>0)
{
data[n]+=value;
n-=LowBit(n);
}
}
int Get(int n) //获取每个位置的值
{
int sum=0;
while(n<=Max)
{
sum+=data[n];
n+=LowBit(n);
}
return sum;
}
char cmd[50];
int main()
{
int n,a,b,v;
scanf("%d%d",&n,&Max);
while(n--)
{
scanf("%s",cmd);
if(!strcmp(cmd,"ADD"))
{
scanf("%d%d%d",&a,&b,&v);
Plus(a-1,-v);
Plus(b,v);
}
else
{
scanf("%d",&a);
printf("%d\n",Get(a));
}
}
}        

但是没有理解为什么lowbit那里是将前面n项都加value??

士兵杀敌(五)

#include <cstdio>
#define SIZE 100010
int sum[SIZE];
int main()
{
int N,C,Q;
scanf("%d%d%d",&N,&C,&Q);
int i;
for(i=0;i<C;i++)
{
int Mi,Ni,Ai;
scanf("%d%d%d",&Mi,&Ni,&Ai);
sum[Mi-1]+=-Ai;
sum[Ni]+=Ai;
}
for(i=N;i>0;i--)
{
sum[i]+=sum[i+1];//注意顺序有没有影响
}
sum[0]=0;
for(i=1;i<=N;i++)
{
sum[i]+=sum[i-1];//累加的过程
sum[i]%=10003;
}
for(i=0;i<Q;i++)
{
int m,n;
scanf("%d%d",&m,&n);
printf("%d\n",(sum[n]-sum[m-1]+10003)%10003);
}
return 0;
}
思路真的很难想出来 晕死!


原创粉丝点击