nyoj 士兵杀敌系列

树状数组(二叉索引树BIT)

对于正整数x,我们定义lowbit(x)为x的二进制表达式中最右边的1所对应的值,lowbit(x)=x&-x


对于结点i,如果他是左结点,于是其父结点为i+lowbit(i)

如果是右结点,父结点为i-lowbit(i)

具体讲解,见于:http://hawstein.com/posts/binary-indexed-trees.html

两个操作:


//执行add操作
void add(int i,int v)
{
	while(i<=n)
	{
		a[i]+=v;
		i+=lowbit(i);
	}
}


//得到前i项和
 int sum(int i)
{
	int sum=0;
	while(i>0)
	{
		sum+=a[i];
		i-=lowbit(i);
	}
	return sum;
}


RMQ问题:范围最小值:

令d(i,j)表示从i开始,长度为2^j的一段元素中的最小值

d(i,j)=min{ d(i,j-1),d(i+2^(j-1),j-1)}       (换成max就是范围最大值)

预处理;

for(int i=1;i!=20;i++)
		for(int j=1;j<=n;j++)
			if(j+(1<<i)-1<=n)
			{
				FMAX[j][i]=max(FMAX[j][i-1],FMAX[j+(1<<(i-1))][i-1]);//最大
				FMIN[j][i]=min(FMIN[j][i-1],FMIN[j+(1<<(i-1))][i-1]);//最小
			}


查询:


scanf("%d%d",&a,&b);
		int len=(int)(log(b-a+1.0)/log(2.0));
		printf("%d\n",max(FMAX[a][len],FMAX[b-(1<<len)+1][len])-min(FMIN[a][len],FMIN[b-(1<<len)+1][len]));//相差





士兵杀敌(一)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 3
描述

南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的。

小工是南将军手下的军师,南将军现在想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧。

注意,南将军可能会问很多次问题。

输入
只有一组测试数据
第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示南将军询问的次数(1<M<100000)
随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)
随后的M行每行有两个整数m,n,表示南将军想知道第m号到第n号士兵的总杀敌数(1<=m,n<=N)。
输出
对于每一个询问,输出总杀敌数
每个输出占一行
样例输入
5 2
1 2 3 4 5
1 3
2 4
样例输出
6
9

数据并没有变动,直接记录即可


 
#include<cstdio>
const int MAX=1000010;
int sum[MAX];
int main()
{
	int N,q,m,n;
	scanf("%d%d",&N,&q);
	for(int i=1;i<=N;++i)
	{
		scanf("%d",&sum[i]);
		sum[i]+=sum[i-1];
	}
	for(int i=0;i!=q;++i)
	{
		scanf("%d%d",&m,&n);
		printf("%d\n",sum[n]-sum[m-1]);
	}
}        




士兵杀敌(二)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 5
描述

南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的。

小工是南将军手下的军师,南将军经常想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧。

南将军的某次询问之后士兵i可能又杀敌q人,之后南将军再询问的时候,需要考虑到新增的杀敌数。

输入
只有一组测试数据
第一行是两个整数N,M,其中N表示士兵的个数(1<N<1000000),M表示指令的条数。(1<M<100000)
随后的一行是N个整数,ai表示第i号士兵杀敌数目。(0<=ai<=100)
随后的M行每行是一条指令,这条指令包含了一个字符串和两个整数,首先是一个字符串,如果是字符串QUERY则表示南将军进行了查询操作,后面的两个整数m,n,表示查询的起始与终止士兵编号;如果是字符串ADD则后面跟的两个整数I,A(1<=I<=N,1<=A<=100),表示第I个士兵新增杀敌数为A.
输出
对于每次查询,输出一个整数R表示第m号士兵到第n号士兵的总杀敌数,每组输出占一行
样例输入
5 6
1 2 3 4 5
QUERY 1 3
ADD 1 2
QUERY 1 3
ADD 2 3
QUERY 1 2
QUERY 1 5
样例输出
6
8
8
20

算法:树状数组:

 
#include<stdio.h>
int a[1000002];
int n,m;

int lowbit(int t)
{
	return t & ( -t ) ;
}


//执行add操作
void add(int i,int v)
{
	while(i<=n)
	{
		a[i]+=v;
		i+=lowbit(i);
	}
}


//得到前i项和
 int sum(int i)
{
	int sum=0;
	while(i>0)
	{
		sum+=a[i];
		i-=lowbit(i);
	}
	return sum;
}
 
void begin()  //根据树状数组的节点规律,初始化数组--或者在进行输入数据时,利用add方法进行初始化
{
	int t,i,j;
	for(i=n;i>=1;i--)
	{
		t=0;
		for(j=i-lowbit(i)+1;j<=i;j++)
		{
			t+=a[j];
		}
		a[i]=t;
	}
}

int main()
{
	int c,b,i,t;
	char s[6];

	scanf("%d%d",&n,&m);

	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);

	begin();

	while(m--)
	{
		scanf("%s %d %d",s,&c,&b);

		if(s[0]=='Q')
		{
			printf("%d\n", (sum(b) - sum(c-1)));	
		}
		else
		{
			add(c,b);
		}
	}
	return 0;
}



        



士兵杀敌(三)

时间限制: 2000 ms  |  内存限制: 65535 KB
难度: 5
描述

南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比较,计算出两个人的杀敌数差值,用这种方法一方面能鼓舞杀敌数高的人,另一方面也算是批评杀敌数低的人,起到了很好的效果。

所以,南将军经常问军师小工第i号士兵到第j号士兵中,杀敌数最高的人与杀敌数最低的人之间军功差值是多少。

现在,请你写一个程序,帮小工回答南将军每次的询问吧。

注意,南将军可能询问很多次。

输入
只有一组测试数据
第一行是两个整数N,Q,其中N表示士兵的总数。Q表示南将军询问的次数。(1<N<=100000,1<Q<=1000000)
随后的一行有N个整数Vi(0<=Vi<100000000),分别表示每个人的杀敌数。
再之后的Q行,每行有两个正正数m,n,表示南将军询问的是第m号士兵到第n号士兵。
输出
对于每次询问,输出第m号士兵到第n号士兵之间所有士兵杀敌数的最大值与最小值的差。
样例输入
5 2
1 2 6 9 3
1 2
2 4
样例输出
1
7

RMQ问题:

 
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int MAX=100010;
int FMAX[MAX][20],FMIN[MAX][20];

int main()
{
	int n,q,a,b,v;
	cin>>n>>q;
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&FMAX[i][0]);
		FMIN[i][0]=FMAX[i][0];
	}
	for(int i=1;i!=20;i++)
		for(int j=1;j<=n;j++)
			if(j+(1<<i)-1<=n)
			{
				FMAX[j][i]=max(FMAX[j][i-1],FMAX[j+(1<<(i-1))][i-1]);
				FMIN[j][i]=min(FMIN[j][i-1],FMIN[j+(1<<(i-1))][i-1]);
			}
	for(int i=0;i!=q;++i)
	{
		scanf("%d%d",&a,&b);
		int len=(int)(log(b-a+1.0)/log(2.0));
		printf("%d\n",max(FMAX[a][len],FMAX[b-(1<<len)+1][len])-min(FMIN[a][len],FMIN[b-(1<<len)+1][len]));
	}
}        



士兵杀敌(四)

时间限制: 2000 ms  |  内存限制: 65535 KB
难度: 5
描述

南将军麾下有百万精兵,现已知共有M个士兵,编号为1~M,每次有任务的时候,总会有一批编号连在一起人请战(编号相近的人经常在一块,相互之间比较熟悉),最终他们获得的军功,也将会平分到每个人身上,这样,有时候,计算他们中的哪一个人到底有多少军功就是一个比较困难的事情,军师小工的任务就是在南将军询问他某个人的军功的时候,快速的报出此人的军功,请你编写一个程序来帮助小工吧。

假设起始时所有人的军功都是0.

输入
只有一组测试数据。
每一行是两个整数T和M表示共有T条指令,M个士兵。(1<=T,M<=1000000)
随后的T行,每行是一个指令。
指令分为两种:
一种形如
ADD 100 500 55 表示,第100个人到第500个人请战,最终每人平均获得了55军功,每次每人获得的军功数不会超过100,不会低于-100。
第二种形如:
QUERY 300 表示南将军在询问第300个人的军功是多少。
输出
对于每次查询输出此人的军功,每个查询的输出占一行。
样例输入
4 10
ADD 1 3 10
QUERY 3
ADD 2 6 50
QUERY 3
样例输出
10
60

树状数组:

 
#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));
		}
	}
	

}        



士兵杀敌(五)

时间限制: 2000 ms  |  内存限制: 65535 KB
难度: 5
描述

南将军麾下有百万精兵,现已知共有M个士兵,编号为0~M,每次有任务的时候,总会有一批编号连在一起人请战(编号相近的人经常在一块,相互之间比较熟悉),最终他们获得的军功,也将会平分到每个人身上,这样,有时候,计算他们中的哪一个人到底有多少军功就是一个比较困难的事情。

在这样的情况下,南将军却经常会在许多次战役之后询问军师小工第i号士兵到第j号士兵所有人的总军功数。

请你帮助军师小工回答南将军的提问。

输入
只有一组测试数据
第一行是三个整数N,C,Q(1<=N,C,Q<=1000000),其中N表示士兵的总数。
随后的C行,每行有三个整数Mi,Ni,Ai(0<=Mi<=Ni<=N,0<=Ai<=100),表示从第Mi号到第Ni号士兵所有人平均增加了Ai的军功。
再之后的Q行,每行有两个正正数m,n,表示南将军询问的是第m号士兵到第n号士兵。
输出
请对每次询问输出m号士兵到第n号士兵的总军功数,由于该数值可能太大,请把结果对10003取余后输出
样例输入
5 3 2
1 3 2
2 4 1
5 5 10
1 5
2 3
样例输出
19
6

把平均值放在区间末尾,在区间之前的一个元素减去,然后统计的时候就可以从后向前加,加出来的就是各自的实际值,然后就可以变成士兵杀敌一的那个问题


 
#include <cstdio>

#define SIZE 1000010

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;
}        






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值