分类: 学习笔记 - CheaSim Blog

TCP,UDP,Socket学习笔记

Linux自学网络

What is Socket 系统调用

TCP UDP
是否连接 面上连接 面上非连接
传输可靠性 可靠 不可靠
应用场合 传输大量的数据,对可靠性要求较高的场景 传输少量数据,对可靠性要求不高的场景
速度

我是Makefile

我是Makefile

我是makefile而不是makelove。最近学校开了Linux这门课,于是我就开始自学Linux了,看的是《Linux就是这个范儿》。实名制推荐,语言又风趣又实在。

什么是Makefile

作为一名程序员,我们第一个程序大概率是通过vs或者是vc6.0来写的。这些都是IDE集成开发环境,他们帮我们做了很多事情,很多底层的事情。其中比较重要的就是编译这个命令了。如果像我参加acm的,会知道如果经常只是写一个单cpp文件的话,我们只需要使用GCC中的g++命令就可以完成将cpp文件编译成可执行文件,但是如果我们是写一个项目呢?如果我们的项目中有好几个头文件,好几个cpp文件呢?这时候makefile就出现了。

Makefile的作用就是自动化编译,一旦我们写好了makefile文件,只需要一个make命令,整个工程就能自动编译。Makefile定义了一系列的规则,来指定哪些文件需要先编译,哪些文件需要后编译,就像一个shell脚本一样。

IDE是高手的挡泥板,却是新手的遮阳伞。

我在南大的编译原理课件中就看到,他们项目的要求也是要用Makefile来完整的做一个项目的。南大还是南大啊。

Makefile的基本概念

目标、条件和命令

对于Makefile来说,最最重要的就是目标、条件和命令这三大要素。

  • 目标是make要产生的东西或者是要做的事情
  • 条件是用于产生目标所需要的文件
  • 命令是有条件转化为目标的方法

举个例子

1
2
main.o:main.c line.h buffer.h tedef.h
cc -c -o main.o main.c

main.o就是目标,main.c line.h buffer.h tedef.h就是条件,最下面那句”cc -c -o main.o main.c”就是命令。

all命令就是Makefile的默认目标。

基本语法

Makefile是看成是一种解释型语言,一般解释型语言都是采用“自顶向下”的解释逻辑。

tip:所有命令都是需要以\tab开头的,规定如此

变量

作为一种类似语言的东西,那么变量肯定是少不了啊。Makefile中的变量类似于宏替代,采用的语法。

1
CC := gcc -g

这就定义了一个变量,变量的名称是CC,变量的值是”gcc -g”,我们可以用”$()或者${}”来替代。

自动变量

Makefile中有特殊变量,他们可以自动取用一条规则中目标和条件中的元素,这样就可以节省输入文件名和条件文件名的力气。ps:确实在vim一键编译中我也看到了类似的东西。

并且自动变量不需要加$().

[acm]hash学习笔记

hash学习

hash是一种比较常见的处理字符串的手法。在acm题目中,经常使用hash来处理字符串。比如判断一个子串在一个字符串中出现过几次。就可以使用hash来处理。

hash主要的方法就是把不同的字符串对应到不同的数字,之后通过数组来确定字符串是否出现过,从而减少检索字符串的时间。

常见的hash方法

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef unsigned long long ull;
ull base[maxn],has[maxn];
void init(){
base[0] = 1;
rep(i,1,maxn) base[i] = base[i-1] * 131;
scanf("%s",s);
int len = strlen(s);
has[len] = 0;
per(i,0,len) has[i] = has[i+1]*131 + (s[i]-'a'+1);
}
ull get_hash(int i,int L){
return has[i] - has[i+L]*base[L];
}

练习

hdoj4821 String

题目大意就是求$S$子串中,

  • 长为$M*L$
  • 其中每一段连续的$L$长度的子串都各不相同。

就是使用hash来做。但是我TLE了。因为暴力了每一段的hash值。

其实由于如果$S$的长度很长的话。其中很多段子段的hash值是被反复求的。所以我们在求完一段$M$个的字符串之后,就可以按照这个把第一段去除。之后选择后一段。这样重复下去。

Wa1:等于号想清楚了在判定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
typedef unsigned long long ull;
const int maxn = 1e5+10;
ull xp[maxn],Hash[maxn];
void init(){
xp[0] = 1;
rep(i,1,maxn) xp[i] = xp[i-1] * 175;
}
ull get_Hash(int i,int L){
return Hash[i] - Hash[i+L] * xp[L];
}
int n,m;
char s[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
init();
// m = length of L
while(~scanf("%d%d",&n,&m)){
scanf("%s",s);
int len = strlen(s);
Hash[len] = 0;
per(i,0,len){
Hash[i] = Hash[i+1]*175+(s[i]-'a'+1);
}
int ans = 0;
for(int i=0;i<m && i<=len-m*n;i++){
map<ull,int> mx;
for(int j=i;j<i+m*n;j+=m){
ull temp = get_Hash(j,m);
mx[temp] ++;
}
if(mx.size() == n) ans++;
for(int j = i+n*m;j+m<=len;j+=m){
ull temp = get_Hash(j-n*m,m);
mx[temp]--;
if(mx[temp] == 0) mx.erase(temp);
temp = get_Hash(j,m);
mx[temp]++;
if(mx.size() == n) ans++;
}
}
printf("%d\n",ans);
}
return 0;
}

hdoj4080 Stammering Aliens

题目大意是找到字符串$S$中,出现次数大于$m$次并且最长的字符串。

我是用hash做的,不过时间是4800ms,差点超时。用别人后缀数组+二分,200ms可以过。。

就酱吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
//head

const int maxn = 4e4+10;
ull base[maxn],has[maxn];
void init(){
base[0] = 1;
rep(i,1,maxn) base[i] = base[i-1] * 131;
}
ull get_hash(int i,int L){
return has[i] - has[i+L] * base[L];
}

int m,n;
char s[maxn];
int check(int len){
map<ull,int> mx;
int ans = -1;
rep(i,0,n-len+1) {
ull temp = get_hash(i,len);
mx[temp] ++;
if(mx[temp] >= m){
ans = i;
}
}
return ans;
}
int right_most = 0;
int res = 0;
void solve(int l,int r){
int mid;
while(l<=r){
mid = l+r>>1;
int temp = check(mid);
if(temp != -1){
l = mid + 1;
res = mid;
right_most = temp;
}else{
r = mid - 1;
}
}
}
int main(){
#ifdef LOCAL
freopen("2.in","r",stdin);
#endif
init();
while(scanf("%d",&m) && m){
scanf("%s",s);
n = strlen(s);
has[n] = 0;
per(i,0,n) has[i] = has[i+1] * 131 + s[i] - 'a' + 1;
right_most = 0; res = 0;
solve(1,n+1);
if(res == 0){
puts("none");
}else{
printf("%d %d\n",res,right_most);
}
}

return 0;
}

reference:

https://blog.csdn.net/u012965373/article/details/38929637

https://blog.csdn.net/ck_boss/article/details/47066727

树链剖分学习笔记

树链剖分学习笔记

题型

当一道题在询问两点之间修改后的权值或者是两点之间边修改后的权值。这类题目往往我们一开始就会思考用线段树来维护,但是线段树无法维护一颗树上的链,所以我们需要来将树上的链剖分下来。

轻重边剖分

将树中的边分为两部分,轻边和重边,用$size[u]$来记录以$u$为根的子树的节点个数,之后令$v$为$u$的儿子中$size$最大的一个,那么我们就可以将$u$的边分为重边和轻边了(重边就是$(u.v)$)。

之后我们就可以得到如下性质

  • 如果$(u,v)$为轻边,那么$size[v] \leq size[u]/2$。因为如果大于的话,他就成为重边了。
  • 从根到某一点的路径上轻边的个数不会超过$logN$。假设从$root->v$,那么$size[v]>=1$,可以递推出$size[root]>=2^k$,其中$k$为根到点进过的点数。

实现

初始化操作

首先我们定义以下几个数组

  • $size[v]$表示以$v$为根的子树的节点数
  • $dep[v]$表示$v$的深度(根的深度为1)
  • $top[v]$表示所在重链的顶端节点
  • $fa[v]$表示父亲节点,$son[v]$表示重儿子节点
  • $w[v]$表示与父亲的连边

然后我们使用两个dfs来把数组确定下来。

第一个dfs求出fa,dep,size,top,w。

第二个dfs

  1. 对于循环到的$v$,当$son[v]$存在的时候,也就是非叶子节点,那么$top[son[v]] = top[v]$是显而易见的。在线段树中我们规定孩子节点的重边必须在父亲节点的后面,那么$w[son[v]]=totw+1$,其中$totw$表示线段树中加入的最后一条边的位置。之后进行递归dfs(son[v]);
  2. 对于$v$的轻孩子,那么他们没有身处重链所以$top[x]=x$。那么我们为了线段树好看一点也将他加入线段树。$w[x]=totw+1$。

经过两个dfs我们把所有的数组都给他确认了。

这代码量该有多大啊,对于常年只写70行就要debug很久的我来说。

更新操作

因为我们其实也是慢慢修改路径上的边的,所以我们先求个LCA。之后更新$u,v$到祖先节点。方法意会吧,或者看代码。

查询操作

跟更新操作很像,就是在过程中不再更新,而是求值。

例题

Aragorn’s Story

+1 更新节点的时候必须在id[x]更新

+1 大于小于没弄明白,在change函数中。

+1 update的时候以为是线段树直接从左边更新到右边,其实是在那个重边更新,所以是update(val,id[x],id[t1],1,totw,1);这样的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
void swap(int &a,int &b){
int temp = a;
a = b;
b = temp;
}
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 5e4+10;
int size[maxn],dep[maxn],top[maxn],fa[maxn],id[maxn],son[maxn];
int head[maxn],w[maxn];
int n,cnt,totw,m;
struct edge{
int next,to;
}G[maxn<<1];
int find(int x){ return x==fa[x]?x:find(fa[x]);}
void addedge(int u,int v){
G[cnt].next = head[u];
G[cnt].to = v;
head[u] = cnt++;
}
void dfs1(int u,int f){
size[u] = 1;
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==f) continue;
dep[v] = dep[u]+1;
fa[v] = u;
dfs1(v,u);
if(size[v] > size[son[u]]) son[u] = v;
size[u]+=size[v];
}
}
void dfs2(int u,int topu){
top[u] = topu;
id[u] = ++totw;
if(son[u]) dfs2(son[u],top[u]);
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==fa[u] || v==son[u]) continue;
dfs2(v,v);
}
}
int data[maxn<<2],lazy[maxn<<2];
void pushdown(int rt){
if(lazy[rt]!=0){
data[rt<<1] += lazy[rt];
data[rt<<1|1] += lazy[rt];
lazy[rt<<1] += lazy[rt];
lazy[rt<<1|1] += lazy[rt];
lazy[rt] = 0;
}
}
void build(int l,int r,int rt){
lazy[rt] = 0;
if(l==r){
data[rt] = w[l];
return;
}
int mid = l+r>>1;
build(lson); build(rson);
}
int query(int pos,int l,int r,int rt){
if(l==r && l==pos) return data[rt];
int mid = l+r>>1;
pushdown(rt);
if(pos<=mid) return query(pos,lson);
else return query(pos,rson);
}
void update(int val,int L,int R,int l,int r,int rt){
if(L<=l && r<=R){
lazy[rt] += val;
data[rt] += val;
return;
}
pushdown(rt);
int mid = l+r>>1;
if(L<=mid) update(val,L,R,lson);
if(mid<R) update(val,L,R,rson);
}
void change(int u,int v,int val){
int t1=top[u],t2=top[v];
while(t1!=t2){
if(dep[t1]<dep[t2]){
swap(t1,t2);
swap(u,v);
}
update(val,id[t1],id[u],1,totw,1);
u = fa[t1];
t1 = top[u];
}
if(dep[u]>dep[v]) swap(u,v);
update(val,id[u],id[v],1,totw,1);
}
int val[maxn];
int p;
char op[20];
void init(){
cnt = totw = 0;
memset(head,-1,sizeof(head));
dep[1] = fa[1] = size[0] = 0;
memset(son,0,sizeof(son));
}
int main(){
#ifdef LOCAL
freopen("4.in","r",stdin);
#endif
while(~scanf("%d%d%d",&n,&m,&p)){
init();
rep(i,1,n+1) scanf("%d",val+i);
rep(i,0,m){
int u,v;scanf("%d%d",&u,&v);
addedge(u,v); addedge(v,u);
}
dfs1(1,0);
dfs2(1,1);
rep(i,1,n+1) w[id[i]] = val[i];
build(1,totw,1);
while(p--){
scanf("%s",op);
if(op[0]=='Q'){
int x;scanf("%d",&x);
printf("%d\n",query(id[x],1,totw,1));
}else{
int u,v,val;scanf("%d%d%d",&u,&v,&val);
if(op[0]=='D') val = -val;
change(u,v,val);
}
}
}
return 0;
}

个人鄙见:

树链剖分就是将树一部分分成区间修改,一部分分成单点修改,但是单点修改的复杂度控制在了$O(logN)$。

Reference:

https://blog.csdn.net/dyx404514/article/details/8718249

LCA学习笔记

LCA学习笔记

祖先的定义

如果学习树结构就会明白除了根节点以外每个节点都有一个父节点,所以我们定义祖先为父节点的父节点,然后我们给出最近公共祖先的定义

一棵树祖先中到两个点距离最近的节点。

能够解决LCA问题的方式有很多,所以作为一个ACM选手,肯定是我全都要

解决方案

欧拉序

树的欧拉序是对树进行dfs的一种序列,他有两条性质

  • 在每个节点进和出都加入序列
  • 只要到达每一个节点就把他加入序列

那么我们寻找他们的最近公共祖先就可以从第一次出现第一个节点和第一次出现的第二个节点之间节点中深度最大的节点。

HDOJ[2586]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define pb push_back
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 4e4+200;
const int maxm = 2e2+10;
int dis[maxn];
int n,m;
struct node{
int next,to,val;
}G[maxn<<1];
int head[maxn];
int cnt;
vector<int> order;
void init(){
cnt = 0;
memset(head,-1,sizeof(head));
order.clear();
}
void add(int u,int v,int val){
G[cnt].next = head[u];
G[cnt].to = v;
G[cnt].val = val;
head[u] = cnt++;
}
void dfs(int u,int fa,int val){
order.pb(u);
dis[u] = val;
for(int i=head[u];~i;i=G[i].next){
int v = G[i].to;
if(v==fa) continue;
dfs(v,u,val+G[i].val);
order.pb(v);
}
}
int find(int p1,int p2){
int mmin = INF,res;
if(p1>p2){
int temp = p1;
p1 = p2;
p2 = temp;
}
rep(i,p1,p2+1){
if(dis[order[i]] < mmin){
mmin = dis[order[i]];
res = i;
}
}
return res+1;
}
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
init();
rep(i,0,n-1){
int u,v,val;
scanf("%d%d%d",&u,&v,&val);
add(u,v,val); add(v,u,val);
}
dfs(1,1,0);
int len = order.size();
rep(i,0,m){
int x,y; int p1=-1,p2=-1;
scanf("%d%d",&x,&y);
rep(j,0,len){
if(p1==-1&&x==order[j]) p1 = j;
if(p2==-1&&y==order[j]) p2 = j;
if(p1!=-1 && p2!=-1) break;
}
int lca = find(p1,p2);
//cout<<dis[lca]<<' '<<dis[x]<<' '<<dis[y]<<endl;
int ans = dis[x]-dis[lca]+dis[y]-dis[lca];
printf("%d\n",ans);
}

}

return 0;
}

Trajan算法

首先Trajan是一个离线算法,在一次遍历中把所有询问一次解决,时间复杂度$O(n+q)$。

大佬题解

我也想自己写啊,但是就是不会啊,自己想是不可能自己想的,只要抄抄别人的博客才能维持生活。

  1. 任选择一个点作为根节点,从根节点开始
  2. 遍历该节点的所有子节点,并标记这些子节点$v$已经被访问过
  3. 如果$v$还有子节点,返回2,否则就进入下一步
  4. 合并$v$到$u$。
  5. 寻找与当前点$u$有询问关系的点$v$
  6. 若是$v$已经被访问过了,则可以确定$u$和$v$的最近公共祖先为$v$被合并的父亲节点$fa[u]$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include<cstdio>
#include<vector>
#include<iostream>
#include<cstring>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int T,n,cnt;
const int maxn = 1e4+10;
struct node{
int next,to;
}G[maxn<<1];
int head[maxn],fa[maxn],vis[maxn],ans[maxn];
void init(){
cnt = 0;
memset(head,-1,sizeof(head));
memset(fa,0,sizeof(fa));
memset(vis,0,sizeof(vis));
rep(i,1,n+1) fa[i] = i;
}
void add(int u,int v){
G[cnt].to = v;
G[cnt].next = head[u];
head[u] = cnt++;
}
int find(int x){
if(fa[x]!=x) fa[x] = find(fa[x]);
return fa[x];
}
struct Query{
int to,id;
Query(){}
Query(int _to,int _id){
to = _to;
id = _id;
}
};
vector<Query> query[maxn];
void dfs(int now){
int u,v;
vis[now] = 1;
for(int i=head[now];~i;i=G[i].next){
v = G[i].to;
if(vis[v]) continue;
dfs(v);
fa[v] = now;
}
for(int i = 0;i<query[now].size();i++){
Query node = query[now][i];
u = node.to; int id = node.id;
if(vis[u] == 2){
ans[find(u)] ++;
}
}
vis[now] = 2;
}
int vvis[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
while(~scanf("%d",&n)){
memset(head,-1,sizeof(head));
cnt = 0;
memset(vis,0,sizeof(vis));
memset(vvis,0,sizeof(vvis));
memset(ans,0,sizeof(ans));
rep(i,1,n+1) query[i].clear();
rep(i,1,n+1) fa[i] = i;
int root = 0;
rep(i,0,n){
int u,x,v;scanf("%d",&u);
scanf("%*c%*c%d%*c",&x);
rep(i,0,x){
scanf("%d",&v);
add(u,v);
vvis[v] = 1;
}
}
rep(i,1,n+1){
if(vvis[i]==0){
root = i;
break;
}
}
int m;scanf("%d",&m);
char ch;
rep(i,0,m){
ch = getchar();
while(ch!='(') ch = getchar();
int u,v;scanf("%d%d",&u,&v);
query[u].push_back(Query(v,i));
query[v].push_back(Query(u,i));
while(ch!=')') ch = getchar();
}
dfs(root);
rep(i,1,n+1){
if(ans[i]){
printf("%d:%d\n",i,ans[i]);
}
}
}
return 0;
}

RMQ算法(ST表)

其实这个方法也就是欧拉序加上RMQ,在第一个解决方案中我们使用枚举来寻找他们的LCA,这也导致时间复杂度变成了$O(n^2)$。而寻找固定区间内的最小值是一个很普遍的问题,也就是RMQ。而解决RMQ有很多种$O(logn)$的方法,

  • 线段树、树状数组
  • ST表

他们均可以有效地解决区间最值问题。

例题[hdoj5044]

使用方法为 RMQ+正负tag

详情见我刷的LCA专题训练。

Reference:https://blog.csdn.net/czl_233/article/details/78368927

http://www.cppblog.com/menjitianya/archive/2015/12/10/212447.html

http://www.cnblogs.com/JVxie/p/4854719.html

web应用开发笔记

web应用开发

现代人的生活方式

  • 移动端,mobile
  • 浏览器,browser

10年前还是以客户端,client软件形式。

两个主流技术

  • .Net
  • J2ee

.Net过气了,用J2ee

J2ee的三个方面

  1. M 模型:负责数据方面的事情,数据分为两个方面,store存储,access访问。依靠模型完成这个功能。
    1. 承载数据。
    2. 把数据中的信息进行表达convey,存储信息和加工信息。
    3. 不只是一个dbms。
  2. V 视图:将信息呈现出来,view显示。
    1. 把数据呈现给用户。
    2. 承载用户修改功能,使用户能够修改信息。
  3. C 控制:把用户的信息进行处理(按照一定的算法和一定的逻辑),之后再保存在M端。

summary:M存储信息,V交互,C逻辑控制(算法)。

image-20180903105104497

梅宏,杨芙青,吕健——网构建

开发V端

V html,jsp,css,jQuery

自学html。 在w3c学

jQuery,是js的一个库。

extjs 扩展版js 丰富了UI设计功能

开发M端

MySql,Oracle,SQL server,DB2,Access

框架Hibernate统一了不同的dbms的操作。

缺点:是将数据和公共模型进行映射,一旦确定就不好改变,开发的时候麻烦。

iBatis 容易改进的框架

MyBatis

开发C端

struts 框架,把数据进行逻辑处理之后保存到M端。

jQuery js spring 开发代码是Java。framework

开发模式

Html + css + mySQL 几种在view端和modle端

jsp + css + mySQL

jQuery+css+mySQL

PHP + css + mySQL

这些都是小型网站。 乐色

我们的模式

JSP+Struts+mySQL plus + spring + ssh

Real 项目: JSP + SSH + CSS + MYSQL

SSH Spring Struts Hibernate

JSP详解

element 语素

1
2
3
<%
//可以嵌入语句
%>

有三类语句

  1. 表达式
  2. 小脚本
  3. 声明

声明

table称为一个标签,tag。标签有很多属性

看看JSP和mySQL

NaviCat可视化操作MySQL

1433 SQLServer

3306 mySQL 端口

各种端口号是必记。

Myecl

集成了子IDE,所以比较方便。

JAR包

是将java代码和文档集成在一个包中。

5.1.35java connect是可以用的

jdbc driver

URL,第一节是JDBC协议,第二节是MySQL表示数据库名称,第三节是代表这MySQL服务器的名字,localhost 127.0.0.1,第四节是端口号。后面都删掉。后面是连着的库。

driver name 随便取。

Schema 是 database的超集。 datebase是table的超集。

数据库

1
StringTypeConversion //把子段转化成串

connection transantion statement resultset

先建立连接,使用JSP连接MySQL

getConnection函数是静态类型,可以通过类名直接调用函数。

语句分为两方面

  • 静态的,
  • 动态的,是由运行时候确定的查询和修改语句

获得了resultset之后因为其中有很多条记录,所以必须要用循环。resultset自带了迭代器,iterator。一行一行,一个记录一个记录访问。

1
2
3
4
5
6
//把当前指针指向的record赋予re,之后指针往下走。
while(re.next()){
TypeConversion(rs,Type.VARCHAR,1);
//and so on 把结果串加入过来。

}
1
2
3
4
rs.close();
stmt.close();
conn.close();
// 记得关闭连接 一级一级一级往回退

交互界面(登陆界面)

早年窗口还叫Window,现在流行叫frame。

  • 使用html的指令去做
  • 使用JSTL做窗口,使用扩展模板库
1
2
3
<input type="text" name="userName"></input>
<input type="password" name="pass"></input>
<input type="submit" value="登陆"></input>

MVC的思想

C的思想

逻辑控制部分,与应用与需求是相关的。

Struts的本质是借用了java类或者说是函数映射成了一个action,就像html的标签一样可以任意的去使用它。

Struts缺省的配置文件是Sruts.xml 。

  1. web.xml
  2. 配置struts.xml文件
  3. 建立Action类文件
  4. 建立Action执行后转向的jsp文件。

例子

${message} 直接将message类中的信息直接输出出来。

他把control端的数据在页面里输出出来。

那么如何把view端的数据在control中获得呢?

实现原理

  • 数据共享 data sharing
  • 配置文件 configuration

struts2提供在一个类中,具有一定特征的函数就可以映射成actions。

result

dispatcher分发包裹的方式,之前建立的页面缺省的是dispatcher。

单调队列学习笔记

单调队列

定义

单调队列,就是指队列中的元素是单调的。

$a_1,a_2,a_3,…,a_n$满足$a1\leq a_2\leq a_3…\leq a_n$的序列便是单调序列。

运用单调队列可以简化问题,由于队列是单调的,那么我们存取最大最小值的复杂度均是$O(1)$。而每一个元素入队一次,出队一次的复杂度也是$O(1)$。

而单调队列和数据结构deque结合较好,如果题目的数据量不是很大,没有卡std,就可以使用deque实现。

维护

以单调增序列为例子:

  1. 如果队列长度一定,先判断队首元素是否在规定范围内,如果超范围就弹出队首。
  2. 每次加入元素和队尾比较,如果当前元素小于队尾元素并且队列非空,就弹出队尾指针,直到满足单调性为止。

例题1 合并果子

(题目太老,只能换个改版的。代码有一定问题,细心的人能看下出来。)

题解

建两个单调队列,寻找两个最小值加入其中一个堆中,有点像haffman编码的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
#define pb push_back
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 1e4 + 10;
int n,ans;
int a[maxn];
int T;
int main(){
#ifdef LOCAL
freopen("111.in","r",stdin);
#endif
scanf("%d",&n);
rep(i,0,n){
scanf("%d",&a[i]);
}
deque<int> dq1;
deque<int> dq2;
sort(a,a+n);
rep(i,0,n) dq1.pb(a[i]);
ans = 0;
int x,y;
rep(i,0,n-1){
if(dq2.empty()){
x = dq1.front();
dq1.pop_front();
}else if(dq1.empty()){
x = dq2.front();
dq2.pop_front();
}else{
if(dq1.front() < dq2.front()){
x = dq1.front(),dq1.pop_front();
}else{
x = dq2.front(),dq2.pop_front();
}
}
if(dq2.empty()){
y = dq1.front();
dq1.pop_front();
}else if(dq1.empty()){
y = dq2.front();
dq2.pop_front();
}else{
if(dq1.front() < dq2.front()){
y = dq1.front(),dq1.pop_front();
}else{
y = dq2.front(),dq2.pop_front();
}
}
ans += (x+y);
dq2.pb(x+y);
}
printf("%d\n",ans);
return 0;
}

例题2 Sliding Window

题解

单调队列+维护一下队头指针不能超过$k$的范围。

由于有最大值和最小值,维护两个单调队列。

需要注意一下的就是如果是手写双向队列的话,我一般是定义

1
2
3
4
5
int st,ed;
st = ed = 0;
mmax[ed++] = a[0];
//判定的时候需要判定ed-1才是指向队尾的值
while(st<ed && mmax[ed-1]<=a[i]) ed--;

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
const int maxn = 1e6 + 10;
int n,k;
int a[maxn];
int mmax[maxn];
int mmin[maxn];
int main(){
#ifdef LOCAL
freopen("12.in","r",stdin);
#endif
scanf("%d%d",&n,&k);
rep(i,0,n) scanf("%d",&a[i]);
int st1,st2,ed1,ed2;
st1 = st2 = ed2 = ed1 = 0;
mmax[ed1++] = 0; mmin[ed2++] = 0;
rep(i,0,k-1){
while(st1<ed1 && a[mmax[ed1-1]] <= a[i]) ed1--;
mmax[ed1++] = i;
while(st2<ed2 && a[mmin[ed2-1]] >= a[i]) ed2--;
mmin[ed2++] = i;
}
rep(i,k-1,n){
while(st2<ed2 && i-mmin[st2]+1 > k) st2++;
while(st2<ed2 && a[mmin[ed2-1]] >= a[i]) ed2--;
mmin[ed2++] = i;
printf("%d%c",a[mmin[st2]],i==n-1?'\n':' ');
}
rep(i,k-1,n){
while(st1<ed1 && i-mmax[st1]+1 > k) st1++;
while(st1<ed1 && a[mmax[ed1-1]] <= a[i]) ed1--;
mmax[ed1++] = i;
printf("%d%c",a[mmax[st1]],i==n-1?'\n':' ');
}
return 0;
}

例题3 Max Sum of Max-K-sub-sequence

题意

给定一个序列,求不超过$k$长的最大连续子段和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int T,n,k;
const int maxn = 1e5 + 123;
int a[maxn*3];
int b[maxn];
int main(){
#ifdef LOCAL
freopen("11.in","r",stdin);
#endif
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k);
rep(i,0,n) scanf("%d",&b[i]);
rep(i,1,n*2+1) a[i] = (a[i-1] + b[(i-1)%n]);
deque<int> dq;
int ans = -INF;
int l,r;
rep(i,1,2*n+1){
while(!dq.empty() && i-dq.front() > k) dq.pop_front();
while(!dq.empty() && a[dq.back()] > a[i-1]) dq.pop_back();
dq.push_back(i-1);
int temp = a[i] - a[dq.front()];
if(ans < temp){
ans = temp;
l = dq.front()+1; r = i;
if(r>n) r-=n;
}
}
printf("%d %d %d\n",ans,l,r);
}
return 0;
}

例题4 Subsequence

题意

给定一段序列,求最长的一段子序列,他的条件是

  • 最大和最小值的差不超过$k$也不小于$m$。

题解

建立两个单调队列,维护$i$之前最大值的下标和$i$之前最小值的下标。

  • 如果最大值最小值超过$k$就弹出下标较小的,并记录较小的位置。

答案是$i-pos$,如果之后的点满足条件了,那么说明$[pos+1,n]$是满足条件的,因为e.g.$a[min[st] 到min[st+1]]$中的元素都大于$a[min[st+1]]$,如果$a[min[st+1]]$满足那么,$a[min[st]+1]$肯定满足条件。

心路历程:我真是太弱了,这就是一道基础的单调队列题目,我想到用两个单调队列来维护,但是对于小于$k$和大于$m$这个条件,不知道怎么去掌控,妈蛋,其实可以靠单调队列的单调性来掌控他们之间的差距。因为一个是递增一个是递减,如果他们之间的差距过大,那么就可以pop掉队首元素,来减少他们的差距,那么大于$m$怎么维护呢?那就是特判他们之间差距是否大于$m$,大于的话就更新一下答案。对于每个$i$就是维护了他之前最大的值的下标和最小值的下标。

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=(a);i<(n);i++)
#define per(i,a,n) for(int i=(n-1);i>=(a);i--)
#define fi first
#define se second
typedef pair <int,int> pII;
typedef long long ll;
const int INF = 0x3f3f3f3f;
//head
int n,m,k;
const int maxn = 1e5 + 10;
int a[maxn];
int main(){
#ifdef LOCAL
freopen("1.in","r",stdin);
#endif
while(~scanf("%d%d%d",&n,&m,&k)){
rep(i,1,n+1) scanf("%d",&a[i]);
deque<int> dq1; //min
deque<int> dq2; //max
int ans = 0;int pos = 0;
rep(i,1,n+1){
while(!dq1.empty() && a[dq1.back()]>=a[i]) dq1.pop_back();
dq1.push_back(i);
while(!dq2.empty() && a[dq2.back()]<=a[i]) dq2.pop_back();
dq2.push_back(i);
while(!dq1.empty() && !dq2.empty() && a[dq2.front()]-a[dq1.front()]>k){
if(dq1.front() < dq2.front())
pos = dq1.front(),dq1.pop_front();
else
pos = dq2.front(),dq2.pop_front();
}
if(!dq1.empty() && !dq2.empty() && a[dq2.front()]-a[dq1.front()] >=m)
ans = max(ans,i-pos);
}
printf("%d\n",ans);
}
return 0;
}

例题5 Trade

题意

股票交易

  • $i$天你可以以$APi$价格买入股票,$BPi$价格卖出股票
  • $i$天最多买$ASi$股,最多卖$BSi$股
  • 最多拥有$MaxP$股股票
  • 交易后有缓冲期$M$天

开始无限制的钱,问最多能赚多少钱?

题解

AC代码

例题6 Cut the Sequence

例题7 瑰丽华尔兹

例题8 Sequence Partitioning

http://www.voidcn.com/article/p-bcrxdtjx-nd.html

最大子段和学习笔记

最大子段和学习笔记

什么是最大子段和?

给定$n$个整数(可以为负数)组成的序列$a_1,a_2,…,a_n$,求该序列连续的字段和最大值。显然如果都是负数最大值可以为0。

解决方案1

暴力枚举开始位置$i$和终止位置$j$,对每一种可能性计算和。

复杂度$O(n^3)$

解决方案2

使用前缀和优化,保存$\sum_{i=1}^{j-1}a[i]$的结果。

复杂度$O(n^2)$

解决方案3

采用分治策略优化复杂度。

将给定序列$a$分成长度两段子序列$a[1,n/2],a[n/2+1,n]$,分别求出这两段的最大子段和,而总序列的最大子段和有三种情况

  1. 和前半段相同
  2. 和后半段相同
  3. 由前半段的部分和后半段的部分组成的序列相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int MaxSubSum(vector<int> ve,int l,int r){
int sum = 0;
if(l==r) return max(ve[l],0);
int mid = (l+r)>>1;
int lm = MaxSubSum(ve,l,mid);
int rm = MaxSubSum(ve,mid+1,r);
int ls = 0;int lefts = 0;
for(int i=mid;i>=l;i--){
lefts += ve[i];
ls = max(ls,lefts) //遍历更新最大值
}
int rs = 0;int rights = 0;
for(int i=mid+1;i<=r;i++){
rights += ve[i];
rs = max(rs,rights);
}
sum = ls + rs;
sum = max(sum,max(lm,rm));
return sum;
}

复杂度$O(nlogn)$

解决方案4

动态规划。

  • 设置$dp[i] = max(dp[i-1]+a[i],a[i])$,$dp[i]$表示以$i$为结尾的最长子串的和。
  • 对于每一个$a[i]$如果之前的最大子串小于$0$了,那么就要重新以他为开头建立一个子串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int MaxSubSum(vector<int> ve,int n,int& l,int& r){
int sum = -INF;int mmax = -INF;
int L=0;int R = 0;
for(int i=0;i<n;i++){
if(sum < 0){
sum = ve[i];
L = R = i;
}else{
sum += ve[i];
R ++;
}
if(mmax < sum){
mmax = sum;
l = L;r = R;
}
}
return mmax;
}

复杂度$O(n)$

解决方案5

最大子段的左右两个数字必定为正数,最左边数字

reference:https://blog.csdn.net/zhong36060123/article/details/4381391

ST表学习笔记

ST表学习笔记

功能

ST表示用来求解给定区间RMQ的最值问题。

预处理复杂度:$O(nlongn)$,查询复杂度$O(1)$。

详解

原理

将原数组分成以2幂次的区间块,用$mn[i][j]$表示从$j$到$j-2^i-1$的最小值,最小值显然等于$$min(前半段最小值,后半段中最小值)$$从而得到递推式子

$$min[i][j]=min(mn[i-1][j],mn[i-1][j+2^{i-1}])$$

预处理代码

1
2
3
4
5
6
7
8
9
p[0] = 1;
rep(i,1,20) p[i] = 1<<i;
Log[0] = -1;
rep(i,1,200000) Log[i] = Log[i/2] + 1;
rep(i,1,n) mn[0][i] = a[i];
rep(i,1,n+1)
rep(j,1,n+1)
if(j+p[i]-1 <= n)
mn[i][j] = min(mn[i-1][j],mn[i-1][j+p[i-1]]);

查询

定理1:$$2^{log(a)}>a/2$$

查询$x$到$y$的最小值可以假设$len=y-x+1,t=log(len)$,根据定理1,$2^t>len/2$,那么位置过了一半之后最小值的可能就落在了$x$后面的$2^t$和$y$前面的$2^t$。式子为$$mmin = min(mn[t][x],mn[t][y-2^t+1])$$

实现

1
2
int t = Log[y-x+1];
int ans = min(mn[t][x],mn[t][y-p[t]+1]);

Reference:https://blog.csdn.net/hanks_o/article/details/77547380