数据结构讲稿

这个数据结构讲稿是NOIP普及组-提高组难度的。下放附了部分常用的数据结构的代码实现。

[pdf-embedder url=”https://mhy12345.xyz/wp-content/uploads/2018/07/数据结构-讲义.pdf” title=”数据结构 讲义”]

[pdf-embedder url=”https://mhy12345.xyz/wp-content/uploads/2018/07/数据结构.pdf” download=’on’]

#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 10000

int stack[MAXN],tops=-1;

void push_stack(int x) {
		stack[++tops] = x;
}

int pop_stack() {
		return stack[tops--];
}

int main() {
	push_stack(1);
	push_stack(2);
	cout<<pop_stack()<<endl;
	cout<<pop_stack()<<endl;
}
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXN 10000

int queue[MAXN],head=-1,tail=-1;

//++tail 表示把tail值加1,然后返回tail值
//tail++ 表示把tail值加1,然后返回之前的tail值

void push_queue(int x) {
	queue[++head] = x;
}

int pop_queue() {
	return queue[++tail];
}

bool is_empty() { 
	return head == tail;
}

int main() {
}

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

int main() {
	priority_queue<int> PQ;
	PQ.push(1);
	PQ.push(3);
	PQ.push(2);
	cout<<PQ.top()<<endl;PQ.pop();
	cout<<PQ.top()<<endl;PQ.pop();
	cout<<PQ.top()<<endl;PQ.pop();
}
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXV 10000 //最大点数
#define MAXE 200000 //最大边数

struct Edge {
	int np;//下一个顶点的编号
	int val;//边权
	Edge *next;//链表里面的指针
}E[MAXE],*V[MAXV];
int tope = -1;

void add_edge(int x,int y,int v) {
	E[++tope].val = v;
	E[tope].next = V[x];
	E[tope].np = y;
	V[x] = &E[tope];
}

// V[1] = (np=3,val=3) -> (np=2,val=2) -> NULL
// V[2] = (np=3,val=1) -> NULL
// V[3] = (np=1,val=3) -> NULL

int main() {
	freopen("input.txt","r",stdin);
	int n;//点数
	int m;//边数
	scanf("%d %d",&n,&m);
	for (int i=0;i<m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add_edge(x,y,z);
	}
	for (int now=1;now<=n;now++){
		for (Edge *ne=V[now];ne;ne=ne->next){
			cout<<"EDGE "<<now<<"->"<<ne->np<<"("<<ne->val<<")"<<endl;
		}
	}
}
#include<iostream>
using namespace std;

//0x12341234
//...........................................
//                   aaaa
//                   ^
//       bbbbbbbb
//       ^
//		     ^
//							cccc
//
//
struct aaa{ 
	int val,a2;
}sa;

int main() {
	int a,c;
	int b[2];
	b[0] = 123;
	b[1] = 312;
	a = 1;
	c = 2;

	int *ptr_a = &a;
	cout<<"PTR_A = "<<ptr_a<<endl;
	cout<<"A = "<<a<<endl;
	cout<<"*PTR_A = "<<(*ptr_a)<<endl;
	(*ptr_a) = -1;
	cout<<"*PTR_A = "<<(*ptr_a)<<endl;
	cout<<"A = "<<a<<endl;

	cout<<"B = "<<b<<endl;
	cout<<"*B = "<<*b<<endl;
	cout<<"B[1] = "<<b[1]<<endl;
	cout<<"*(B+1) = "<<*(b+1)<<endl;

	cout<<"(A+1) = "<<*(ptr_a+1)<<endl;

	//一个struct的指针可以使用->访问成员变量
	aaa *ptr_sa = &sa;
	cout<<"(*ptr_sa).val"<<((*ptr_sa).val)<<endl;
	cout<<"ptr_sa -> val"<<ptr_sa->val<<endl;
}
#include<iostream>
using namespace std;
#define MAXN 100000
int jump[MAXN][20];
int a[MAXN];
int ff[MAXN];

//  a << b
//  a >> b
//
//  00000000000011
//  00000000000001
//  00000000000110
//  00000000001100

int main() {
	scanf("%d",&n);
	for (int i=0;i<n;i++)
		scanf("%d",a+i);

	memset(ff,-1,sizeof(ff));
	for (int i=0;(1<<i)<MAXN;i++)
		ff[1<<i] = 1<<i;
	for (int i=1;i<MAXN;i++)
		if (ff[i] == -1)
			ff[i] = ff[i-1];

	// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
	// . . . . . . . . . .  .  .  .  .  .  .  .  .
	//   1 2   4       8                     16
	// - 1 2 2 4 4 4 4 8 8  8  8  8  8  8  8 16 16
	for (int i=0;i<n;i++)
		jump[i][0] = a[i];
	for (int i=1;i<20;i++)
		for (int j=0;j+(1<<i)-1<n;j++)
			jump[j][i] = max(jump[j][i-1],jump[j+(1<<(i-1))][i-1]);
	//jump[i][0]->[i,i]
	//jump[i][j]
	//[3,3+2^4-1]
	//[3,3+2^3-1] [3+2^3,3+2^4-1]
	//
	// . . . . . . . . . . q q q q q q q q q q q . . . . . 
	// . . . . . . . . . . a a a a a a a a 
	// . . . . . . . . . . . . . a a a a a a a a . . . . .
	scanf("%d",&m);
	for (int i=0;i<m;i++) {
		scanf("%d%d",&l,&r);
		int len = r-l+1;
		int k = ff[len];
		int ans = max(jump[l][k],jump[r-(1<<k)+1][r]);
		printf("%d\n",ans);
	}
}

//jump[i][j] 表示区间[i,i+2^j-1]的最大值
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 100000
#define lowbit(x) ((x) & (-(x)))
int tarr[MAXN];//树状数组

void AddTarr(int pos,int v) {
	while (pos < MAXN) {
		tarr[pos] += v;
		pos += lowbit(pos);
	}
}

int QryTarr(int pos) {
	int res = 0;
	while (pos) {
		res += tarr[pos];
		pos -= lowbit(pos);
	}
	return res;
}

int main() {
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++) { int x; scanf("%d",&x); AddTarr(i,x); }
	int m; scanf("%d",&m);
	for (int i=0;i<m;i++) {
		int cid; scanf("%d",&cid);
		if (cid == 0) {//单点加操作
			int x,v;
			scanf("%d%d",&x,&v);
			AddTarr(x,v);
		}else if (cid == 1) {//区间和查询
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%d\n",QryTarr(r) - QryTarr(l-1));
		}
	}
}
#include<iostream>
#include<cstdio>
using namespace std;
#define MAXS 10000
#define MAXT 100000
char buffer[MAXS];

struct TrieNode {
	int total;
	int nxt[26];
}trie[MAXT];
int topt = 1;
int root = 1;

void AddTrie(char* ptr) {
	int now = root;
	while (*ptr) {
		//*ptr-'a'
		if (!trie[now].nxt[*ptr-'a'])
			trie[now].nxt[*ptr-'a'] = ++topt;
		now = trie[now].nxt[*ptr-'a'];
		trie[now].total ++;
		ptr++;
	}
}

int QryTrie(char *ptr) {
	int now = root;
	while (*ptr) {
		if (!trie[now].nxt[*ptr-'a'])
			return 0;
		now = trie[now].nxt[*ptr-'a'];
		ptr++;
	}
	return trie[now].total;
}
		
int main() {
	freopen("input.txt","r",stdin);
	int n,m;
	scanf("%d%d\n",&n,&m);
	for (int i=0;i<n;i++) {
		scanf("%s\n",buffer);
		AddTrie(buffer);
	}
	for (int i=0;i<m;i++) {
		scanf("%s\n",buffer);
		cout<<QryTrie(buffer)<<endl;
	}
}
#include<iostream>
using namespace std;
#define MAXN 101000
int uf[MAXN];

void init() {
	for (int i=0;i<n;i++)
		uf[i] = i;
}

int get_uf(int x) {
	return uf[x] == x ? x : uf[x] = get_uf(uf[x]);
}

bool join(int x,int y) { 
	if (get_uf(x) == get_uf(y))
		return false;
	uf[get_uf(x)] = get_uf(y);
	return true;
}

int main() {
}
#include<iostream>
#include<algorithm>
#include<cassert>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 100000
#define MAXT MAXN*4
#define root 1
#define lch ((now<<1))
#define rch ((now<<1)^1)
#define smid ((l+r)>>1) //(l+r)/2

//堆式线段树
struct sgt_node {
	int sum;
	int l,r;
}sgt[MAXT];
int a[MAXN];

void update(int now) {
	sgt[now].sum = sgt[lch].sum + sgt[rch].sum;
}

void build_sgt(int now,int l,int r) {//建立一个以now为根的线段树,这个线段树对应区间l,r
	sgt[now].l = l;
	sgt[now].r = r;
	if (l == r) {
		sgt[now].sum = a[l];
		return ;
	}
	build_sgt(lch,l,smid);
	build_sgt(rch,smid+1,r);
	update(now);
}

int query_sgt(int now,int l,int r,int x,int y) {//查询以now为根的线段树,线段树now节点对应区间为[l,r],而查询区间为[x,y]
	assert(l == sgt[now].l  && r == sgt[now].r);
	if (l == x && r == y)
		return sgt[now].sum;
	if (y <= smid)
		return query_sgt(lch,l,smid,x,y);
	else if (smid < x) // smid+1 <= x
		return query_sgt(rch,smid+1,r,x,y);
	else 
		return query_sgt(lch,l,smid,x,smid) + query_sgt(rch,smid+1,r,smid+1,y);
}

void modify_sgt(int now,int l,int r,int pos,int val) {//修改位置pos的值为val
	assert(l == sgt[now].l  && r == sgt[now].r);
	if (l == r) { //l == r && r == pos
		sgt[now].sum = val;
		return ;
	}
	if (pos <= smid)
		modify_sgt(lch,l,smid,pos,val);
	else
		modify_sgt(rch,smid+1,r,pos,val);
	update(now);
}

int main() {
	freopen("input.txt","r",stdin);
	int n;
	scanf("%d",&n);
	for (int i=0;i<n;i++)
		scanf("%d",a+i);
	build_sgt(root,0,n-1);
	int m;
	scanf("%d",&m);
	for (int i=0;i<m;i++) {
		int cid;
		scanf("%d",&cid);
		if (cid == 1) {
			int x,y;
			scanf("%d%d",&x,&y);x--,y--;
			int res = query_sgt(root,0,n-1,x,y);
			printf("%d\n",res);
		}else if (cid == 2) {
			int p,v;
			scanf("%d%d",&p,&v);p--;
			modify_sgt(root,0,n-1,p,v);
		}
	}
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 100000
#define MAXT MAXN*4
#define smid ((l+r)>>1)
int a[MAXN];

struct sgt_node {
	int lch,rch;
	int sum;
	int pls;
}sgt[MAXT];
int topt = 0;

void update(int now) {
	sgt[now].sum = sgt[sgt[now].lch].sum + sgt[sgt[now].rch].sum;
}

void do_plus(int &now,int l,int r,int v) {//给now整棵子树都加上v
	if (!now)now = ++topt;
	sgt[now].pls += v;
	sgt[now].sum += (r-l+1)*v;
}

void push_down(int now,int l,int r) {
	if (sgt[now].pls) {
		do_plus(sgt[now].lch,l,smid,sgt[now].pls);
		do_plus(sgt[now].rch,smid+1,r,sgt[now].pls);
		sgt[now].pls = 0;
	}
}


int query_sgt(int &now,int l,int r,int x,int y) {
	if (!now)now=++topt;
	if (l == x && r == y)
		return sgt[now].sum;
	push_down(now,l,r);
	if (y <= smid)
		return query_sgt(sgt[now].lch,l,smid,x,y);
	else if (smid < x)
		return query_sgt(sgt[now].rch,smid+1,r,x,y);
	else
		return query_sgt(sgt[now].lch,l,smid,x,smid) + query_sgt(sgt[now].rch,smid+1,r,smid+1,y);
}

void modify_sgt(int &now,int l,int r,int pos,int v) {
	if (!now)now=++topt;
	if (l ==  r) {
		sgt[now].sum = v;
		return ;
	}
	push_down(now,l,r);
	if (pos <= smid)
		modify_sgt(sgt[now].lch,l,smid,pos,v);
	else
		modify_sgt(sgt[now].rch,smid+1,r,pos,v);
	update(now);
}

void plus_sgt(int &now,int l,int r,int x,int y,int v) {
	if (!now)now=++topt;
	if (l ==  x && r == y) {
		do_plus(now,l,r,v);
		return ;
	}
	push_down(now,l,r);
	if (y <= smid)
		plus_sgt(sgt[now].lch,l,smid,x,y,v);
	else if (smid < x)
		plus_sgt(sgt[now].rch,smid+1,r,x,y,v);
	else
		plus_sgt(sgt[now].lch,l,smid,x,smid,v),plus_sgt(sgt[now].rch,smid+1,r,smid+1,y,v);
	update(now);
}


int main() {
	freopen("input.txt","r",stdin);
	int n;
	scanf("%d",&n);
	int root = 0;
	int m;
	scanf("%d",&m);
	for (int i=0;i<m;i++) {
		int cid;
		scanf("%d",&cid);
		if (cid == 1) {
			int x,y;
			scanf("%d%d",&x,&y);x--,y--;
			int res = query_sgt(root,0,n-1,x,y);
			printf("%d\n",res);
		}else if (cid == 2) {
			int p,v;
			scanf("%d%d",&p,&v);p--;
			modify_sgt(root,0,n-1,p,v);
		}else if (cid == 3) {
			int x,y,v;
			scanf("%d%d%d",&x,&y,&v);x--,y--;
			plus_sgt(root,0,n-1,x,y,v);
		}
	}
}

oi课件合集

数论部分讲稿

[pdf-embedder url=”https://mhy12345.xyz/wp-content/uploads/securepdfs/2018/07/数论-讲义.pdf” title=”数论 讲义v1.0″]

[pdf-embedder url=”https://mhy12345.xyz/wp-content/uploads/securepdfs/2018/07/数论-课件.pdf” title=”数论 课件 v1.0″]

bool Miller_Rabbin(int n,int a)//a属于[2,n)
{
        if (n<2)return false;
        if (!(n%a))return false;
        int r=0,d=n-1;
        while (!(d&1))
        {
                d>>=1;
                r++;
        }
        int k=pow_mod(a,d,n);
        if (k==1)return true;
        for (int i=0;i<r;i++)
        {
                if (k==n-1)return true;
                k=k*k%n;
        }
        return false;
}
typedef long long qword;
qword ext_gcd(qword a,qword b,qword &x,qword &y)
{
        if (a%b==0)
        {
                //a*x+b*y==b
                x=0;y=1;
                return y;
        }
        qword ret=ext_gcd(b,a%b,x,y);
        qword tx=x,ty=y;
        x=ty;
        y=tx-a/b*ty;
        return ret;
}
#include<iostream>
using namespace std;
#define MAXN 1010
bool pflag[MAXN];
int prime[MAXN],topp=-1;
void init() {
	for (int i=2;i<MAXN;i++)
	{
		if (!pflag[i])
			prime[++topp] = i;
		for (int j=0;j<=topp && prime[j]*i<MAXN;j++)
		{
			pflag[i*prime[j]] = true;
			if (i%prime[j] == 0)break;
		}
	}
	for (int i=0;i<=topp;i++)
		cout<<prime[i]<<endl;
}

int main()
{
	init();
}

 

#include<iostream>
using namespace std;
#define MAXN 100
#define MOD 1000000007
bool pflag[MAXN];
int prime[MAXN],topp=-1;
int inv[MAXN];
int phi[MAXN];
int h[MAXN];//存最小的那个质数所在的括号
int hh[MAXN];//存其他括号的乘积
int g[MAXN];//存最小那个质数所在的括号有多少个数
int gg[MAXN];//存其他括号的个数的乘积

long long pow_mod(long long a,long long b) {
	long long res = 1;
	while (b) {
		if (b&1)
			res = res * a%MOD;
		a = a*a%MOD;
		b>>=1;
	}
	return res;
}
//(1+5+25)*(1+3)   *   (1+2)
//(1+5+25)*(1+3)   *   (1+2+4)
//    hh[i]              h[i]
//
//(1+2)*2+1 = (1+2+4)
//(1+2+4)*2+1 = (1+2+4+8)
//
//(1+5+25)         *   (1+3)
//(1+5+25)*(1+3)   *   (1+2)

void init() {
	inv[1] = 1;
	for (int i=2;i<MAXN;i++) {
		if (!pflag[i]) {
			prime[++topp] = i;
			inv[i] = pow_mod(i,MOD-2);
			phi[i] = i-1;
			h[i] = i+1;
			hh[i] = 1;
			g[i] = 2;
			gg[i] = 1;
		}
		for (int j=0;j<=topp && i*prime[j]<MAXN;j++) {
			pflag[i*prime[j]] = true;
			inv[i*prime[j]] = (long long)inv[i] * inv[prime[j]] %MOD;
			if (i%prime[j] == 0) {
				phi[i*prime[j]] = phi[i] * prime[j];
				h[i*prime[j]] = h[i] * prime[j] + 1;
				hh[i*prime[j]] = hh[i];
				g[i*prime[j]] = g[i]+1;
				gg[i*prime[j]] = gg[i];
				break;
			}else {
				phi[i*prime[j]] = phi[i] * (prime[j]-1);
				h[i*prime[j]] = prime[j] + 1;
				hh[i*prime[j]] = h[i] * hh[i];
				g[i*prime[j]] = 2;
				gg[i*prime[j]] = g[i] * gg[i];
			}
		}
	}
}

int main() {
	init();
	/*
	for (int i=0;i<=topp;i++)
		cout<<prime[i]<<"\t";*/
	for (int i=2;i<MAXN;i++) {
		cout<<i<<" INV="<<inv[i]<<" PHI="<<phi[i]<<" H="<<h[i]*hh[i]<<" G="<<g[i]*gg[i]<<endl;
	}
}

 

数学部分讲稿

[pdf-embedder url=”https://mhy12345.xyz/wp-content/uploads/securepdfs/2018/07/数学-讲义-v1.1.pdf” title=”数学 讲义 v1.1″]

[pdf-embedder url=”https://mhy12345.xyz/wp-content/uploads/securepdfs/2018/07/数学-习题.pdf” title=”数学 习题”]

 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cassert>
using namespace std;
#define MOD 100000007

struct matrix {
		static const int maxn = 2;
		int n,m;
		int mat[maxn][maxn];
		matrix() {
				memset(mat,0,sizeof(mat));
		}
		void initEye(int n) {
				this->n = this->m = n;
				memset(mat,0,sizeof(mat));
				for (int i=0;i<n;i++)
						mat[i][i] = 1;
		}
		void initStart() {
				this->n = 2;
				this->m = 1;
				memset(mat,0,sizeof(mat));
				mat[0][0] = mat[1][0] = 1;
		}
		void initFib() {
				this->n = this->m = 2;
				mat[0][0] = 0;
				mat[1][0] = mat[0][1] = mat[1][1] = 1;
		}
		void print() {
				cout<<"MATRIX "<<n<<" "<<m<<endl;
				for (int i=0;i<n;i++) {
						for (int j=0;j<m;j++) {
								cout<<mat[i][j]<<"\t";
						}
						cout<<endl;
				}
		}
};

matrix operator* (const matrix& m1,const matrix& m2) {
		assert(m1.m == m2.n);
		matrix res;
		res.n = m1.n;
		res.m = m2.m;
		for (int i=0;i<m1.n;i++) {
				for (int j=0;j<m2.m;j++) {
						for (int k=0;k<m1.m;k++) {
								res.mat[i][j] = (res.mat[i][j] + (long long)m1.mat[i][k]*m2.mat[k][j])%MOD;
						}
				}
		}
		return res;
}
matrix pow_mod(matrix x,int y) {
		matrix res;
		res.initEye(2);
		while(y) {
				if (y&1) 
						res = res * x;
				x = x*x;
				y>>=1;
		}
		return res;
}

int main() {
		matrix x;
		matrix T;
		x.initStart();
		x.print();
		T.initFib();
		T.print();
		(pow_mod(T,60000000)*x).print();
		while (1);
		
}

 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cassert>
#include<cmath>
using namespace std;
#define MOD 100000007
#define eps 1e-7

struct matrix {
	static const int maxn = 20;
	int n,m;
	double mat[maxn][maxn];
	matrix() {
		memset(mat,0,sizeof(mat));
	}
	void print() {
		cout<<"MATRIX "<<n<<" "<<m<<endl;
		for (int i=0;i<n;i++) {
			for (int j=0;j<m;j++) {
				cout<<mat[i][j]<<"\t";
			}
			cout<<endl;
		}
	}
	void random(int n) {
		this->n = n;
		this->m = n;
		for (int i=0;i<n;i++)
			for (int j=0;j<n;j++)
				mat[i][j] = rand()%100;
	}
	void initSquare() {
		this->n = 4;
		this->m = 4;
		memset(mat,0,sizeof(mat));
		mat[0][1] = mat[0][3] = 1;
		mat[1][0] = mat[1][2] = 1;
		mat[2][1] = mat[2][3] = 1;
		mat[3][0] = mat[3][2] = 1;
		mat[0][0] = mat[1][1] = mat[2][2] = mat[3][3] = -2;
		this->n --;//去一行
		this->m --;//去一列
	}
	double gauss() {
		double ans = 1;
		for (int i=0;i<n;i++) {
			int sid = -1;
			for (int j=i;j<n;j++)
				if (abs(mat[j][i]) > eps)
				{
					sid = j;
					break;
				}
			if (sid == -1)
				continue;
			if (sid != i) {
				for (int j=0;j<n;j++)
				{
					swap(mat[sid][j],mat[i][j]);
					ans = - ans;
				}
			}
			for (int j=i+1;j<n;j++) {
				double ratio = mat[j][i]/mat[i][i];
				for (int k=0;k<n;k++) {
					mat[j][k] -= mat[i][k] * ratio;
				}
			}
			//print();
		}
		for (int i=0;i<n;i++)
			ans *= mat[i][i];
		return abs(ans);
	}
};


int main() {
	srand(1);
	matrix T;
	//T.random(2);
	T.initSquare();
	T.print();
	double ans = T.gauss();
	T.print();
	cout<<ans<<endl;
}

更多课件在这里

WordPress速度优化

网站访问速度是搜索引擎衡量一个网站的核心因素,因为这在另一方面意味着用户点选这个词条的用户体验。因此,我们十分有必要优化Wordpress的速度。这里,我使用了WP Super Cache和Jetpack插件,WP Super Cache可以缓存你的博客页面,特别是对于匿名用户,不论如何,网站对匿名用户返回的始终是同一个页面,因此将他们缓存下来可以达到很好的效果(从原来的2s变成了几乎看不到延迟)。同时Jetpack插件提供了免费的CDN服务。也可以优化图片以及视频的加载速度。

我是如何和搜索引擎斗智斗勇的.要有一个固定的域名

要有一个固定的域名

之前曾经提到百度将我的博客列为“用户不友好网站”,追溯其原因。回想之前对网站做的危险操作,不外乎变更子域名了。最初我的博客使用的是blog.mhy12345.xyz,提交了百度收录之后,我又心血来潮将站点的域名改变成了mhy12345.xyz。当时的想仅仅是,也许我改变了域名,搜索引擎自己会直接迁移过去blablabla,但是可能并不是这样。

从搜索引擎视角来看,首先出现了一个blog.mhy12345.xyz网站,接着有出现了一个冒充blog.mhy12345.xyz网站的另一个网站,内容完全相似。直接判定为抄袭站点。接下来发生的事情就更好解释了,百度只收录blog.mhy12345.xyz,我一怒之下,删去了对于blog.mhy12345.xyz的解析,剩下了一个mhy12345.xyz,而这个网站已经被搜索引擎判定为了恶意站点。然后……就没有然后了……(等等,为啥google就没有判断出错呢? 唔,毕竟百度不是世界一流互联网公司吧)

我的网站

自己写博客,特别是技术类博客,当然希望能够有其他人能够看到。我的第一篇正式文章Ubuntu 16.04 & Docker 搭建 WordPress 写于2017年4月,而到现在已经一年多了。大大小小杂文有七十于篇(怎么会那么多!?)自己认为很有用的也有大约10篇了。

有了足够的干货,当然希望更多人能够看到,这就依赖于搜索引擎了。然而搜索引擎却貌似并不买我的账。怎么说呢,google是个好公司啊,因为至少在我一点一点写博客这个时间段,google索引量在逐渐增加的,google给我带来了95%的搜索引擎索引量。而百度,甚至将我的博客列为了“用户不友好网站”==。想一想其实不无道理,google面向国外,有非常多的优质个人站点,所以理所当然赋予了个人站点更高的权值,反观百度的用户主体,国内,随随便便点开一个小站,各种广告网站满天飞,是我也想一枪毙掉所有的个人网站了。

 

真实感图像渲染系列:小结

这是真实感图像渲染系列的第十篇文章。

这里写一写个人在编写过程中发现的一些坑,分享出来以免之后再踩吧。

  • CMake好啊,比Makefile好,可以很容易整合第三方库,但是……编译超级慢,别人的工程3s编译完成我的需要半分钟……
  • eps设多少确实是个坑,我设的是1e-7,但是得保证牛顿迭代的精度严格高于eps,否则焦点可能存在大于eps的误差,eps的设置就没有意义了
  • MAC的错误报告挺有用的 == 比如下面这个直接秒调

真实感图像渲染系列:相机(Camera)与景深效果(Field of Depth)

这是真实感图像渲染系列的第九篇文章。

普通相机模型

不同的相机模型中,有一个人眼位置和一个屏幕,对于屏幕中每一个像素,连接人眼和该像素位置得到一条射出光线。其2D示意图如下:

首先,一直屏幕像素坐标,以及屏幕本身在场景中的位置,我们可以计算出该像素的位置,如图中红点所述。接着,连接人眼和像素的光线就可以用于计算该光线的颜色。这一部分挺容易理解的。

带景深的相机模型

在普通相机模型的屏幕和人眼位置之外,带景深的相机模型额外存在一个焦平面。如下图所示

通过普通的相机模型,我们可以求得普通相机的光线的方向。

普通相机的光线一定会和焦平面在某个位置碰撞,为了达到景深效果,一方面,我们需要保证同一个像素对应的光线一定会击打到焦平面上的同一个点,另一方面,在焦平面之外的其他位置,同一个像素对应的光线应该击打到不同的点。

解决方案如下:我们求得在普通相机模型中,光线和焦平面的焦点,将该焦点景深相机模型的焦点。接下来,我们对光线的起始位置做一个微小扰动,并且修改光线方向使得光线恒定过焦平面上的点。这样,无论初始光线如何扰动,焦平面上的点都可以最清晰的表现出来。而不在焦平面上的点,会产生随机的模糊效果。

 

 

真实感图像渲染系列:贝塞尔曲线旋转体与直线求交

这是真实感图像渲染系列的第八篇文章。

概述

贝塞尔曲线是一个由多个控制点组合而成的曲线。在本文探讨的贝塞尔曲线,是由多条简单的4阶贝塞尔曲线拼接而成。绕z轴旋转贝塞尔曲线,我们可以得到一个旋转体曲面,该曲面就是待渲染的参数曲面。本文将探讨如何使用牛顿迭代法与直线求交。求交过程中

  1. 关于迭代初值的求法
  2. 迭代中的一些优化技巧
  3. 关于浮点数精度误差eps的求法。

贝塞尔曲线的定义

贝塞尔曲线是一个由多个控制点组合而成的曲线,n阶贝塞尔曲线定义为

(1)   \begin{equation*}  P(u) = \left[ \begin{matrix}P^{(x)}(u) \\ P^{(y)}(u) \end{matrix} \right] = \left[ \begin{matrix} \sum _{i=0}^n P_i^{(x)} C(n,i) (1-u)^{n-i}u^i \\ \sum _{i=0}^n P_i^{(y)}C(n,i) (1-u)^{n-i}u^i \end{matrix} \right] \end{equation*}

式(1)定义了该曲线P(u)的关于参数u \in [0,1]的参数方程,其中P_i为n个曲线的控制点。当我看到这个公式的时候,整个人都已经懵掉了。实际上理解起来并不复杂。

函数P(u)由参数u定义了一个二维向量,其中P^{(x)}P^{(y)}相互独立。只需要理解其中一个的产生式即可。考虑函数 f_i(u) =C(n,i) u^i (1-u)^{n-i-1},发现这f_0(u),f_1(i),\cdots,f_{n-1}(u)实际上是一个和为1的数列,数列在杨辉三角形中定义。换句话来说,f_i(u)实际上可以得到一个权值的分配,使用f_i(u)对于所有的控制点P_i加权平均,就是贝塞尔曲线尝试完成的事情。当f_0(u) = C(n,0) u^0 (1-u)^{n-i} = 1权值全部分配给了P_0,因此贝塞尔曲线的起点(u=0)和P_0重合。

贝塞尔曲线旋转体

在三维空间中,我们通过旋转贝塞尔曲线得到一个曲面。曲面强制对z轴旋转对称。(注:在代码中是对x轴旋转对称,不过z轴对称相对好理解一些)。

(2)   \begin{equation*}  S(u,\theta) = \left[ \begin{matrix} Q_x + sin\theta P^{(x)}(u) \\ Q_y + cos\theta P^{(x)}(u) \\ Q_z + P^{(y)}(u) \end{matrix} \right] \end{equation*}

一个贝塞尔曲线旋转体要能够成功渲染,其核心就是需要支持与光线的求交。光线我们使用(rayO,rayD)表示,rayO表示光线起点,rayD表示光线方向,是一个单位向量。则光线上一点可表示为C(t),满足:

(3)   \begin{equation*}  C(t) = rayO + t \times rayD \end{equation*}

倘若直线C(t)S(u,\theta)相交,则有

(4)   \begin{equation*}  F(t,u,\theta) = C(t) - S(u,\theta) = 0 \end{equation*}

其中,F(t,u,\theta)t,u,\theta的函数,要求t>00\leq u \leq 1,原问题可转化为求函数零点问题。函数零点问题使用牛顿迭代解决。

(5)   \begin{equation*}  x_{i+1} = x_i - [F'(x_i)]^{-1} \cdot F(x_i) \end{equation*}

其中F'(x_i)为函数F(x_i)的Jacobian矩阵,定义为:

(6)   \begin{equation*}  \frac{\partial F}{\partial t} = \left[ \begin{matrix} \frac{\partial F_1}{\partial t} & \frac{\partial F_1}{\partial u} & \frac{\partial F_1}{\partial \theta} \\ \frac{\partial F_2}{\partial t} & \frac{\partial F_2}{\partial u} & \frac{\partial F_2}{\partial \theta} \\ \frac{\partial F_3}{\partial t} & \frac{\partial F_3}{\partial u} & \frac{\partial F_3}{\partial \theta} \\ \end{matrix} \right] \end{equation*}

要试图计算出式(\ref(eq:jacobian)),我们需要分别求出F(t,u,\theta)对于t,u,\theta偏导数的每一个分量的值(共九个)。结果为:

(7)   \begin{equation*}  \frac{\partial F}{\partial args} = \left[ \begin{matrix} rayD_x & -sin\theta \frac{dP_x}{du} & -cos\theta P_x(u)\\ rayD_y & -cos\theta \frac{dP_x}{du} & +sin\theta P_x(u)\\ rayD_z & -\frac{dP_y}{du} & 0\\ \end{matrix} \right] \end{equation*}

其中\frac{dP}{du}是对于式子(1)按照最原始的求导法则求导的结果。

贝塞尔曲面物体设计

如图

共6条4阶贝塞尔曲线,每一条由绿蓝红绿四个控制点定义。

注意事项

  1. 牛顿迭代需要保证答案中u \in [0,1],而牛顿迭代本身无法附加条件,因此需要在答案的邻域中查找合适的u,t,\theta,我用到的方法是,对于给定曲线,生成一个圆柱体包含框,随机该圆柱体里面高度h的一个圆形面片,将光线与该面片的交点的u,t,\theta作为迭代的初值。随机h约30次即可得解。
  2. 由于牛顿迭代不太精确,可能是的结果产生微小偏移,这种偏移会对判断点在平面哪一侧产生影响,我们可以通过调整eps的取值,不同部分赋予不同的eps,使得这些误差不至于相互影响。
  3. 贝塞尔曲线求交本身费时间,但是判断是否一定没有交点却相对简单。可以求出贝塞尔曲线旋转体的“圆柱体包围盒”,(更精确的话,可以是空心圆柱体包围盒)。然后判断直线是否与包围盒有交点。判断直线与圆柱交点P = rayO + k \times rayD的位置,可以先讲所有东西映射到z=0屏幕,这是变成2维直线和圆求交,算出k,回带到三维情况验证。
  4. 实际上,像本文采用的多条低阶贝塞尔曲线求交并不明智,倘若将本文那个碗的6条贝塞尔曲线改为1条简单一些的曲线,然后做一个玻璃杯什么的,渲染效率将提高6倍!