线段树
一种略复杂,但是很高效的查找方法。总体思路类似于二分法。对于一组数据每一次都分成两份,并且由此建立一个二叉树。个人感觉比较有趣的是刚好凭借靠着这种2和3整除2都为1这种数字关系可以找回自己的上一个节点。比较简单的应用就是例题POJ - 1166。求一串数字的指定区间的和。从根节点1开始。节点1储存所有的和,2储存1前半部分的和,3储存1后半部分的和。从这里继续,4储存2前半部分的和,5储存2后半部分的和。由此类推一次向下展开,一直到该线段树本身的和是指向单个数字的时候,也就是找到了叶节点,在这里结束。
带上自己写的代码,有利于理解
#include<stdio.h>
#define N 50010
int t,n,k[N],a,b,mm,nn,kk[N];
char m[10];
struct tree{
int l,r,sum=0;
}trees[N*4];
void build(int l,int r,int i){
if(l==r){
trees[i].l=trees[i].r=l;
trees[i].sum=k[l];
kk[l]=i;//标记叶节点所处的位置。
return;
}
else{
int mid=l+r>>1;
build(l,mid,i<<1);
build(mid+1,r,(i<<1)+1);
trees[i].sum=trees[i<<1].sum+trees[(i<<1)+1].sum;
trees[i].l=l;
trees[i].r=r;
}
}
void rebuild(int x,int b){
while(x>0){
x=x>>1;
trees[x].sum+=b;
}
}
void result(int l,int r,int i){
if(l<=trees[i].l&&r>=trees[i].r){
nn+=trees[i].sum;
}
else{
int mid=(trees[i].l+trees[i].r)>>1;
if(mid>=l&&mid<r){
result(l,mid,i<<1);
result(mid+1,r,i<<1|1);
}
else if(mid<l){
result(l,r,i<<1|1);
}
else if(mid>=r){
result(l,r,i<<1);
}
}
}
int main(void){
scanf("%d",&t);
while(t--){
printf("Case %d:\n",++mm);
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&k[i]);
}
build(0,n-1,1);
/*for(int i=0;i<30;i++){
printf("* %d %d *%d**%d\n",trees[i].l,trees[i].r,i,trees[i].sum);
}*/
while(~scanf("%s",m)&&m[0]!='E'){
scanf("%d%d",&a,&b);
a--;
if(m[0]=='A'){
trees[kk[a]].sum+=b;
rebuild(kk[a],b);
}
else if(m[0]=='S'){
trees[kk[a]].sum-=b;
rebuild(kk[a],-b);
}
else if(m[0]=='Q'){
b--;
nn=0;
result(a,b,1);
printf("%d\n",nn);
}
}
}
}
其实接下来还有好多种线段树的关系,比如找最大值,找最大子序列等等,视不同的情况,复杂程度也会不一样,所以主要了解一下数学模型是什么样子,具体题目具体分析。
贪心
这怎么说呢,并不是一种数学模型,而是一种思考的方式,单独列出来其实我个人觉得可能,大概,也许,应该,maybe没太大必要吧。这种就是自己在做题的时候有没有考虑到这一点,说是要有一种固定的套路或者模型的话是没有的。总体来说,大体是,当一个问题可以拆解为各个不相互影响的小问题的时候,拆分开来并且解出每个小问题的最优解,之后再把每个最优解组合起来。
dp
说实话,这几天的贪心,dp还有刚学的hash。尤其是dp和hash,都是一知半解的。还需要再理解理解。怎么说呢,有一些例子我会很好懂,但另外一些,开始抽象了,是慢慢有点难理解了。这里dp的话最直观的问题,也是我最先理解的问题就是例题HDU 2602。听说这个问题和背包问题差不多。不过我反正是对该问题的模型是理解了的,然后再看别的例题的时候,发现模型一样,但是多多少少还是有点不理解为什么可以这样。不过dp的核心思路应该还是局部最优解以及迭代递归。类似于这一题中,每一块骨头有自己不同的重量和不同的价值,问在固定的背包容量中,如何装下最大总价值的骨头。大体思路我感觉也是贪心。先放第一块骨头,然后背包的容量从零到最大值,放不下的丢掉,放得下就放下。之后再放第二块骨头,背包的容量也从零到最大值开始,放不下丢掉,放得下但只能放下第一块或者第二块的时候,考虑该背包容量的情况下,放哪个价值最高,取最高的那一块。如果两块都能放下,那最好,就都放下。如此向后迭代的话,总结来说就是,遇到一块骨头,能放下就放下,放不下就判断,是拿掉一块骨头好,还是就不要这个东西了。这里放张图,还有我的代码,比较好理解,真的我看了图和代码,就瞬间顿悟了。
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int main(void){
int t,m,n,w[1005],v[1005];
static int mm[1005][1005];
scanf("%d",&t);
while(t--){
memset(mm,0,sizeof(mm));
scanf("%d%d",&m,&n);
for(int i=0;i<m;i++){
scanf("%d",&v[i]);
}
for(int i=0;i<m;i++){
scanf("%d",&w[i]);
}
for(int i=1;i<=m;i++){
for(int j=0;j<=n;j++){
if(j>=w[i-1]){
mm[i][j]=max(mm[i-1][j],mm[i-1][j-w[i-1]]+v[i-1]);
}
else{
mm[i][j]=mm[i-1][j];
}
}
}
printf("%d\n",mm[m][n]);
}
}
之后还有一题是什么记忆化搜索,我也感觉挺妙的,直接放代码吧。
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<string.h>
#include<stdio.h>
using namespace std;
int m,n,k[100][100],num[100][100],mm=0;
int x[4]={0,0,1,-1};
int y[4]={1,-1,0,0};
int find(int a,int b){
int z=1;
if(num[a][b]){
return num[a][b];
}
else{
for(int i=0;i<4;i++){
int ii=a+x[i];
int jj=b+y[i];
if(ii>=0&&ii<m&&jj>=0&&jj<n&&k[ii][jj]<k[a][b]){
z=max(z,find(ii,jj)+1);
}
}
num[a][b]=z;
return z;
}
}
int main(void){
scanf("%d%d",&m,&n);
memset(num,0,sizeof(num));
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
scanf("%d",&k[i][j]);
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
num[i][j]=find(i,j);
if(mm<num[i][j]){
mm=num[i][j];
}
}
}
printf("%d\n",mm);
}