省选前模板总计,基于假设检验的Filter方法

写在前面

全套项目都托管在了 Github
上:
追寻更为有利于的版本见:
这一节内容恐怕会用到的库文件有 Quick,同样在 Github 上得以找到。
善用 Ctrl + F 查找难题。

DATA STRUCTURE

  转发请标明出处:

自身要检举本次校赛出题人的低沉出题!!!

习题&题解

1.数据结构

  

法定题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实正是一堆代码没有题解)

2.3.1

1.1字符串哈希

为后缀总结三个哈希值,满足\(H(i)=H(i+1)x+s[i]\)(其中\(0 \leq i < n, H(n) = 0\)),例如
\[H(4)=s[4]\]
\[H(3)=s[4]x+s[3]\]
一般地,\(H(i)=s[n-1]x^{n-1-i}+s[n-2]x^{n-2-i}+…+s[i+1]x+s[i]\)
对于一段长度为L的子串s[i]~s[i+L-1],定义他的哈希值\(Hash(i, L) = H(i)-H(i+L)x^L\)。
预处理H和\(x^L\)。注意到hash值极大,大家把她存在unsigned
long long中,那样就得以实现了自然溢出,能够大大减小常数。

  Filter特征选取方式是一种启发式方法,其基本考虑是:制定2个轨道,用来衡量每一个特征/属性,对目的属性的最首要程度,以此来对持有特征/属性举行排序,大概进行优选操作。常用的权衡准则有假如检验的p值、相关全面、互音信、消息增益等。本文基于候选属性和对象属性间关联性的若是检验,依据p值的大小量化各候选属性的重要程度。

 

题目

遵照 Partition() 方法的轨迹的格式给出该方法是何许切分数组 E A S Y Q U E
S T I O N 的。

1.2树状数组

  设有$D
$维数据集$\mathfrak D = \{\vec x_n \}_{n=1,…,N} $,其中$\vec
x_n = (x_{n1},…,x_{n,D-1},y_n) $,数据集$\mathfrak D $由$D-1
$个候选属性$X_1,…,X_{D-1} $和三个目的属性$Y
$刻画。属性在本文中分成二种档次:连续型属性和离散型属性,各属性均有恐怕是三番五次型的只怕离散型的。本文的做事是因此对候选属性$X
\; (\in \{X_1,…,X_{D-1} \})$和目的属性$Y
$之间的关联性举办假使检验,量化各候选属性单独预测目的属性的力量,依据属性类型的比不上,能够区分为4种状态:

A.
树链剖分数据结构板题

解答

美高梅开户网址 1

1.2.1经常树状数组

树状数组好写好调,能用树状数组的时候尽量要利用。
树状数组从1初步。

int lowbit(int x) { return x & -x; }
int sum(int x) {
  int ans = 0;
  while (x) {
    ans += bit[x];
    x -= lowbit(x);
  }
  return ans;
}
void add(int i, int x) {
  while (i <= n) {
    bit[i] += x;
    i += lowbit(i);
  }
}

  1)$X$和$Y
$都以离散型属性;

题材大意:我没看,看不懂。

2.3.2

1.2.2推来推去区间修改的树状数组

只要对于区间\([a,
b]\)增加k,令g[i]为加了k未来的数组。
\[g[i] = s[i] (i < a)\]
\[g[i] = s[i] + i*k – k(a-1) (i >=
a 且 i <= b)\]
\[g[i] = s[i] + b*k – k(a-1) (i >
b)\]
因而我们进行八个树状数组。

  2)$X$是连接型属性,$Y
$是离散型属性;

基本思路:我不会。

题目

依照本节中高速排序所示轨迹的格式给出快速排序是何许将数组 E A S Y Q U E S
T I O N 排序的(出于练习的指标,可以忽略开首打乱数组的一部分)。

1.2.3二维树状数组

间接扩张就能够了。非凡的直观和强烈。

//bzoj3132
void change(int id, int x, int y, int val) {
  for (int i = x; i <= n; i += i & -i) {
    for (int j = y; j <= m; j += j & -j) {
      c[id][i][j] += val;
    }
  }
}
int qu(int id, int x, int y) {
  int ans = 0;
  for (int i = x; i > 0; i -= i & -i) {
    for (int j = y; j > 0; j -= j & -j) {
      ans += c[id][i][j];
    }
  }
  return ans;
}

  3)$X$是离散型属性,$Y
$是三番五次型属性;

参照代码:找Oyk老师和Czj老师去。

解答

美高梅开户网址 2

1.3线段树

  4)$X$和$Y
$都以一连型属性。

 

2.3.3

1.3.1数见不鲜线段树

以此线段树版本来自bzoj1798,代表了一种最基础的线条树类型,帮忙区间修改,多重标记下传等等操作。

//bzoj1798
void build(int k, int l, int r) {
  t[k].l = l;
  t[k].r = r;
  if (r == l) {
    t[k].tag = 1;
    t[k].add = 0;
    scanf("%lld", &t[k].sum);
    t[k].sum %= p;
    return;
  }
  int mid = (l + r) >> 1;
  build(k << 1, l, mid);
  build(k << 1 | 1, mid + 1, r);
  t[k].sum = (t[k << 1].sum + t[k << 1 | 1].sum) % p;
  t[k].add = 0;
  t[k].tag = 1;
}
void pushdown(int k) {
  if (t[k].add == 0 && t[k].tag == 1)
    return;
  ll ad = t[k].add, tag = t[k].tag;
  ad %= p, tag %= p;
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  t[k << 1].tag = (t[k << 1].tag * tag) % p;
  t[k << 1].add = ((t[k << 1].add * tag) % p + ad) % p;
  t[k << 1].sum = (((t[k << 1].sum * tag) % p + (ad * (mid - l + 1) % p)%p)%p) % p;
  t[k << 1 | 1].tag = (t[k << 1 | 1].tag * tag) % p;
  t[k << 1 | 1].add = ((t[k << 1 | 1].add * tag) % p + ad) % p;
  t[k << 1 | 1].sum = (((t[k << 1|1].sum * tag) % p + (ad * (r-mid) % p)%p)%p) % p;
  t[k].add = 0;
  t[k].tag = 1;
  return;
}
void update(int k) { t[k].sum = (t[k << 1].sum%p + t[k << 1 | 1].sum%p) % p; }
void add(int k, int x, int y, ll val) {
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  if (x <= l && r <= y) {
    t[k].add = (t[k].add + val) % p;
    t[k].sum = (t[k].sum + (val * (r - l + 1) % p) % p) % p;
    return;
  }
  pushdown(k);
  if (x <= mid)
    add(k << 1, x, y, val);
  if (y > mid)
    add(k << 1 | 1, x, y, val);
  update(k);
}
void mul(int k, int x, int y, ll val) {
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  if (x <= l && r <= y) {
    t[k].add = (t[k].add * val) % p;
    t[k].tag = (t[k].tag * val) % p;
    t[k].sum = (t[k].sum * val) % p;
    return;
  }
  pushdown(k);
  if (x <= mid)
    mul(k << 1, x, y, val);
  if (y > mid)
    mul(k << 1 | 1, x, y, val);
  update(k);
}
ll query(int k, int x, int y) {
  int l = t[k].l, r = t[k].r, mid = (l + r) >> 1;
  if (x <= l && r <= y) {
    return t[k].sum%p;
  }
  pushdown(k);
  ll ans = 0;
  if (x <= mid)
    ans = (ans + query(k << 1, x, y)) % p;
  if (y > mid)
    ans = (ans + query(k << 1 | 1, x, y)) % p;
  update(k);
  return ans%p;
}

  对属性$X$和$Y
$的关联性作假设检验的思索是:将$X$和$Y
$看作是八个随机变量,将数据集$\mathfrak D
$中相关属性下的数值作为是随机变量的观测值,即有观测值$x_1,…,x_N
$和$y_1,…,y_N $,依据这个观测值构造出与$X$和$Y
$独立性/关联性相关,且遵循已知分布(首假设$\chi^2 $、$t $和$F
$分布)的总计量,最终依照已知分布的可能率函数显著独立性检验的p值,用于属性$X$和$Y
$关联程度的心气。

B. The
background of water problem

题目

对此长度为 N 的数组,在 Quick.sort()
执行时,其最大因素最多会被换来多少次?

1.3.2可持久化线段树

一种可持久化数据结构。
接二连三一样的节点,只是把修改的重复链接,节省空间。
简单爆空间。
不时与权值线段树和二分答案结合。

int rt[maxn], lc[maxm], rc[maxm], sum[maxm];
void update(int l, int r, int x, int &y, int v) {
  y = ++sz;
  sum[y] = sum[x] + 1;
  if (l == r)
    return;
  lc[y] = lc[x];
  rc[y] = rc[x];
  int mid = (l + r) >> 1;
  if (v <= mid)
    update(l, mid, lc[x], lc[y], v);
  else
    update(mid + 1, r, rc[x], rc[y], v);
}

1、基于Pearson $\chi^2 $检验的离散变量关联性衡量

  针对属性$X$和$Y
$都是离散型的,可以通过Pearson $\chi^2 $检验方法检验$X$和$Y
$的独立性。设$X$有$s $种大概取值,$Y $有$t $种恐怕取值,记$N_{ij}
$为$X$取第$i $个值$Y$取第$j $个值的数据对象个数,且有$N_{i\cdot} =
\sum_{j=1}^t N_{ij} $,$N_{\cdot j} = \sum_{i=1}^s N_{ij}
$,可作如下列联表:

表 1:列联表

美高梅开户网址 3

  假使把$X$和$Y
$看作是随机变量,则有可能率分布,记为$p_{Xi} \; (i=1,…,s) $和$p_{Xj}
\; (j=1,…,t) $,且有二维随机变量$(X,Y) $的概率分布:$p_{ij} \;
(i=1,…,s; \; j= 1,…,t) $.易知,$\forall i=1,…,s; \; j=1,…,t
$有$p_{ij} = p_{Xi} \cdot p_{Yj} $时,$X$与$Y $独立。因而有假诺$$
\begin{equation} H: \; p_{ij} = p_{Xi} \cdot p_{Yj} \quad
(i=1,…,s; \; j=1,…,t) \end{equation}
$$观看值接受此要是的程度即为p值。 

  首先预计(1)式中的参数得$$
\begin{equation} \begin{split} \hat p_{Xi} = N_{i\cdot}/N \;
(i=1,…,s) \\ \hat p_{Yj} = N_{\cdot j}/N \; (j=1,…,t)
\end{split} \end{equation}$$进一步计算如下计算量:$$ \begin{equation}
\begin{split} K & = \sum_{i=1}^s \sum_{j=1}^t \frac{(N_{ij} –
N\hat p_{Xi} \hat p_{Yj})^2}{N\hat p_{Xi} \hat p_{Yj}} \\ & =
N \left ( \sum_{i=1}^s \sum_{j=1}^t \frac{N_{ij}^2}{N_{i
\cdot}N_{\cdot j}} – 1 \right) \end{split} \end{equation}
$$经求证总结量$K $的遍布收敛于自由度为$ d = (s-1)(t-1) $的$\chi^2
$分布,因而p值近似为:$$ \begin{equation} p_{value} = P(\chi^2_d
> K) \end{equation} $$因为$\chi^2_d
$的概率密度函数是已知的,全部简单通过(4)式计算出p值。

  由(3)式可知,当$K
$越小时,$|N_{ij}/N – \hat p_{Xi} \hat p_{Yj}| \rightarrow 0
$,由此拒绝(1)式若是的票房价值越小,即$X$与$Y
$独立的或然越大,由(4)式总括出的p值越大;相反地,p值越小,$X$与$Y
$独立的恐怕越小,相关联的恐怕性越大。由此p值越小,属性$X$越主要。

美高梅开户网址 4

图 1:Pearson $\chi^2
$检验的p值

标题大意(大写加粗的水题):给定$N$个学生和她俩$K$个学科的成绩$S_i$,再交由各科目$K_i$的权重顺序$Q_i$,求排行之后,拥有id为$X$的是哪个学生。

解答

N / 2
在快速排序中,一个因素要被换到,有以下三种情形
1.该因素是枢轴,在切分的尾声一步被换来
2.该因素位于枢轴错误的边缘,必要被换到到另一侧去
在意,以上二种处境在一回切分中只会出现一次

先是来看率先种意况,假使一个要素变为了枢轴
这正是说在随后的切分中该因素会被解除,不存在继续的交流。
故此我们的靶子应该是:
最大的成分总是出现在错误的边际,同时切分的次数尽可能多。

接下去大家来构思怎么样组织那样的数组
出于大家针对的是最大的成分,由此「错误的边沿」就是枢轴的右边。
为了使切分的次数尽或然多,我们须求保证最大值移动的相距尽量短。
但假设老是只移动一位的话,下二遍切分时最大值就会成为枢轴
譬如说 4 10 3 5 6,枢轴为 4,沟通后数组变为:
4 3 10 5 6
随后 4 和 3 交换
3 4 10 5 6
下1次切分时 10 会变成枢轴,不再到场后续的切分。
由此我们须求让最大值每一遍运动五个成分。

设想上边包车型地铁数组:
2 10 4 1 6 3 8 5 7 9
首先次切分的时候,枢轴为 2,10 和 1 进行沟通
数组变为:
2 1 4 10 6 3 8 5 7 9
随后枢轴调换,数组变为:
1 2 4 10 6 3 8 5 7 9
第3回切分,枢轴为 4,10 和 3 实行置换。
1 2 4 3 6 10 8 5 7 9
进而枢轴调换 数组变为:
1 2 3 4 6 10 8 5 7 9
其二遍切分,枢轴为 6,10 和 5 调换
1 2 3 4 6 5 8 10 7 9
接着枢轴调换,数组变为:
1 2 3 4 5 6 8 10 7 9
第4次切分,枢轴为 8,10 和 7 调换
1 2 3 4 5 6 8 7 10 9
枢轴沟通,数组变为
1 2 3 4 5 6 7 8 10 9
最终2回切分,枢轴为 10,直接交流
1 2 3 4 5 6 7 8 9 10

我们得以总计出要结构那样的数组的沙盘
a2 max a3 a1
其中 a1 < a2 < a3 < max
max 每轮切分移动两格,总共切分 N/ 2 次。

1.3.3标志永久化

一种奇怪的架势,又称李超先生线段树。
美高梅开户网址 ,给节点打下的标志不实行下传,而是一味在急需的时候举行下传,那正是所谓永久化标记。
美高梅开户网址 5

struct line {
  double k, b;
  int id;
  double getf(int x) { return k * x + b; };
};
bool cmp(line a, line b, int x) {
  if (!a.id)
    return 1;
  return a.getf(x) != b.getf(x) ? a.getf(x) < b.getf(x) : a.id < b.id;
}
const int maxn = 50010;
line t[maxn << 2];
line query(int k, int l, int r, int x) {
  if (l == r)
    return t[k];
  int mid = (l + r) >> 1;
  line tmp;
  if (x <= mid)
    tmp = query(k << 1, l, mid, x);
  else
    tmp = query(k << 1 | 1, mid + 1, r, x);
  return cmp(t[k], tmp, x) ? tmp : t[k];
}
void insert(int k, int l, int r, line x) {
  if (!t[k].id)
    t[k] = x;
  if (cmp(t[k], x, l))
    std::swap(t[k], x);
  if (l == r || t[k].k == x.k)
    return;
  int mid = (l + r) >> 1;
  double X = (t[k].b - x.b) / (x.k - t[k].k);
  if (X < l || X > r)
    return;
  if (X <= mid)
    insert(k << 1, l, mid, t[k]), t[k] = x;
  else
    insert(k << 1 | 1, mid + 1, r, x);
}
void Insert(int k, int l, int r, int x, int y, line v) {
  if (x <= l && r <= y) {
    insert(k, l, r, v);
    return;
  }
  int mid = (l + r) >> 1;
  if (x <= mid)
    Insert(k << 1, l, mid, x, y, v);
  if (y > mid)
    Insert(k << 1 | 1, mid + 1, r, x, y, v);
}

② 、基于单因子方差分析的连续变量与离散变量间关联性衡量

  先来探视怎样是单因子方差分析[\[1\]](file:///C:/Users/yua/Documents/%E5%85%B6%E5%AE%83/%E7%AC%94%E8%AE%B0/%E7%AC%94%E8%AE%B0%20-%20%E7%89%B9%E5%BE%81%E9%80%89%E6%8B%A9%E2%80%94%E2%80%94%E5%9F%BA%E4%BA%8E%E5%81%87%E8%AE%BE%E6%A3%80%E9%AA%8C%E7%9A%84Filter%E6%96%B9%E6%B3%95/Latex%E7%AC%94%E8%AE%B0%20-%20%E7%89%B9%E5%BE%81%E9%80%89%E6%8B%A9%E2%80%94%E2%80%94%E5%9F%BA%E4%BA%8E%E5%81%87%E8%AE%BE%E6%A3%80%E9%AA%8C%E7%9A%84Filter%E6%96%B9%E6%B3%95.docx#_edn1)

基本思路:尽管$K$唯有$10$,$S$唯有$100$,但有M组查询,所以本来不可能开个long
long去hash各个学生。大家简要点,开个结构体,排个序,就好了。

另请参阅

Number of largest element exchanges for quicksort-Stack
Overflow

1.4 平衡树

2.1 单因子方差分析(one-way ANOVA)

  即使有$I
$组试验,每组包括$J_i \; (i=1,…,I) $个样本,设$Y_{ij} $表示第$i
$个试验组的第$j $个样本,$\bar Y_i $表示第$i $组试验的样本均值,$\bar
Y $代表完全均值,有下式:$$ \begin{equation} \begin{split}
\sum_{i=1}^I \sum_{j=1}^{J_i} (Y_{ij} – \bar Y)^2 = &
\sum_{i=1}^I \sum_{j=1}^{J_i} [(Y_{ij} – \bar Y_i) + (\bar
Y_i – \bar Y)]^2 \\ = & \sum_{i=1}^I \sum_{j=1}^{J_i} (Y_{ij}

  • \bar Y_i)^2 + \sum_{i=1}^I \sum_{j=1}^{J_i} (\bar Y_i – \bar
    Y)^2 \\ & + 2\sum_{i=1}^I \sum_{j=1}^{J_i}(Y_{ij} – \bar
    Y_i)(\bar Y_i – \bar Y) \\ = & \sum_{i=1}^I \sum_{j=1}^{J_i}
    (Y_{ij} – \bar Y_i)^2 + \sum_{i=1}^I \sum_{j=1}^{J_i} (\bar
    Y_i – \bar Y)^2 \end{split} \end{equation} $$上式简记为$SS_{TOT} =
    SS_W + SS_B $,即总的平方和卓越组内平方和添加组间平方和,$SS_W
    $代表试验组内部数据散布的胸襟,$SS_B $表示试验组之间散布的襟怀。

  设第$i
$个试验组的期望为$\mu_i $,可提议只要:$$ \begin{equation} H: \mu_1
= \mu_2 = … = \mu_I \end{equation}
$$文献[1]中表达,当上述假若创设,有$$ \begin{equation}
\frac{E[SS_B]}{I-1} = \frac{E[SS_W]}{J-I} \end{equation}
$$式中$E[\cdot] $表示期待,$J=\sum_{i=1}^I J_i
$.然则当(6)式中假设不树立时,有$$ \begin{equation}
\frac{E[SS_B]}{I-1} > \frac{E[SS_W]}{J-I} \end{equation}
$$

  又有认证,计算量$$
\begin{equation} K = \frac{SS_B/(I-1)}{SS_W/(J-I)} \end{equation}
$$在(6)式假诺下遵从自由度为$I-1 $和$J-I $的$F $分布,即$K \sim F_{I-1,
J-I} $.依照(7)式和(8)式的分析,当(6)式假若为真时,$K
$的值接近于1,不然$K $的值应该较大。由此得以依照$P(F_{I-1,J-I} > K)
$接受或拒绝若是$H $.

参照代码

2.3.4

1.4.1 Splay伸展树

美高梅开户网址 6
一种最为常用的BBST。

int ch[maxn][2], fa[maxn];
int size[maxn], data[maxn], sum[maxn], la[maxn], ra[maxn], ma[maxn], cov[maxn],
    a[maxn];
bool rev[maxn];
int n, m, sz, rt;
std::stack<int> st;
void update(int x) {
  if (!x)
    return;
  la[x] = std::max(la[l(x)], sum[l(x)] + data[x] + std::max(0, la[r(x)]));
  ra[x] = std::max(ra[r(x)], sum[r(x)] + data[x] + std::max(0, ra[l(x)]));
  ma[x] = std::max(std::max(ma[l(x)], ma[r(x)]),
                   data[x] + std::max(0, ra[l(x)]) + std::max(0, la[r(x)]));
  sum[x] = sum[l(x)] + sum[r(x)] + data[x];
  size[x] = size[l(x)] + size[r(x)] + 1;
}
void reverse(int x) {
  if (!x)
    return;
  std::swap(ch[x][0], ch[x][1]);
  std::swap(la[x], ra[x]);
  rev[x] ^= 1;
}
void recover(int x, int v) {
  if (!x)
    return;
  data[x] = cov[x] = v;
  sum[x] = size[x] * v;
  la[x] = ra[x] = ma[x] = std::max(v, sum[x]);
}
void pushdown(int x) {
  if (!x)
    return;
  if (rev[x]) {
    reverse(ch[x][0]);
    reverse(ch[x][1]);
    rev[x] = 0;
  }
  if (cov[x] != -inf) {
    recover(ch[x][0], cov[x]);
    recover(ch[x][1], cov[x]);
    cov[x] = -inf;
  }
}
void zig(int x) {
  int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1;
  fa[ch[y][l] = ch[x][r]] = y;
  fa[ch[x][r] = y] = x;
  fa[x] = z;
  if (z)
    ch[z][ch[z][1] == y] = x;
  update(y);
  update(x);
}
void splay(int x, int aim = 0) {
  for (int y; (y = fa[x]) != aim; zig(x))
    if (fa[y] != aim)
      zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x);
  if (aim == 0)
    rt = x;
  update(x);
}
int pick() {
  if (!st.empty()) {
    int x = st.top();
    st.pop();
    return x;
  } else
    return ++sz;
}
int setup(int x) {
  int t = pick();
  data[t] = a[x];
  cov[t] = -inf;
  rev[t] = false;
  sum[t] = 0;
  la[t] = ra[t] = ma[t] = -inf;
  size[t] = 1;
  return t;
}
int build(int l, int r) {
  int mid = (l + r) >> 1, left = 0, right = 0;
  if (l < mid)
    left = build(l, mid - 1);
  int t = setup(mid);
  if (r > mid)
    right = build(mid + 1, r);
  if (left) {
    ch[t][0] = left, fa[left] = t;
  } else
    size[ch[t][0]] = 0;
  if (right) {
    ch[t][1] = right, fa[right] = t;
  } else
    size[ch[t][1]] = 0;
  update(t);
  return t;
}
int find(int k) {
  int x = rt, ans;
  while (x) {
    pushdown(x);
    if (k == size[ch[x][0]] + 1)
      return ans = x;
    else if (k > size[ch[x][0]] + 1) {
      k -= size[ch[x][0]] + 1;
      x = ch[x][1];
    } else
      x = ch[x][0];
  }
  return -1;
}
void del(int &x) {
  if (!x)
    return;
  st.push(x);
  fa[x] = 0;
  del(ch[x][0]);
  del(ch[x][1]);
  la[x] = ma[x] = ra[x] = -inf;
  x = 0;
}
void print(int x) {
  if (!x)
    return;
  if (ch[x][0])
    print(ch[x][0]);
  std::cout << data[x] << ' ';
  if (ch[x][1])
    print(ch[x][1]);
}

2.2 关联性衡量

  再来看看哪些利用单因子方差分析衡量离散变量和一而再变量间的关联性。以$X$是离散变量,$Y
$是再三再四变量为例($X$是接二连三变量,$Y
$是离散变量的情景可以类推)。设$X$有$I $种可能取值,记为$x_1, …, x_I
$,经过数据对象的次第变换之后,总是能够获得如下表的数量方式:

表 2:数据表

 

$\cdots $

$X$

$\cdots $

$Y $

$\vec x_1 $

$\cdots $

$x_1 $

$\cdots $

$y_{11} $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vec x_{J_1} $

$\cdots $

$x_1 $

$\cdots $

$y_{1J_1} $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vec x_{J_1 + … + J_{i-1} + 1} $

$\cdots $

$x_i $

$\cdots $

$y_{i1} $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vec x_{J_1 + … + J_{i-1} + J_i} $

$\cdots $

$x_i $

$\cdots $

$y_{iJ_i} $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vec x_{J_1 + … + J_{I-1} + 1} $

$\cdots $

$x_I $

$\cdots $

$y_{I1} $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vdots $

$\vec x_{J_1 + … + J_{I-1} + J_I} $

$\cdots $

$x_I $

$\cdots $

$y_{IJ_I} $

其中$N = J_1 + … +
J_I
$代表数据对象的总个数。也正是说数据对象能够像2.1小节那样依照$X$的取值划分为$I
$组。

  遵照概率论的学识,变量$X$与$Y
$相互独立,则有$$ \begin{equation} P(Y<y,X=x) = P(Y<y)P(X=x)
\end{equation} $$然则一旦变量$X$与$Y$不单独,有$$ \begin{equation}
P(Y<y,X=x) = P(Y<y|X=x)P(X=x) \end{equation} $$即当变量$X$与$Y
$相互独立刻$P(Y<y|X=x) = P(Y<y) $,因而在上述的各组中,变量$Y
$具有同样的分布函数:$$ \begin{equation} P(Y<y|X=x_1) = … =
P(Y<y|X=x_I) = P(Y<y) \end{equation} $$假设$X$取值$x_i
$的数码组下$Y \sim N(\mu_i, \sigma^2) $,变量$X$与$Y
$互相独登时必然有$\mu_1 = … = \mu_I $,那与(6)式中的假如$H
$完全相同,所以离散变量$X$与三番五次变量$Y
$之间的关联性能够经过单因子方差分析衡量。

  首先根据(5)式中的定义总计$SS_B
$和$SS_W $,而后依照(9)式总结出$K $值:$$ \begin{equation} K =
\frac{\sum_{i=1}^I J_i(\bar y_i – \bar y)^2 /
(I-1)}{\sum_{i=1}^I (J_i – 1) s^2_j / (N-I)} \end{equation}
$$其中$\bar y_i = \sum_{j=1}^{J_i} y_{ij} / J_i $,$\bar y =
\sum_{i=1}^I J_i \bar y_i / N $,$s^2_j = \sum_{j=1}^{J_i}
(y_{ij} – \bar y_i)^2 / (J_i – 1) $,进而总计p值:$$
\begin{equation} p_{value} = P(F_{I-1,J-I} > K) \end{equation}
$$

根据上文的辨析,当$X$与$Y
$关联性越小,$K $越小,此时p值越大;相反地,当$X $与$Y $关联性越大,$K
$越大,p值越小。

合法代码(将成绩和id分开放,制止在排序时复制构造大结构体):

题目

假使跳过起来打乱数组的操作,
交由七个带有 10 个成分的数组,
使得 Quick.sort() 所需的相比次数达到最坏情形。

1.5树套树

③ 、基于Pearson相关全面字展现著性检验的接二连三变量关联性度量

  针对均为一连变量的$X$和$Y
$,依照数据集$\mathfrak D $的取值,有数量对$(x_n, y_n) \;
(n=1,…,N) $,设均值和方差的总结量如下:$$ \bar x = \frac 1N
\sum_{n=1}^N x_n $$$$\bar y = \frac 1N \sum_{n=1}^N y_n$$$$
s^2_x = \frac 1{N-1} \sum_{n=1}^N (x_n – \bar x)^2 $$$$s^2_y =
\frac 1{N-1} \sum_{n=1}^N (y_n – \bar
y)^2$$简单获得二维随机变量$(X,Y) $的Pearson相关周详的总计量:$$
\begin{equation} r = \frac{\sum_{n=1}^N (x_n – \bar x)(y_n –
\bar y)}{(N-1)\sqrt{s^2_x s^2_y}} \end{equation}$$注意(15)式中$r
$仅是因此样本得到的相关周密的计算量,如设变量$X$与$Y $的相关周详为$\rho
$,则$r $只是$\rho $的估算。$X$是或不是与$Y $互相独立,$r=0
$说的不算,唯有$\rho =0 $才行,因而构造如下要是:$$ \begin{equation}
H:\rho = 0 \end{equation} $$必要经过样本总结获得的$r
$的值,对(16)式的$H $做检验,如若接受$H $表示$X$与$Y $相互独立。

  为了对$H
$做假使检验,首先对$r $作变换:$$ \begin{equation} K =
\frac{r\sqrt{N-2}}{\sqrt{1-r^2}} \end{equation} $$能够印证$K
$服从自由度为$N-2
$的t分布[\[2\]](file:///C:/Users/yua/Documents/%E5%85%B6%E5%AE%83/%E7%AC%94%E8%AE%B0/%E7%AC%94%E8%AE%B0%20-%20%E7%89%B9%E5%BE%81%E9%80%89%E6%8B%A9%E2%80%94%E2%80%94%E5%9F%BA%E4%BA%8E%E5%81%87%E8%AE%BE%E6%A3%80%E9%AA%8C%E7%9A%84Filter%E6%96%B9%E6%B3%95/Latex%E7%AC%94%E8%AE%B0%20-%20%E7%89%B9%E5%BE%81%E9%80%89%E6%8B%A9%E2%80%94%E2%80%94%E5%9F%BA%E4%BA%8E%E5%81%87%E8%AE%BE%E6%A3%80%E9%AA%8C%E7%9A%84Filter%E6%96%B9%E6%B3%95.docx#_edn2),即$K
\sim t_{N-2}
$.注解进度很复杂,本文仅叙述注明思路,假使深究,请参考文献[2]:假定随机变量$X$与$Y
$是正态无相关的,总括相关周到总计量$r $的可能率密度函数$f(r)
$,依照(17)式的涉嫌总结出$K $的可能率密度函数,发现$K
$的可能率密度函数与自由度为$N-2 $的t分布的概率密度函数完全一致,因而$K
\sim t_{N-2} $.

  基于$t_{N-2}
$分布对$H $做假诺检验,可计算p值:$$ \begin{equation} p_{value} =
\begin{cases} 0, & r^2 = 1 \\ 2 \cdot P(t_{N-2} > |K|), &
\text{otherwise} \end{cases} \end{equation} $$

美高梅开户网址 7


2:Pearson相关周详字展现著性检验的p值

  可是Pearson相关周全有1个相差:假诺任意变量$X$和$Y
$之间存在线性关系,即$Y = aX+b $,则Pearson相关周到适用,不过借使$X$和$Y
$之间的关联是非线性的,则Pearson相关周全不适用。举个例子:

  例 1:设$X
$与$Y $有如下的函数关系:$$ \begin{equation} y = \begin{cases} x, & 0
\le x < 1 \\ 3-x, & 1 \le x \le 2 \end{cases} \end{equation}
$$个中$X $服从区间$[0,2] $的均匀分布,可算得$X $与$Y $相关周全为:$$
\begin{equation} \rho_{XY} =
\frac{E[(X-E(X))(Y-E(Y))]}{\sqrt{E[(X-E(X))^2]E[(Y-E(Y))^2]}} =
\frac34 \end{equation} $$然则一旦有随机变量$Y’ = X
$,易得$\rho_{XY’} = 1 $,有$\rho_{XY} \ne \rho_{XY’} $.

  尝试分别对二维变量$(X,Y)
$和$(X,Y’) $作如图所示的离散化:$X$、$Y $、$Y’
$等宽的细分成五个小区间,依次以壹 、贰 、三 、4报到。

美高梅开户网址 8

图 3:离散化

  离散化后,图中黄绿方格代表概率为零,鲜蓝方格代表概率为$四分一$,如$P(X=1,Y=1)=四分一 $.依照$\chi^2 $检验的原理,易知离散化后,$(X,Y)
$与$(X,Y’) $有平等的关联性,与上述Pearson相关周全的结果并不相符。

  类似例
1的例证还有很多,那些都以出于Pearson不帮忙非线性相关性引起的,在动用上述格局做特色采用时,必要专注这一点。

美高梅开户网址 9美高梅开户网址 10

解答

老是只让枢轴变为已排序,那正是最坏情形。
那种时候枢轴是当下子数组的最大值 / 最小值。
鉴于在我们的兑现中延续取子数组的率先个要素作为枢轴。
因此三个已排序的数组能够直达最坏景况,相比次数达到 O(n^ 2)。
万一换作取最终贰个因素,最坏情状会成为逆序数组。

我们的兑现中一旦碰着与枢轴相等的要素也会停下循环,
由此借使数组中有再度的要素会压缩相比较次数。

例如:

1 2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10 11
3 4 5 6 7 8 9 10 11 12
4 5 6 7 8 9 10 11 12 13
5 6 7 8 9 10 11 12 13 14
6 7 8 9 10 11 12 13 14 15

1.5.1 线段树套splay

你必要写一种数据结构(可参照标题的题),来维护1个邯郸学步数列,其中必要提供以下操作:

  1. 查询k在区间内的排行
  2. 查询区间内排行为k的值
  3. 修改某一个人值上的数值
  4. 查询k在区间内的先辈(四驱定义为小于x,且最大的数)
  5. 查询k在距离内的后继(后继定义为大于x,且最小的数)

    #include
    #include
    #include
    using namespace std;
    const int maxn = 4e6 + 5;
    const int inf = 1e9;
    int ans, n, m, opt, l, r, k, pos, sz, Max;
    int a[maxn], fa[maxn], ch[maxn][2], size[maxn], cnt[maxn], data[maxn], rt[maxn];
    inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {

    if (ch == '-')
      f = -1;
    ch = getchar();
    

    }
    while (isdigit(ch)) {

    x = x * 10 + ch - '0';
    ch = getchar();
    

    }
    return x * f;
    }
    struct Splay {
    void clear(int x) {

    fa[x] = ch[x][0] = ch[x][1] = size[x] = cnt[x] = data[x] = 0;
    

    }
    void update(int x) {

    if (x) {
      size[x] = cnt[x];
      if (ch[x][0])
        size[x] += size[ch[x][0]];
      if (ch[x][1])
        size[x] += size[ch[x][1]];
    }
    

    }
    void zig(int x) {

    int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1;
    fa[ch[y][l] = ch[x][r]] = y;
    fa[ch[x][r] = y] = x;
    fa[x] = z;
    if (z)
      ch[z][ch[z][1] == y] = x;
    update(y);
    update(x);
    

    }
    void splay(int i, int x, int aim = 0) {

    for (int y; (y = fa[x]) != aim; zig(x))
      if (fa[y] != aim)
        zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x);
    if (aim == 0)
      rt[i] = x;
    

    }
    void insert(int i, int v) {

    int x = rt[i], y = 0;
    if (x == 0) {
      rt[i] = x = ++sz;
      fa[x] = ch[x][0] = ch[x][1] = 0;
      size[x] = cnt[x] = 1;
      data[x] = v;
      return;
    }
    while (1) {
      if (data[x] == v) {
        cnt[x]++;
        update(y);
        splay(i, x);
        return;
      }
      y = x;
      x = ch[x][v > data[x]];
      if (x == 0) {
        ++sz;
        fa[sz] = y;
        ch[sz][0] = ch[sz][1] = 0;
        size[sz] = cnt[sz] = 1;
        data[sz] = v;
        ch[y][v > data[y]] = sz;
        update(y);
        splay(i, sz);
        rt[i] = sz;
        return;
      }
    }
    

    }
    void find(int i, int v) {

    int x = rt[i];
    while (1) {
      if (data[x] == v) {
        splay(i, x);
        return;
      } else
        x = ch[x][v > data[x]];
    }
    

    }
    int pre(int i) {

    int x = ch[rt[i]][0];
    while (ch[x][1])
      x = ch[x][1];
    return x;
    

    }
    int succ(int i) {

    int x = ch[rt[i]][1];
    while (ch[x][0])
      x = ch[x][0];
    return x;
    

    }
    void del(int i) {

    int x = rt[i];
    if (cnt[x] > 1) {
      cnt[x]--;
      return;
    }
    if (!ch[x][0] && !ch[x][1]) {
      clear(rt[i]);
      rt[i] = 0;
      return;
    }
    if (!ch[x][0]) {
      int oldroot = x;
      rt[i] = ch[x][1];
      fa[rt[i]] = 0;
      clear(oldroot);
      return;
    }
    if (!ch[x][1]) {
      int oldroot = x;
      rt[i] = ch[x][0];
      fa[rt[i]] = 0;
      clear(oldroot);
      return;
    }
    int y = pre(i), oldroot = x;
    splay(i, y);
    rt[i] = y;
    ch[rt[i]][1] = ch[oldroot][1];
    fa[ch[oldroot][1]] = rt[i];
    clear(oldroot);
    update(rt[i]);
    return;
    

    }
    int rank(int i, int v) {

    int x = rt[i], ans = 0;
    while (1) {
      if (!x)
        return ans;
      if (data[x] == v)
        return ((ch[x][0]) ? size[ch[x][0]] : 0) + ans;
      else if (data[x] < v) {
        ans += ((ch[x][0]) ? size[ch[x][0]] : 0) + cnt[x];
        x = ch[x][1];
      } else if (data[x] > v) {
        x = ch[x][0];
      }
    }
    

    }
    int find_pre(int i, int v) {

    int x = rt[i];
    while (x) {
      if (data[x] < v) {
        if (ans < data[x])
          ans = data[x];
        x = ch[x][1];
      } else
        x = ch[x][0];
    }
    return ans;
    

    }
    int find_succ(int i, int v) {

    int x = rt[i];
    while (x) {
      if (v < data[x]) {
        if (ans > data[x])
          ans = data[x];
        x = ch[x][0];
      } else
        x = ch[x][1];
    }
    return ans;
    

    }
    } sp;
    void insert(int k, int l, int r, int x, int v) {
    int mid = (l + r) >> 1;
    sp.insert(k, v);
    if (l == r)

    return;
    

    if (x <= mid)

    insert(k << 1, l, mid, x, v);
    

    else

    insert(k << 1 | 1, mid + 1, r, x, v);
    

    }
    void askrank(int k, int l, int r, int x, int y, int val) {
    int mid = (l + r) >> 1;
    if (x <= l && r <= y) {

    ans += sp.rank(k, val);
    return;
    

    }
    if (x <= mid)

    askrank(k << 1, l, mid, x, y, val);
    

    if (mid + 1 <= y)

    askrank(k << 1 | 1, mid + 1, r, x, y, val);
    

    }
    void change(int k, int l, int r, int pos, int val) {
    int mid = (l + r) >> 1;
    sp.find(k, a[pos]);
    sp.del(k);
    sp.insert(k, val);
    if (l == r)

    return;
    

    if (pos <= mid)

    change(k << 1, l, mid, pos, val);
    

    else

    change(k << 1 | 1, mid + 1, r, pos, val);
    

    }
    void askpre(int k, int l, int r, int x, int y, int val) {
    int mid = (l + r) >> 1;
    if (x <= l && r <= y) {

    ans = max(ans, sp.find_pre(k, val));
    return;
    

    }
    if (x <= mid)

    askpre(k << 1, l, mid, x, y, val);
    

    if (mid + 1 <= y)

    askpre(k << 1 | 1, mid + 1, r, x, y, val);
    

    }
    void asksucc(int k, int l, int r, int x, int y, int val) {
    int mid = (l + r) >> 1;
    if (x <= l && r <= y) {

    ans = min(ans, sp.find_succ(k, val));
    return;
    

    }
    if (x <= mid)

    asksucc(k << 1, l, mid, x, y, val);
    

    if (mid + 1 <= y)

    asksucc(k << 1 | 1, mid + 1, r, x, y, val);
    

    }
    int main() {
    #ifdef D
    freopen(“input”, “r”, stdin);
    #endif
    n = read(), m = read();
    for (int i = 1; i <= n; i++)

    a[i] = read(), Max = max(Max, a[i]), insert(1, 1, n, i, a[i]);
    

    for (int i = 1; i <= m; i++) {

    opt = read();
    if (opt == 1) {
      l = read(), r = read(), k = read();
      ans = 0;
      askrank(1, 1, n, l, r, k);
      printf("%d\n", ans + 1);
    } else if (opt == 2) {
      l = read(), r = read(), k = read();
      int head = 0, tail = Max + 1;
      while (head != tail) {
        int mid = (head + tail) >> 1;
        ans = 0;
        askrank(1, 1, n, l, r, mid);
        if (ans < k)
          head = mid + 1;
        else
          tail = mid;
      }
      printf("%d\n", head - 1);
    } else if (opt == 3) {
      pos = read();
      k = read();
      change(1, 1, n, pos, k);
      a[pos] = k;
      Max = std::max(Max, k);
    } else if (opt == 4) {
      l = read();
      r = read();
      k = read();
      ans = 0;
      askpre(1, 1, n, l, r, k);
      printf("%d\n", ans);
    } else if (opt == 5) {
      l = read();
      r = read();
      k = read();
      ans = inf;
      asksucc(1, 1, n, l, r, k);
      printf("%d\n", ans);
    }
    

    }

参考文献


[1] Rice, J.A.著,
田金方译. 数理总括与数据解析(原书第二版)[M]. 机械工业出版社, 2011, pp.
328-333.

[2] Fisz M.
可能率论及数理总计[M]. 法国首都科技社, 壹玖陆肆.

 1 #include <cstdio>
 2 #include <string.h>
 3 #include <algorithm>
 4 #include <vector>
 5 using namespace std;
 6 
 7 int N,K,M,X;
 8 int people[1005][11];
 9 int cmpOrder[11];
10 
11 struct CmpNode{
12     CmpNode(int x):id(x){}
13     int id;
14     bool operator < (const CmpNode &other) const
15     {
16         for(int i=0; i<K; i++)
17         {
18             if(people[this->id][cmpOrder[i]] > people[other.id][cmpOrder[i]])
19                 return true;
20             else if(people[this->id][cmpOrder[i]] < people[other.id][cmpOrder[i]])
21                 return false;
22         }
23         return this->id<other.id;
24     }
25 };
26 
27 void solve(FILE *fin=stdin, FILE *fout=stdout)
28 {
29     int t;
30     fscanf(fin,"%d",&t);
31     while(t--)
32     {
33         fscanf(fin,"%d%d",&N,&K);
34         vector<CmpNode> nodes;
35         for(int i=0;i<N;i++)
36         {
37             nodes.push_back(CmpNode(i));
38             for(int j=1;j<=K;j++)
39                 fscanf(fin,"%d",&people[i][j]);
40         }
41         fscanf(fin,"%d",&M);
42         while(M--)
43         {
44             for(int i=0;i<K;i++)
45                 fscanf(fin,"%d",cmpOrder+i);
46             fscanf(fin,"%d", &X);
47             sort(nodes.begin(),nodes.end());
48             fprintf(fout,"%d\n",nodes[X-1].id+1);
49         }
50     }
51 }
52 
53 int main()
54 {
55     solve(stdin,stdout);
56     return 0;
57 }
另请参阅

Analysis of
Quicksort-khanacademy
Worst case for QuickSort – When can it occur?-Stack
Overflow

1.6点分治

void getroot(int x, int fa) {
  size[x] = 1;
  f[x] = 0;
  for (int i = 0; i < G[x].size(); i++) {
    edge &e = G[x][i];
    if (!vis[e.to] && e.to != fa) {
      getroot(e.to, x);
      size[x] += size[e.to];
      f[x] = max(f[x], size[e.to]);
    }
  }
  f[x] = max(f[x], sum - size[x]);
  if (f[x] < f[rt])
    rt = x;
}
void getdeep(int x, int fa) {
  cnt[deep[x]]++;
  for (int i = 0; i < G[x].size(); i++) {
    edge &e = G[x][i];
    if (!vis[e.to] && e.to != fa) {
      deep[e.to] = (deep[x] + e.weigh) % 3;
      getdeep(e.to, x);
    }
  }
}
int cal(int x, int now) {
  cnt[0] = cnt[1] = cnt[2] = 0;
  deep[x] = now;
  getdeep(x, 0);
  return cnt[1] * cnt[2] * 2 + cnt[0] * cnt[0];
}
void work(int x) {
  ans += cal(x, 0); //统计不同子树通过重心的个数
  vis[x] = 1;
#ifndef ONLINE_JUDGE
  printf("In root %d: %d\n", rt, ans);
#endif
  for (int i = 0; i < G[x].size(); i++) {
    edge &e = G[x][i];
    if (!vis[e.to]) {
      ans -= cal(e.to, e.weigh); //去除在同一个子树中被重复统计的
      rt = 0;
      sum = size[e.to];
      getroot(e.to, 0);
      work(rt); // Decrease and Conquer
    }
  }
}

B. The background of water
problem

2.3.5

1.7树链剖分

void dfs1(int x) {
  vis[x] = size[x] = 1;
  for (int i = 1; i <= 17; i++) {
    if (deep[x] < (1 << i))
      break;
    fa[x][i] = fa[fa[x][i - 1]][i - 1];
  }
  for (int i = head[x]; i; i = e[i].next) {
    if (!vis[e[i].to]) {
      deep[e[i].to] = deep[x] + 1;
      fa[e[i].to][0] = x;
      dfs1(e[i].to);
      size[x] += size[e[i].to];
    }
  }
}
void dfs2(int x, int chain) {
  pl[x] = ++sz;
  que[sz] = x;
  belong[x] = chain;
  int k = 0;
  for (int i = head[x]; i; i = e[i].next)
    if (deep[e[i].to] > deep[x] && size[k] < size[e[i].to])
      k = e[i].to;
  if (!k)
    return;
  dfs2(k, chain);
  for (int i = head[x]; i; i = e[i].next)
    if (e[i].to != k && deep[e[i].to] > deep[x])
      dfs2(e[i].to, e[i].to);
}
void update(int k) {}
void build(int k, int l, int r) {
  t[k].l = l, t[k].r = r, t[k].s = 1, t[k].tag = -1;
  if (l == r) {
    t[k].lc = t[k].rc = value[que[l]];
    return;
  }
  int mid = (l + r) >> 1;
  build(k << 1, l, mid);
  build(k << 1 | 1, mid + 1, r);
  update(k);
}
int lca(int x, int y) {
  if (deep[x] < deep[y])
    std::swap(x, y);
  int t = deep[x] - deep[y];
  for (int i = 0; i <= 17; i++) {
    if (t & (1 << i))
      x = fa[x][i];
  }
  for (int i = 17; i >= 0; i--) {
    if (fa[x][i] != fa[y][i]) {
      x = fa[x][i];
      y = fa[y][i];
    }
  }
  if (x == y)
    return x;
  else
    return fa[x][0];
}
void pushdown(int k) {}
int query(int k, int x, int y) {}//线段树操作
int getcolor(int k, int pos) {}//线段树操作
int solvesum(int x, int f) {
  int sum = 0;
  while (belong[x] != belong[f]) {
    sum += query(1, pl[belong[x]], pl[x]);
    if (getcolor(1, pl[belong[x]]) == getcolor(1, pl[fa[belong[x]][0]]))
      sum--;
    x = fa[belong[x]][0];
  }
  sum += query(1, pl[f], pl[x]);
  return sum;
}
void change(int k, int x, int y, int c) {}//线段树操作
void solvechange(int x, int f, int c) {
  while (belong[x] != belong[f]) {
    change(1, pl[belong[x]], pl[x], c);
    x = fa[belong[x]][0];
  }
  change(1, pl[f], pl[x], c);
}
void solve() {
  dfs1(1);
  dfs2(1, 1);
  build(1, 1, n);
  for (int i = 1; i <= m; i++) {
    char ch[10];
    scanf("%s", ch);
    if (ch[0] == 'Q') {
      int a, b;
      scanf("%d %d", &a, &b);
      int t = lca(a, b);
      printf("%d\n", solvesum(a, t) + solvesum(b, t) - 1);
    } else {
      int a, b, c;
      scanf("%d %d %d", &a, &b, &c);
      int t = lca(a, b);
      solvechange(a, t, c);
      solvechange(b, t, c);
    }
  }
}

非官方代码(那是清一色放在结构体的例证,无论算法比赛依旧工程都不建议如此排序):

题目

付给一段代码将已知只有三种主键值的数组排序。

1.8 Link-Cut Tree

对于树上的操作,大家明日早已有了树链剖分能够拍卖这几个题材。不过树链剖分不辅助动态维护树上的拓扑结构。所以大家需求Link-Cut
Tree(lct)来缓解那种动态树难题。顺带一提的是,动态树也是Tarjan发明的。

先是大家介绍一个概念:Preferred
path(实边),别的的边都以虚边。大家选用splay来实时地掩护那条路线。

lct的着力操作是access。access操作能够把虚边变为实边,通过改动splay的拓扑结构来保障实边。

有了这一个数据结构,大家逐条来设想七个操作。

对于链接七个节点,我们需求首先把x节点变为他所在树的根节点,然后直接令fa[x]
= y即可。

怎么换根呢?稍微思考一下得以发现,大家直接把从根到她的门路反转即可。

对此第③种操作,大家间接断开拓扑关系即可。

除此以外实现的时候要留意,splay的根节点的父亲是她的上二个节点。所以zig和splay的写法应该13分留心。

inline bool isroot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }
void pushdown(int k) {
  if (rev[k]) {
    rev[k] = 0;
    rev[ch[k][0]] ^= 1;
    rev[ch[k][1]] ^= 1;
    std::swap(ch[k][0], ch[k][1]);
  }
}
void zig(int x) {
  int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1;
  if (!isroot(y))
    ch[z][ch[z][1] == y] = x;
  fa[ch[y][l] = ch[x][r]] = y;
  fa[ch[x][r] = y] = x;
  fa[x] = z;
}
void splay(int x) {
  stack<int> st;
  st.push(x);
  for (int i = x; !isroot(i); i = fa[i])
    st.push(fa[i]);
  while (!st.empty()) {
    pushdown(st.top());
    st.pop();
  }
  for (int y = fa[x]; !isroot(x); zig(x), y = fa[x])
    if (!isroot(y))
      zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x);
}
void access(int x) {
  int t = 0;
  while (x) {
    splay(x);
    ch[x][1] = t;
    t = x;
    x = fa[x];
  }
}
void rever(int x) {
  access(x);
  splay(x);
  rev[x] ^= 1;
}
void link(int x, int y) {
  rever(x);
  fa[x] = y;
  splay(x);
}
void cut(int x, int y) {
  rever(x);
  access(y);
  splay(y);
  ch[y][0] = fa[x] = 0;
}
int find(int x) {
  access(x);
  splay(x);
  int y = x;
  while (ch[y][0])
    y = ch[y][0];
  return y;
}

美高梅开户网址 11美高梅开户网址 12

解答

法定达成:

算法 gif 动图
美高梅开户网址 13

1.9 KMP

对于字符串S的前i个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它自身除了),最长的尺寸记作next[i]

多用于DP

for (int i = 2; i <= m; i++) {
    while (j > 0 && ch[j + 1] != ch[i])
      j = p[j];
    if (ch[j + 1] == ch[i])
      j++;
    p[i] = j;
  }
 1 #include <stdio.h>
 2 #include <algorithm>
 3 
 4 int N, K, M, X;
 5 int order[11];
 6 
 7 struct stu {
 8     int id, score[11];
 9     bool operator <(const stu&x) const {
10         for(int i=1; i<=K; i++)
11             if(score[order[i]] != x.score[order[i]])
12                 return score[order[i]] > x.score[order[i]];
13         return id < x.id;
14     }
15 }student[1001];
16 
17 int main() {
18     int T;
19     scanf("%d", &T);
20     while(T--) {
21         scanf("%d%d", &N, &K);
22         for(int i=1; i<=N; i++) {
23             student[i].id = i;
24             for(int j=1; j<=K; j++)
25                 scanf("%d", &student[i].score[j]);
26         }
27         scanf("%d", &M);
28         while(M--) {
29             for(int i=1; i<=K; i++)
30                 scanf("%d", order+i);
31             std::sort(student+1, student+1+N);
32             scanf("%d", &X);
33             printf("%d\n", student[X].id);
34         }
35     }
36     return 0;
37 }
代码
namespace Quick
{
    /// <summary>
    /// 用于将只有两种元素的数组排序。
    /// </summary>
    public class Sort2Distinct : BaseSort
    {
        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public Sort2Distinct() { }

        /// <summary>
        /// 对数组 a 进行排序。
        /// </summary>
        /// <typeparam name="T">数组 a 的元素类型。</typeparam>
        /// <param name="a"></param>
        public override void Sort<T>(T[] a)
        {
            int lt = 0, gt = a.Length - 1;
            int i = 0;
            while (i <= gt)
            {
                int cmp = a[i].CompareTo(a[lt]);
                if (cmp < 0)
                    Exch(a, lt++, i++);
                else if (cmp > 0)
                    Exch(a, i, gt--);
                else
                    i++;
            }
        }
    }
}

1.10 AC自动机

KMP在Trie上的扩大.

void insert(char s[101]) {
  int now = 1, c;
  for (int i = 0; i < strlen(s); i++) {
    c = s[i] - 'A' + 1;
    if (a[now][c])
      now = a[now][c];
    else
      now = a[now][c] = ++sz;
  }
  leaf[now] = 1;
}
void ac() {
  std::queue<int> q;
  q.push(1);
  point[1] = 0;
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int i = 1; i <= 26; i++) {
      if (!a[u][i])
        continue;
      int k = point[u];
      while (!a[k][i])
        k = point[k];
      point[a[u][i]] = a[k][i];
      if (leaf[a[k][i]])
        leaf[a[u][i]] = 1;
      q.push(a[u][i]);
    }
  }
}

B. The background of water
problem 

另请参阅

Quick

1.11 后缀数组

void getsa(int sa[maxn], int rank[maxn], int Sa[maxn], int Rank[maxn]) {
  for (int i = 1; i <= n; i++)
    v[rank[sa[i]]] = i;
  for (int i = n; i >= 1; i--)
    if (sa[i] > k)
      Sa[v[rank[sa[i] - k]]--] = sa[i] - k;
  for (int i = n - k + 1; i <= n; i++)
    Sa[v[rank[i]]--] = i;
  for (int i = 1; i <= n; i++)
    Rank[Sa[i]] = Rank[Sa[i - 1]] + (rank[Sa[i - 1]] != rank[Sa[i]] ||
                                     rank[Sa[i - 1] + k] != rank[Sa[i] + k]);
}
void getheight(int sa[maxn], int rank[maxn]) {
  int i, k = 0;
  for (i = 1; i <= n; height[rank[i++]] = k) {
    if (k)
      k--;
    int j = sa[rank[i] - 1];
    while (a[i + k] == a[j + k])
      k++;
  }
}
void da() {
  p = 0, q = 1, k = 1;
  for (int i = 1; i <= n; i++)
    v[a[i]]++;
  for (int i = 1; i <= 2; i++)
    v[i] += v[i - 1];
  for (int i = 1; i <= n; i++)
    sa[p][v[a[i]]--] = i;
  for (int i = 1; i <= n; i++)
    rank[p][sa[p][i]] =
        rank[p][sa[p][i - 1]] + (a[sa[p][i - 1]] != a[sa[p][i]]);
  while (k < n) {
    getsa(sa[p], rank[p], sa[q], rank[q]);
    p ^= 1;
    q ^= 1;
    k <<= 1;
  }
  getheight(sa[p], rank[p]);
}

 

2.3.6

1.12 后缀自动机

美高梅开户网址 14

struct Suffix_Automaton {
  ll fa[maxn], trans[maxn][26], len[maxn], right[maxn];
  ll last, root, sz;
  bool flag[maxn];
  void init() {
    memset(flag, 0, sizeof(flag));
    sz = 0;
    last = root = ++sz;
  }
  void insert(ll x) {
    ll p = last, np = last = ++sz;
    len[np] = len[p] + 1;
    flag[np] = 1;
    right[np] = right[p] + 1;
    for (; !trans[p][x]; p = fa[p])
      trans[p][x] = np;
    if (p == 0)
      fa[np] = root;
    else {
      ll q = trans[p][x];
      if (len[q] == len[p] + 1) {
        fa[np] = q;
      } else {
        ll nq = ++sz;
        fa[nq] = fa[q];
        memcpy(trans[nq], trans[q], sizeof(trans[q]));
        len[nq] = len[p] + 1;
        fa[q] = fa[np] = nq;
        for (; trans[p][x] == q; p = fa[p])
          trans[p][x] = nq;
      }
    }
  }
} sam;

C. Oyk
cut paper forever

题目

编排一段代码来总括 $ C_N $ 的准确值,
在 $ N=100、1000 $ 和 $10 000 $ 的景况下相比准确值和估摸值 $ 2NlnN $
的差别。

1.13 Manacher

void manacher() {
  int mx = 1, id = 1;
  for (int i = n; i; i--)
    str[i * 2] = '#', str[i * 2 - 1] = str[i];
  n <<= 1;
  for (int i = 1; i <= n; i++) {
    p[i] = std::min(p[id * 2 - i], mx - i);
    while (i - p[i] > 0 && str[i - p[i]] == str[i + p[i]]) {
      int al = (i - p[i]) / 2 + 1;
      int ar = (i + p[i] + 1) / 2;
      // printf("%d %d\n", al, ar);
      sam.query(al, ar);
      p[i]++;
    }
    if (i + p[i] > mx)
      mx = i + p[i], id = i;
  }
}

难题马虎

解答

运行结果如下:
美高梅开户网址 15

1.14 分块

任凭给1个例题吧.

给定1个数列,您需求援助一下三种操作:1.给[l,r]同加一个数.
2.询问[l,r]中有个别许数字当先或等于v.

把数量分为\(\sqrt
n\)一份,那么对于每二个询问,大家都得以把这么些查询分为\(\sqrt n\)个区间,修改的时候也是\(O(\sqrt
n)\)的级别,所以总的复杂度正是\(O(\sqrt n log \sqrt n)\)

具体地,对于每一块,大家都存款和储蓄排序前和排序后的行列,那样我们就一举成功了这一个题。

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
int n, q, m, block;
const int maxn = 1000001;
int a[maxn], b[maxn], pos[maxn], add[maxn];
using std::sort;
using std::min;
inline int read() {}
inline void reset(int x) {
  int l = (x - 1) * block + 1, r = min(x * block, n);
  for (int i = l; i <= r; i++)
    b[i] = a[i];
  sort(b + l, b + r + 1);
}
inline int find(int x, int v) {
  int l = (x - 1) * block + 1, r = min(x * block, n);
  int last = r;
  while (l <= r) {
    int mid = (l + r) >> 1;
    if (b[mid] < v)
      l = mid + 1;
    else
      r = mid - 1;
  }
  return last - l + 1;
}
inline void update(int x, int y, int v) {
  if (pos[x] == pos[y]) {
    for (int i = x; i <= y; i++)
      a[i] = a[i] + v;
  } else {
    for (int i = x; i <= pos[x] * block; i++)
      a[i] = a[i] + v;
    for (int i = (pos[y] - 1) * block + 1; i <= y; i++)
      a[i] = a[i] + v;
  }
  reset(pos[x]);
  reset(pos[y]);
  for (int i = pos[x] + 1; i < pos[y]; i++)
    add[i] += v;
}
inline int query(int x, int y, int v) {
  int sum = 0;
  if (pos[x] == pos[y]) {
    for (int i = x; i <= y; i++)
      if (a[i] + add[pos[i]] >= v)
        sum++;
  } else {
    for (int i = x; i <= pos[x] * block; i++)
      if (a[i] + add[pos[i]] >= v)
        sum++;
    for (int i = (pos[y] - 1) * block + 1; i <= y; i++)
      if (a[i] + add[pos[i]] >= v)
        sum++;
    for (int i = pos[x] + 1; i < pos[y]; i++)
      sum += find(i, v - add[i]);
  }
  return sum;
}
int main() {
#ifndef ONLINE_JUDGE
  freopen("input", "r", stdin);
#endif
  n = read(), q = read();
  if (n >= 500000)
    block = 3676;
  else if (n >= 5000) {
    block = 209;
  } else
    block = int(sqrt(n));
  for (int i = 1; i <= n; i++) {
    a[i] = read();
    pos[i] = (i - 1) / block + 1;
    b[i] = a[i];
  }
  if (n % block)
    m = n / block + 1;
  else
    m = n / block;
  for (int i = 1; i <= m; i++)
    reset(i);
  for (int i = 1; i <= q; i++) {
    char ch[5];
    int x, y, v;
    scanf("%s", ch);
    x = read(), y = read(), v = read();
    if (ch[0] == 'M')
      update(x, y, v);
    else
      printf("%d\n", query(x, y, v));
  }
}

  永远的Oyk剪纸(灰霾)。Oyk给面子Z大师,玩$C$轮剪纸,每轮给定一条长为$k$个单位的纸带,Z大师先手可以剪去(任意)$N_1$个单位,但必须剪或任何拿走。此后每轮都只可以剪$1$到$2\times
N_1$个单位,能拿走最后一段纸带的人克制,问Oyk第二回获胜是第几轮。

代码

新建三个 QuickSortAnalyze 类,在 QuickSort 的基础上添加3个 CompareCount
属性,用于记录比较次数。重写 Less 方法,每调用3遍就让 CompareCount 扩充1 。

using System;
using System.Diagnostics;

namespace Quick
{
    /// <summary>
    /// 自动记录比较次数以及子数组数量的快速排序类。
    /// </summary>
    public class QuickSortAnalyze : BaseSort
    {
        /// <summary>
        /// 比较次数。
        /// </summary>
        public int CompareCount { get; set; }

        /// <summary>
        /// 是否启用打乱。
        /// </summary>
        public bool NeedShuffle { get; set; }

        /// <summary>
        /// 是否显示轨迹。
        /// </summary>
        public bool NeedPath { get; set; }

        /// <summary>
        /// 大小为 0 的子数组数量。
        /// </summary>
        public int Array0Num { get; set; }

        /// <summary>
        /// 大小为 1 的子数组数量。
        /// </summary>
        public int Array1Num { get; set; }

        /// <summary>
        /// 大小为 2 的子数组数量。
        /// </summary>
        public int Array2Num { get; set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortAnalyze()
        {
            this.CompareCount = 0;
            this.NeedShuffle = true;
            this.NeedPath = false;
            this.Array0Num = 0;
            this.Array1Num = 0;
            this.Array2Num = 0;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            this.Array0Num = 0;
            this.Array1Num = 0;
            this.Array2Num = 0;
            this.CompareCount = 0;
            if (this.NeedShuffle)
                Shuffle(a);
            if (this.NeedPath)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    Console.Write("  ");
                }
                Console.WriteLine("\tlo\tj\thi");
            }
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            if (hi - lo == 1)
                this.Array2Num++;
            else if (hi == lo)
                this.Array1Num++;
            else if (hi < lo)
                this.Array0Num++;

            if (hi <= lo)                   // 别越界
                return;
            int j = Partition(a, lo, hi);
            if (this.NeedPath)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    Console.Write(a[i] + " ");
                }
                Console.WriteLine("\t" + lo + "\t" + j + "\t" + hi);
            }
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }

        /// <summary>
        /// 比较第一个元素是否小于第二个元素。
        /// </summary>
        /// <typeparam name="T">要比较的元素类型。</typeparam>
        /// <param name="a">第一个元素。</param>
        /// <param name="b">第二个元素。</param>
        /// <returns></returns>
        new protected bool Less<T>(T a, T b) where T : IComparable<T>
        {
            this.CompareCount++;
            return a.CompareTo(b) < 0;
        }
    }
}

主方法

using System;
using Quick;

namespace _2._3._6
{
    /*
     * 2.3.6
     * 
     * 编写一段代码来计算 C_N 的准确值,
     * 在 N=100、1000 和 10 000 的情况下比较准确值和估计值 2NlnN 的差距。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("N\t准确值\t估计值\t比值");
            QuickSortAnalyze sort = new QuickSortAnalyze();
            int N = 100;
            int trialTime = 500;
            for (int i = 0; i < 3; i++)
            {
                int sumOfCompare = 0;
                int[] a = new int[N];
                for (int j = 0; j < trialTime; j++)
                {
                    for (int k = 0; k < N; k++)
                    {
                        a[k] = k;
                    }
                    SortCompare.Shuffle(a);
                    sort.Sort(a);
                    sumOfCompare += sort.CompareCount;
                }
                int averageCompare = sumOfCompare / trialTime;
                double estimatedCompare = 2 * N * Math.Log(N);
                Console.WriteLine(N + "\t" + averageCompare + "\t" + (int)estimatedCompare + "\t" + averageCompare / estimatedCompare);
                N *= 10;
            }
        }
    }
}

1.15 莫队算法

假设你了然了[L,R]的答案。你可以在O(1)的小时下获得[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案的话。就足以动用莫队算法。
对此莫队算法本人觉得就是暴力。只是预先领会了颇具的摸底。能够合理的团伙测算各种询问的逐一以此来下跌复杂度。要驾驭大家算完[L,R]的答案后近期要算[L’,R’]的答案。由于可以在O(1)的日子下获得[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案.所以总括[L’,R’]的答案花的小运为|L-L’|+|Rubicon-LX570’|。假使把通晓[L,R]用作平面上的点a(L,奥迪Q3).询问[L’,R’]看做点b(L’,福睿斯’)的话。那么时间支出就为两点的曼哈顿距离。所以对于每一种询问看做二个点。大家要按一定顺序计算每种值。这开支就为曼哈顿相差的和。要总计到每一个点。那么路径至少是一棵树。所以难题就成为了求二维平面包车型客车微乎其微曼哈顿距离生成树。
关于二维平面最小曼哈顿距离生成树。感兴趣的能够参照胡泽聪大佬的那篇文章

如此只要本着树边总结3遍就ok了。可以印证时间复杂度为\(O(n∗\sqrt n)\).

不过那种措施编制程序复杂度稍微高了一些。所以有贰个比较优雅的替代品。那正是先对队列分块。然后对于有所询问遵照L所在块的大大小小排序。假若一致再依据Tucson排序。然后依据相排版序后的依次总计。为什么这么测算就足以下降复杂度呢。

壹 、i与i+1在同样块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时刻复杂度是n^1.5。
贰 、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一有些时刻复杂度是n^1.5
叁 、i与i+1在相同块内时变化不超越n^0.5,跨越一块也不会超过2*n^0.5,无妨看成是n^0.5。由于有n个数,所以时间复杂度是n^1.5
于是乎就改为了Θ(n1.5)

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
const int maxn = 50010;
#define ll long long
ll num[maxn], up[maxn], dw[maxn], ans, aa, bb, cc;
int col[maxn], pos[maxn];
struct qnode {
  int l, r, id;
} qu[maxn];
bool cmp(qnode a, qnode b) {
  if (pos[a.l] == pos[b.l])
    return a.r < b.r;
  else
    return pos[a.l] < pos[b.l];
}
ll gcd(ll x, ll y) {
  ll tp;
  while ((tp = x % y)) {
    x = y;
    y = tp;
  }
  return y;
}
void update(int x, int d) {
  ans -= num[col[x]] * num[col[x]];
  num[col[x]] += d;
  ans += num[col[x]] * num[col[x]];
}
int main() {
  int n, m, bk, pl, pr, id;
#ifndef ONLINE_JUDGE
  freopen("input", "r", stdin);
#endif
  scanf("%d %d", &n, &m);
  memset(num, 0, sizeof(num));
  bk = ceil(sqrt(1.0 * n));
  for (int i = 1; i <= n; i++) {
    scanf("%d", &col[i]);
    pos[i] = (i - 1) / bk;
  }
  for (int i = 0; i < m; i++) {
    scanf("%d %d", &qu[i].l, &qu[i].r);
    qu[i].id = i;
  }
  std::sort(qu, qu + m, cmp);
  pl = 1, pr = 0;
  ans = 0;
  for (int i = 0; i < m; i++) {
    id = qu[i].id;
    if (qu[i].l == qu[i].r) {
      up[id] = 0, dw[id] = 1;
      continue;
    }
    if (pr < qu[i].r) {
      for (int j = pr + 1; j <= qu[i].r; j++)
        update(j, 1);
    } else {
      for (int j = pr; j > qu[i].r; j--)
        update(j, -1);
    }
    pr = qu[i].r;
    if (pl < qu[i].l) {
      for (int j = pl; j < qu[i].l; j++)
        update(j, -1);
    } else {
      for (int j = pl - 1; j >= qu[i].l; j--)
        update(j, 1);
    }
    pl = qu[i].l;
    aa = ans - qu[i].r + qu[i].l - 1;
    bb = (ll)(qu[i].r - qu[i].l + 1) * (qu[i].r - qu[i].l);
    cc = gcd(aa, bb);
    aa /= cc, bb /= cc;
    up[id] = aa, dw[id] = bb;
  }
  for (int i = 0; i < m; i++)
    printf("%lld/%lld\n", up[i], dw[i]);
}

### 1.16 全体二分&CDQ分治

基本思路

另请参阅

Quick

GRAPHIC

  斐波那契博弈,注脚参见:

2.3.7

2.图论

    • 斐波那契博弈(Fibonacci
      Nim) –
      nyist_xiaod
    • 下棋—斐波那契博弈 –
      Lola&弗兰ce
    • Fibonacci
      Nim 取子游戏 –
      H老总Na
题目

在应用高效排序将 N 个不重复的因素排序时,
计量大小为 0、1 和 2 的子数组的数额。假若你欢乐数学,请推导;
万一您不爱好,请做一些尝试并提议疑心。

2.1图的连通性

  原题参见
hdoj 2516.
取石子游戏 等。

解答

本人讨厌数学= =

证明:
我们设 $ C_0(n) $ 代表将 $ n $ 个不另行成分排序时大小为 0
的数组的多少。
同理有 $ C_1(n) $ 和 $ C_2(n) $ 代表大小为 1 的数组的数目以及大小为 2
的数组的数目。
设 k 代表切分地方,鲜明切分地点随机且可能率相等,在 1~n 之间均匀分布。
遵照标准,$ C_0(n), C_1(n),C_2(n) $ 都知足下式:
\[ C(n)=
\frac{\sum_{k=1}^{n}(C(k-1)+C(n-k))}{n} \]
依照高速排序算法, $ \sum_{k=1}^{n}C(k-1)=\sum_{k=1}^{n}C(n-k) $
,因此
\[
C(n)=\frac{2\sum_{k=1}^{n}C(k-1)}{n}\\
nC(n)=2\sum_{k-1}^{n}C(k-1) \]
同理代入 $ n-1 $ 有
\[ (n-1)C(n-1)=2\sum_{k-1}^{n-1}C(k-1)
\]
相减
\[ nC(n)-(n-1)C(n-1)=2C(n-1)\\
C(n)=\frac{n+1}{n}C(n-1) \]
应用累乘法求到通项公式
\[ \frac{C(n)}{C(n-1)}=\frac{n+1}{n} \\
\frac{C(n)}{C(n-1)}\times\frac{C(n-1)}{C(n-2)}\times\dots\times\frac{C(m+1)}{C(m)}=
\frac{n+1}{n}\times\frac{n}{n-1}\times\dots\times\frac{m+2}{m+1}\\
\frac{C(n)}{C(m)}=\frac{n+1}{m+1}\\
C(n)=C(m)\frac{n+1}{m+1},n>m \]
对于 $ C_0(n) $ ,大家有始发标准 $ C_0(0)=1,
C_0(1)=0,C_0(2)=C_0(0)+C_0(1)=1 $
\[ C_0(n)=\frac{n+1}{3}, n>2
\]
对于 $ C_1(n) $ ,我们有始发标准 $
C_1(0)=0,C_1(1)=1,C_1(2)=C_1(0)+C_1(1)=1 $
\[ C_1(n)=\frac{n+1}{3},n>2
\]
对于 $ C_2(n) $ ,大家有始发标准 $
C_2(0)=C_2(1)=0,C_2(2)=1,C_2(3)=\frac{2\times(C_2(0)+C_2(1)+C_2(2))}{3}=\frac{2}{3}
$
\[ C_2(n)=\frac{n+1}{6},n>3
\]
结论
\[ C_0(n)=C_1(n)=\frac{n+1}{3},n>2
\\ C_2(n)=\frac{n+1}{6},n>3 \]
试行结果:
美高梅开户网址 16

2.1.1 双连通分量

  • 定理: 在无向过渡图G的DFS树中,
    非根节点u是G的割顶当且仅当u存在一個子节点v,
    使得v及其子孙都并未反向边连回u的祖先.
  • 设low(u)为u及其子孙所能连回的最早的祖先的pre值, 则定理中的条件就是:
  • 节点u存在一个子节点v, 使得low(v) >= pre(u).
  • 对此一个连通图, 假如任意两点存在至少两条”点不重复”的门径,
    就说这么些图是点-双连缀的, 这几个供给等价于任意两条边都在同一个简易环中,
    即内部无割顶. 类似的定义边-双连通. 对于一张无向图,
    点-双连通的极大子图称为双连通分量.
  • 今非昔比双连通分量最七唯有三个公共点, 且它必将是割顶.
    任意割顶都以三个不等双连通分量的集体点.

    stack S;
    int dfs(int u, int fa) {
    int lowu = pre[u] = ++dfs_clock;
    int child = 0;
    for(int i = 0; i < G[u].size(); i++) {

    int v = G[u][i];
    Edge e = (Edge){u, v};
    if(!pre[v]) {
      S.push(e);
      child++;
      int lowv = dfs(v, u);
      lowu = min(lowu, lowv);
      if(lowv >= pre[u]) {
        iscut[u] = true;
        bcc_cnt++;
        bcc[bcc_cnt].clear();
        for(;;) {
          Edge x = S.top(); S.pop();
          if(bccno[x.u] != bcc_cnt) {bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;}
          if(bccno[x.v] != bcc_cnt) {bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;}
          if(x.u == u && x.v == v) break;
        }
      }
    }
    else if(pre[v] < pre[u] && v != fa) {
      S.push(e);
      lowu = min(lowu, pre[v]);
    }
    

    }
    if(fa < 0 && child == 1) iscut[u] = 0; return lowu; }

  • 边-双连通分量能够运用更简约的不二法门求出, 分三个步骤,
    先做3回dfs标记出富有的桥, 然后再做贰回dfs找出边-双连通分量.
    因为边-双连通分量是没有集体节点的,
    所以只要在其次次dfs的时候保证不通过桥即可.

  首先知道了那是个斐波那契博弈,接下去大家要做的正是判定$K_i$是或不是为Fibonacci数。容易想到的是用递推打多个表,将Fibonacci数存起来或标志一下。可是我们领略斐波这契数列通项公式为$F_n=\frac1{\sqrt5}\left[\left(\frac{1+\sqrt5}2\right)^n-\left(\frac{1-\sqrt5}2\right)^n\right]$(比内公式),还清楚判断一个数$x$是还是不是为Fibonacci数只需判断$5x^2+4$或$5x^2-4$是否为完全平方数(参考:Wiki,示例)(即判断开根号后是还是不是为整数),于是Over。

代码

QuickSortAnalyze 类,添加了八个天性用于总计数组数量。

using System;
using System.Diagnostics;

namespace Quick
{
    /// <summary>
    /// 自动记录比较次数以及子数组数量的快速排序类。
    /// </summary>
    public class QuickSortAnalyze : BaseSort
    {
        /// <summary>
        /// 比较次数。
        /// </summary>
        public int CompareCount { get; set; }

        /// <summary>
        /// 是否启用打乱。
        /// </summary>
        public bool NeedShuffle { get; set; }

        /// <summary>
        /// 是否显示轨迹。
        /// </summary>
        public bool NeedPath { get; set; }

        /// <summary>
        /// 大小为 0 的子数组数量。
        /// </summary>
        public int Array0Num { get; set; }

        /// <summary>
        /// 大小为 1 的子数组数量。
        /// </summary>
        public int Array1Num { get; set; }

        /// <summary>
        /// 大小为 2 的子数组数量。
        /// </summary>
        public int Array2Num { get; set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortAnalyze()
        {
            this.CompareCount = 0;
            this.NeedShuffle = true;
            this.NeedPath = false;
            this.Array0Num = 0;
            this.Array1Num = 0;
            this.Array2Num = 0;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            this.Array0Num = 0;
            this.Array1Num = 0;
            this.Array2Num = 0;
            this.CompareCount = 0;
            if (this.NeedShuffle)
                Shuffle(a);
            if (this.NeedPath)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    Console.Write("  ");
                }
                Console.WriteLine("\tlo\tj\thi");
            }
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            if (hi - lo == 1)
                this.Array2Num++;
            else if (hi == lo)
                this.Array1Num++;
            else if (hi < lo)
                this.Array0Num++;

            if (hi <= lo)                   // 别越界
                return;
            int j = Partition(a, lo, hi);
            if (this.NeedPath)
            {
                for (int i = 0; i < a.Length; i++)
                {
                    Console.Write(a[i] + " ");
                }
                Console.WriteLine("\t" + lo + "\t" + j + "\t" + hi);
            }
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }

        /// <summary>
        /// 比较第一个元素是否小于第二个元素。
        /// </summary>
        /// <typeparam name="T">要比较的元素类型。</typeparam>
        /// <param name="a">第一个元素。</param>
        /// <param name="b">第二个元素。</param>
        /// <returns></returns>
        new protected bool Less<T>(T a, T b) where T : IComparable<T>
        {
            this.CompareCount++;
            return a.CompareTo(b) < 0;
        }
    }
}

主方法

using System;
using Quick;

namespace _2._3._7
{
    /*
     * 2.3.7
     * 
     * 在使用快速排序将 N 个不重复的元素排序时,
     * 计算大小为 0、1 和 2 的子数组的数量。
     * 如果你喜欢数学,请推导;
     * 如果你不喜欢,请做一些实验并提出猜想。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            // 证明
            // 我们设 C0(n) 代表将 n 个不重复元素排序时大小为 0 的数组的数量。
            // 同理有 C1(n) 和 C2(n) 代表大小为 1 的数组的数量和大小为 2 的数组的数量。
            // 设 k 代表切分位置,显然切分位置随机且概率相等,在 1~n 之间均匀分布。
            // 根据条件,三者都满足下式。
            // C(n) = 1/n sum(C(k - 1) + C(n - k)), k=1,2,...,n
            // 显然 sum(C(k - 1)) = sum(C(n - k)), k=1,2,...,n
            // 于是可以化简为
            // C(n) = 2/n sum(C(k - 1)), k=1,2,...,n
            // nC(n) = 2 * sum(C(k-1)), k=1,2,...,n
            // 同理有
            // (n-1)C(n-1) = 2 * sum(C(k-1)), k = 1,2,...,n-1
            // 相减得到递推式
            // nC(n) - (n-1)C(n-1) = 2*C(n-1)
            // C(n) = (n+1)/n * C(n-1)
            // 利用累乘法可以求得通项公式
            // C(n)=C(k)*(n+1)/(k+1), n>k
            // 对于 C0 有 C0(0)=1, C0(1)=0
            // C0(2)=C(0)+C(1)=1
            // C0(n)=(n+1)/3, n>2
            // 对于 C1 有 C1(0)=0, C1(1)=1
            // C1(2)=C1(0)+C1(1)=1
            // C1(n)=(n+1)/3, n>2
            // 对于 C2 有 C2(0)=C2(1)=0, C2(2)=1
            // C2(3)=1/3*2*(C2(0)+C2(1)+C2(2))=2/3
            // C2(n)=C2(3)*(n+1)/4=(n+1)/6, n>3
            // 结论
            // C0(n)=C1(n)=(n+1)/3, C2(n)=(n+1)/6
            int n = 1000;
            QuickSortAnalyze sort = new QuickSortAnalyze();
            Console.WriteLine("n\t0\t1\t2");
            for (int i = 0; i < 5; i++)
            {
                int[] a = new int[n];
                for (int j = 0; j < n; j++)
                {
                    a[j] = j;
                }
                SortCompare.Shuffle(a);
                sort.Sort(a);
                Console.WriteLine(n + "\t" + sort.Array0Num + "\t" + sort.Array1Num + "\t" + sort.Array2Num);
                n *= 2;
            }
        }
    }
}

2.1.2 强连通分量

kosaraju算法.

void dfs(int v) {
  vis[v] = true;
  for (int i = 0; i < G[v].size(); i++) {
    if (!vis[G[v][i]])
      dfs(G[v][i]);
  }
  vs.push_back(v);
}
void rdfs(int v, int k) {
  vis[v] = true;
  cnt[v] = k;
  for (int i = 0; i < rG[v].size(); i++) {
    if (!vis[rG[v][i]])
      rdfs(rG[v][i], k);
  }
  vs.push_back(v);
  sc[k].push_back(v);
}
int scc() {
  memset(vis, 0, sizeof(vis));
  vs.clear();
  for (int v = 1; v <= n; v++) {
    if (!vis[v])
      dfs(v);
  }
  memset(vis, 0, sizeof(vis));
  int k = 0;
  for (int i = vs.size() - 1; i >= 0; i--) {
    if (!vis[vs[i]]) {
      rdfs(vs[i], k++);
    }
  }
  return k;
}

参照代码

另请参阅

Quick

What is the expected number of subarrays of size 0, 1 and 2 when
quicksort is used to sort an array of N items with distinct keys?-Stack
Overflow

2.2 最短路与最小生成树

合法代码(丧sha病bi出题人改了一点波代码,大家依旧伪装上边包车型客车是对的吗):

2.3.8

2.2.1 SPFA

虽然是NOIp(professional)知识, 不过出于在省选中国和北美洲经常用,
照旧写一下.

最短路算法也会在分层图初级中学结束学业生升学考试察.

spfa也得以应用在DP中的转移.

void spfa() {
  memset(dist, 0x3f, sizeof(dist));
  dist[s][0] = 0;
  queue<state> q;
  q.push((state){s, 0});
  memset(inq, 0, sizeof(inq));
  inq[s][0] = 1;
  while (!q.empty()) {
    state u = q.front();
    q.pop();
    inq[u.pos][u.k] = 0;
    for (int i = 0; i < G[u.pos].size(); i++) {
      edge &e = G[u.pos][i];
      if (dist[e.to][u.k] > dist[u.pos][u.k] + e.value) {
        dist[e.to][u.k] = dist[u.pos][u.k] + e.value;
        if (!inq[e.to][u.k]) {
          q.push((state){e.to, u.k});
          inq[e.to][u.k] = 1;
        }
      }
      if (u.k < k && dist[e.to][u.k + 1] > dist[u.pos][u.k]) {
        dist[e.to][u.k + 1] = dist[u.pos][u.k];
        if (!inq[e.to][u.k + 1]) {
          q.push((state){e.to, u.k + 1});
          inq[e.to][u.k + 1] = 1;
        }
      }
    }
  }
}

spfa能够用来判负环. 所谓负环正是环上面权和为负的环.
一般采纳dfs版本spfa判负环.

double dist[maxn];
inline void spfa(int x) {
  int i;
  vis[x] = false;
  for (i = 0; i < rg[x].size(); i++) {
    edge &e = rg[x][i];
    if (dist[e.to] > dist[x] + e.value)
      if (!vis[e.to]) {
        flag = true;
        break;
      } else {
        dist[e.to] = dist[x] + e.value;
        spfa(e.to);
      }
  }
  vis[x] = true;
}
bool check(double lambda) {
  for (int i = 1; i <= n; i++) {
    rg[i].clear();
    for (int j = 0; j < G[i].size(); j++) {
      rg[i].push_back((edge){G[i][j].to, (double)G[i][j].value - lambda});
    }
  }
  memset(vis, 1, sizeof(vis));
  memset(dist, 0, sizeof(dist));
  flag = false;
  for (int i = 1; i <= n; i++) {
    spfa(i);
    if (flag)
      return true;
  }
  return false;
}

美高梅开户网址 17美高梅开户网址 18

题目

Quick.sort() 在处理 N 个全部重复的因素时大致供给多少次比较?

2.2.2 动态最小生成树

最小生成树算是很常见的考试场点.

至于最小生成树, 大家有以下结论:

  • 对此连通图中的任意贰个环 C
    :假使C中有边e的权值大于该环中随意五个其余的边的权值,那么那个边不会是最小生成树中的边.
  • 在一幅连通加权无向图中,给定任意的切分,它的横切边中权值最小的边必然属于图的最小生成树。
  • 就算图的保有最小权值的边只有一条,那么那条边包罗在自由贰个最小生成树中。
  • 次小生成树: 树上倍增+lca
  • 五个点时期的最大权最小路径一定在最小生成森林上(水管司长)
  • 欧几里德最小生成树
    动态最小生成树: 使用Link-Cut Tree维护.

  • 矩阵树定理(Matrix-Tree)

    下边我们介绍一种新的办法——Matrix-Tree定理(Kirchhoff矩阵-树定理)。Matrix-Tree定理是消除生成树计数难题最精锐的器械之一。它首先于1847年被Kirchhoff评释。在介绍定理此前,大家首先明确多少个概念:
    ① 、G的度数矩阵 class=”math inline”>\(D[G]\)是一个 class=”math inline”>\(n \times
    n\)的矩阵,并且满意:当\(i\not
    = j\)时, class=”math inline”>\(d_{ij}=0\);当 class=”math inline”>\(i=j\)时, class=”math inline”>\(d_{ij}\)等于 class=”math inline”>\(v_i\)的度数。
    贰 、G的邻接矩阵 class=”math inline”>\(A[G]\)也是3个 class=”math inline”>\(n \times n\)的矩阵,
    并且满意:若是\(v_i\)、 class=”math inline”>\(v_j\)之间有边直接相接,则 class=”math inline”>\(a_{ij}\)=1,否则为0。
    小编们定义 class=”math inline”>\(G\)的Kirchhoff矩阵(也叫做拉普Russ算子)C[G]为C[G]=D[G]-A[G],则Matrix-Tree定理能够描述为:G的持有差别的生成树的个数等于其Kirchhoff矩阵C[G]其余多个n-1阶主子式的行列式的相对值。所谓n-1阶主子式,正是对于r(1≤r≤n),将C[G]的第r行、第r列同时去掉后收获的新矩阵,用Cr[G]表示。

  • kruskal 算法: 贪心地采纳每一条边

 1 #include <cstdlib>
 2 #include <cstdio>
 3 #include <cmath>
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     int T;
 9     scanf("%d",&T);
10     while(T--){
11         int n,i;
12         int ans=0;
13         scanf("%d",&n);
14         for(i = 1;i<=n;i++){
15             int a;
16             scanf("%d",&a);
17             if(!ans&&(sqrt(5*a*a+4)-(int)sqrt(5*a*a+4)<1e-6||sqrt(5*a*a-4)-(int)sqrt(5*a*a-4)<1e-6)){
18                 ans=i;
19             }
20         }
21         if(ans)printf("%d\n",ans);
22         else puts("Oyk forever!");
23     }
24     return 0;
25 }
解答

历次切分都会把数组平分,共切分 logN 次(二分法),每一趟切分相比 N 次(i
和 j 会一人一位地从两边向中档靠拢)。
共比较 NlogN 次。

2.3网络流

C. OykOyk!

2.3.9

2.3.1 预备知识

  • 流网络\(G=(V,E)\)是一个有向图,
    个中每条边\(<u, v> \in
    E\)均为有一非负体量\(c(u, v)
    \geqslant 0\), 规定: 若\(<u,v> \not \in E\), 则\(c(u,v)=0\). 互连网中有八个特殊点\(s\)和\(t\).
  • 互联网的流是三个实值函数\(f\):\(V \times V \rightarrow R\),
    且满意多个属性: 容量限制, 反对称性, 流守恒性.
  • 最大的流是指该网络中流值最大的流.
  • 遗留互连网由得以容纳更加多流的边构成.
  • 增广路径\(p\)为残留互联网上\(s\)到\(t\)的一条简单路径.
  • 流网络\(G=(V, E)\)的割\([S, T]\)将点集划分为\(S\省选前模板总计,基于假设检验的Filter方法。)和\(T\)两部分, 使得\(s \in S\ and\ t \in T\).
    符号\([S, T]={<u,v>|<u,v>
    \in E, u \in S, v \in T}\), 通过割的净流为\(f(S,T)\), 容积定义为\(c(S,T)\).
  • 一个互连网的细微割正是网络中体积最小的割.

非官方代码(打表出神蹟):

题目

请表明 Quick.sort()
在拍卖唯有二种主键值时的一坐一起,以及在处理唯有三种主键值的数组时的一言一行。

2.3.2 最大流最小割定理与线性规划

率先大家只要读者已经有了线性规划的中央知识.

最大流难题的线性规划描述:
$$
\begin{alignat}{2}

\max\quad &f_{ts} &{}& \tag{LP1} \label{eqn – lp}\

\mbox{s.t.}\quad

&f_{u,v}\leqslant c_{u,v}, &\quad& (u,v)\in E\

&\sum_{v} f_{uv} = \sum_v f_{vu}, &{}& u \in V\
& f_{uv} \geqslant 0, &{}& (u,v) \in E \cup{(t,s)}

\end{alignat}
\[ 最小割难题的线性规划描述: \]
\begin{alignat}{2}

\min\quad &\sum_{(u,v) \in E}c_{uv}d_{uv} &{}& \tag{LP2} \

\mbox{s.t.}\quad

&d_{u,v}-p_u+p_v &\geqslant 0, &\quad& (u,v)\in E\
&p_s-p_t &\geqslant 1\
&p_u, d_{uv} \in {0, 1}

\end{alignat}
\($ 令\)p_u=[u \in S]$, \(d_{uv}=\max\{p_u-p_v, 0\}\).

考虑最大流的双双: 记由体量限制爆发的变量为\(d_{uv}\), 由点\(u\)的流量守恒爆发的变量为\(p_u\), 那么对偶难点便是:
$$
\begin{alignat}{2}

\min\quad &\sum_{(u,v) \in E}c_{uv}d_{uv} &{}& \tag{LP3} \

\mbox{s.t.}\quad

&d_{u,v}-p_u+p_v &\geqslant 0, &\quad& (u,v)\in E\
&p_s-p_t &\geqslant 1\
&d_{uv} &\geqslant 0, &{}&(u,v)\in E

\end{alignat}
\($ 我们得出结论:
(最大流最小割定理)给定2个源为\)s\(,
汇为\)t\(的网络, 则\)s,t\(的最大流等于\)s,t$的细微割.

美高梅开户网址 19美高梅开户网址 20

解答

切分时,枢轴左边都以低于(或等于)枢轴的,
左边都以高于(或等于)枢轴的
唯有三种主键值时,
首先次切分之后,某旁边的要素将整个等同
(假诺枢轴选了较大的,那么左侧将全部一如既往,反之则右边全体一如既往)

唯有二种主键值时,和一般飞速排序并无两样。
但假设第②次切分时精选了中间值作为枢轴,且中间值唯有三个
那便是说只须求1回切分数组便会静止。

2.3.3 最大流算法

 1 #include <stdio.h>
 2 using namespace std;
 3 
 4 int flag[100100];
 5 void init() {
 6     int a = 1, b = 2;
 7     while(b<=100000) {
 8         ++flag[b];
 9         b += a;
10         a = b-a;
11     }
12 }
13 int main() {
14     int T; init();
15     scanf("%d", &T);
16     while(T--) {
17         int C, k, res=0;
18         scanf("%d", &C);
19         for(int i=1; i<=C; i++) {
20             scanf("%d", &k);
21             if(!res&&flag[k])
22                 res = i;
23         }
24         res?printf("%d\n", res):puts("Oyk forever!");
25     }
26     return 0;
27 }

2.3.10

2.3.3.1 Dinic算法
int dist[maxn], iter[maxn];
inline void bfs(int s) {
  memset(dist, -1, sizeof(dist));
  dist[s] = 0;
  queue<int> q;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    for (int i = 0; i < G[u].size(); i++) {
      edge &e = edges[G[u][i]];
      if (e.cap > 0 && dist[e.to] == -1) {
        dist[e.to] = dist[u] + 1;
        q.push(e.to);
      }
    }
  }
}
inline int dfs(int s, int t, int flow) {
  if (s == t)
    return flow;
  for (int &i = iter[s]; i < G[s].size(); i++) {
    edge &e = edges[G[s][i]];
    if (e.cap > 0 && dist[e.to] > dist[s]) {
      int d = dfs(e.to, t, min(e.cap, flow));
      if (d > 0) {
        e.cap -= d;
        edges[G[s][i] ^ 1].cap += d;
        return d;
      }
    }
  }
  return 0;
}
inline int dinic(int s, int t) {
  int flow = 0;
  while (1) {
    bfs(s);
    if (dist[t] == -1)
      return flow;
    memset(iter, 0, sizeof(iter));
    int d;
    while (d = dfs(s, t, inf))
      flow += d;
  }
  return flow;
}

C. Oyk forever!

题目

Chebyshev 不等式申明,三个随机变量的正规差异离均值大于 k 的概率小于
1/k^2 。
对此 N=100 万,用 Chebyshev 不等式计算快捷排序所选拔的比较次数抢先 1000亿次的可能率(0.1N^2)。

2.3.3.2 费用流

泛指一种与支出相关的流算法.EK算法比较常用

bool spfa(ll &flow, ll &cost) {
  for (int i = 0; i <= n + 1; i++) {
    dist[i] = -inf;
  }
  memset(inq, 0, sizeof(inq));
  dist[s] = 0, inq[s] = 1, pre[s] = 0, fi[s] = inf;
  queue<int> q;
  q.push(s);
  while (!q.empty()) {
    int u = q.front();
    q.pop();
    inq[u] = 0;
    for (int i = 0; i < G[u].size(); i++) {
      edge &e = E[G[u][i]];
      if (e.cap > e.flow && dist[e.to] < dist[u] + e.cost) {
        dist[e.to] = dist[u] + e.cost;
        pre[e.to] = G[u][i];
        fi[e.to] = min(fi[u], e.cap - e.flow);
        if (!inq[e.to]) {
          q.push(e.to);
          inq[e.to] = 1;
        }
      }
    }
  }
  if (dist[t] <= -inf)
    return false;
  if (cost + dist[t] * fi[t] < 0) { 
    ll temp = cost / (-dist[t]); //temp:还能够增加的流
    flow += temp;
    return false;
  }
  flow += fi[t];
  cost += dist[t] * fi[t];
  int u = t;
  while (u != s) {
    E[pre[u]].flow += fi[t];
    E[pre[u] ^ 1].flow -= fi[t];
    u = E[pre[u]].from;
  }
  return true;
}
ll mcmf(int s, int t) {
  ll flow = 0;
  ll cost = 0;
  while (spfa(flow, cost))
    ;
  return flow;
}

 

解答

切比雪夫不等式(Chebyshev’s inequality)
\[ P(|X-\mu|\geq k\sigma)\leq
\frac{1}{k^2} \]
其中,$ \mu $ 代表希望,$ \sigma $ 代表标准差。
对于连忙排序的可比次数来说,$ \mu = 2N\ln N $ ,$ \sigma=0.65N $。
(那八个结论见 2.3 节的命题 K 和命题 L)
难题中要求比较次数超过 $ 0.1N^2 $ ,能够求得 $ k $ 的值。
\[ 0.65kN=0.1N^2 \\ k=\frac{2N}{13}
\]
将 $ N=1,000,000 $ 代入
\[ P(|X-27,631,021|\geq
100,000,000,000)\leq 0.00000000004225 \]

2.3.4 建模方法

D.
最小成本流

另请参阅

切比雪夫不等式到底是个如何概念? – 马同学的答应 –
乐乎

2.3.4.1 基本建立模型
  • 七个起源和汇点(一流起点, 一级汇点)
  • 无向图: 拆成两条边
  • 终点体量限制: 拆点
  • 不相交的两条路径: 拆点
  • 上下界互连网流:美高梅开户网址 21
  • 上下界费用流:美高梅开户网址 22
  • 图部分产生变化: 重复利用从前的结果
  1. 体积扩展, 直接跑最大流
  2. 体积裁减, 假如\(f(e) \leq
    c(e)-1\), 那么不用动, 不然退流

    for (int i = 1; i <= n; i++) {
      int now = cc[i].id;
      int u = now, v = now + n;
      bfs(u);
      if (dist[v] != -1)
        continue;
      rec[tot++] = now;
      dinic(t, v);
      dinic(u, s);
      edges[(now - 1) * 2].cap = edges[(now - 1) * 2 + 1].cap = 0;
    }
    
  • 容积为负数: 适当变形
  • 支出为负数的景观:
  1. 消负圈
  2. 经过适当的变形. 比如说, 纵然每趟增广所用的边数都以如出一辙的(记做m),
    那么把富有边的开支都添加常数\(k\), 然后从最短路减去\(mk\)就获得了原图最短路的长短
  3. 美高梅开户网址 23

标题马虎

2.3.11

2.3.4.2 最大流建立模型
  • 与棋盘有关的难题能够考虑最大流

  挖$n$天的宝藏,天天需求$君越_i$只铲子,当天用完会坏掉。有$3$种方法准备铲子:从事商业店买,$p$元1只;找铁匠$A$修理,每只$f$元同时修$m$天;找铁匠$B$修理,每只$s$元同时修$t$天。问最小开销。

题目

比方在蒙受和切分成分重复的要素时大家延续扫描数组而不是停下来,
评释使用那种形式的飞快排序在拍卖唯有若干种成分值的数组时运维时刻是平方级别的。

2.3.4.3 最小割建立模型
  • 用体量为\(\infty\)的边表示争论
  • 从两点关系的角度举行小小的割建立模型

基本思路

解答

唯有多少种成分值意味着大量的连年重复。
(由于存在打乱这一步骤,不设有接二连三重复的大概性是十分低的)
接下去大家考虑这么的连年重复在修改后的快排下的质量。
1 1 1 1 1 1 1
对此这么的数组,枢轴选为 1,j 将会在 j = lo 处终止。
由此最终的结果将是历次只有数组的首先个因素被排序
已知每一回切分都是 O(k – 1) 的(i 和 j 都将走完全部子数组)
就此那样的连忙排序所需时日 = $ 2 (N – 1 + N – 2 + \cdots + 1) = (N –
1)N $
故此对于值相同的子数组,那样的快排运行时刻是平方级其余
那么当数组中那样的接连重复内容更加多,运转时刻就越接近平方级别。

2.3.4.4 费用流建模

  最小费用流,参见互连网流24题之餐巾难点。

2.3.12

2.3.4.5 流量平衡考虑

参照代码

题目

规行矩步级成员代表码所示轨迹的格式给出新闻量最棒的长足排序第③次是如何切分
数组 B A B A B A B A C A D A B R A 的。

2.4二分图

  • 二分图, 指顶点能够分成五个不相交的多少个\(U\)和\(V\)(\(U\
    and\ V\)皆为独立集), 使得在同一个集内的终点不相邻的图.
  • 无向图G为二分图\(\Leftrightarrow\)G至少有两极分化,
    且全部回路的长短均为偶数\(\Leftrightarrow\)没有奇圈
  • 最大边独立集的基数等于最大独立集的基数
  • 最大独立集的基数和最大匹配基数之和, 等于顶点数目.
  • 对此连通的二分图: 最小顶点覆盖集的基数等于最大匹配的基数
  • 哈尔l定理是二个用于判定二分图是还是不是富有最大匹配的定律。
    第③对于二分图G=(X∪Y,E),点集被分为了XX和YY两部分。
    是不是具备最大匹配,首先3个最主旨的口径就是|X|=|Y||X|=|Y|。
    哈尔l定理则在此基础上交给了二个更强的准绳。
    第3对于二个点集T⊆X,定义Γ(T)Γ(T)如下:
    Γ(T)={v∣u→v∈E,u∈T,v∈Y}
    Γ(T)={v∣u→v∈E,u∈T,v∈Y}
    即意味着TT中全部点能够直接到达的YY中的点的聚众。
    美高梅开户网址 24
    上图中,Γ({1,3})={4,5,6}Γ({1,3})={4,5,6}。
    那正是说哈尔l条件则用来判断3个二分图是或不是存在最大匹配。哈尔l条件如下:
    对此自由的点集T⊆X,均设有:
    |T|≤|Γ(T)|
    |T|≤|Γ(T)|
    那正是说此二分图必定期存款在最大匹配。

美高梅开户网址 25美高梅开户网址 26

解答

美高梅开户网址 27

2.5 其余常用结论

  • 对于不存在孤立点的图,|最大匹配|+|最小边覆盖| = |V|

  • 最大独立成团 + 最小顶点覆盖 = V

  • 对于二分图:|最大匹配| = |最小顶点覆盖|

  • 平面圖的頂點個數、邊數和面包车型大巴個數之間有一個以歐拉命名的公式:美高梅开户网址 28

里头,V是頂點的数额,E是邊的數目,F是面包车型地铁數目,C是组成圖形的連通部分的數目。當圖是單連通圖的時候,公式簡化為:
美高梅开户网址 29

  • 任何叁个平面图的对偶图照旧是平面图
  1 #include <iostream>
  2 #include<stdio.h>
  3 #include<string.h>
  4 #define INF 99999999
  5 #define min(x,y) (x)>(y)?(y):(x)
  6 #define abs(x) ((x)>0?(x):-(x))
  7 #define E 50000
  8 struct p
  9 {
 10     int v,next,k,t,cost;
 11 }edge[200000];
 12 int n,m,ans,tot,S,T,head[1001],pre[1001],pid[1001],pop[100001];
 13 int mark[1001],dis[1001],now[1001];
 14 void addedge(int a,int b,int k,int cost)
 15 {
 16     edge[tot].v=b;
 17     edge[tot].k=k;
 18     edge[tot].cost=cost;
 19     edge[tot].t=tot+1;
 20     edge[tot].next=head[a];
 21     head[a]=tot++;
 22 
 23     edge[tot].v=a;
 24     edge[tot].k=0;
 25     edge[tot].cost=-cost;
 26     edge[tot].t=tot-1;
 27     edge[tot].next=head[b];
 28     head[b]=tot++;
 29 }
 30 int spfa()
 31 {
 32     int i,top,tail,cur;
 33     for(i=0;i<=T;i++)
 34         dis[i]=INF,mark[i]=0;
 35     top=tail=0;
 36     pop[top++]=S;
 37     dis[S]=0;
 38     mark[S]=1;
 39     while(tail!=top)
 40     {
 41         cur=pop[tail++];
 42         tail%=50000;
 43         mark[cur]=0;
 44         for(i=head[cur];i!=-1;i=edge[i].next)
 45             if(edge[i].k>0&&dis[edge[i].v]>dis[cur]+edge[i].cost)
 46             {
 47                 dis[edge[i].v]=dis[cur]+edge[i].cost;
 48                 pre[edge[i].v]=cur;
 49                 pid[edge[i].v]=i;
 50                 if(mark[edge[i].v]==0)
 51                 {
 52                     mark[edge[i].v]=1;
 53                     pop[top++]=edge[i].v;
 54                     top%=50000;
 55                 }
 56             }
 57     }
 58     return dis[T];
 59 }
 60 int mincost()
 61 {
 62     int i,flow,tmp,ans,maxflow=0;
 63     ans=0;
 64     while(1)
 65     {
 66         tmp=spfa();
 67         if(tmp==INF) break;
 68         flow=INF;
 69         for(i=T;i!=S;i=pre[i])
 70             if(edge[pid[i]].k<flow)
 71                 flow=edge[pid[i]].k;
 72         for(i=T;i!=S;i=pre[i])
 73         {
 74             edge[pid[i]].k-=flow;
 75             edge[edge[pid[i]].t].k+=flow;
 76         }
 77         maxflow+=flow;
 78         ans+=tmp*flow;
 79     }
 80     return ans;
 81 }
 82 int main()
 83 {
 84     freopen("in.txt","r",stdin);
 85     freopen("out.txt","w",stdout);
 86     int i,j,p,m1,f,m2,s,tmp;
 87     while(scanf("%d%d%d%d%d%d",&n,&p,&m1,&f,&m2,&s)!=EOF)
 88     {
 89         memset(edge,0xff,sizeof(edge));
 90         tot=n*2+3;
 91         S=0;
 92         T=n*2+1;
 93         for(i=1;i<=n;i++)
 94         {
 95             scanf("%d",&tmp);
 96             tmp++;
 97             addedge(S,i+n,INF,p);
 98             addedge(S,i,tmp,0);
 99             addedge(i+n,T,tmp,0);
100             if(i<n) addedge(i,i+1,INF,0);
101             if(i+m1<=n) addedge(i,n+i+m1,INF,f);
102             if(i+m2<=n) addedge(i,n+i+m2,INF,s);
103         }
104         printf("%d\n",mincost());
105     }
106     return 0;
107 }

2.3.13

MATHEMATICS

D. Dig the treasure

题目

在拔尖、平均和最坏情形下,快捷排序的递归深度分别是稍微?
那决定了系统为了追踪递归调用所需的栈的轻重。
在最坏景况下保障递归深度为数组大小的对数级的办法请见练习 2.3.20。

3.数学知识

 

解答

立时排序先将数组分为
(小于枢轴)枢轴(大于枢轴)三片段,然后再各自递归的排序左右两部分数组。
在此间,我们得以将一点也不慢排序的递归树看作是一棵二叉搜索树(BST, Binary
Search Tree)。
枢轴作为根结点,左子树即为左数组构造的 BST,右子树即为右数组构造的
BST。
如此那般问题中所求的递归深度即为所协会的 BST 的冲天。

最坏景况,每便都唯有枢轴和不止枢轴两有些,BST 退化为链表,中度为 $ n-1
$。

最棒状态,每一趟枢轴都碰巧平分数组,构造一棵完全二叉树,中度为 $ \log n
$。

平均意况,难点转化为:贰个由 $ n $ 个因素随机构造的 BST
的平分高度是稍稍?
《算法导论》给出的下结论是 $ \log n $ ,具体表达如下:
设由 $ n $ 个结点随机组合的 BST 的可观为 $ h_n $,那么有:
\[ h_n=1+\max(h_{l}+h_{r})
\]
其中,$ h_l $ 和 $ h_r $ 分别代表左数组和右数组构造的 BST 的惊人。
设枢轴地方为 $ i $,上式可简化为:
\[ h_n=1+\max(h_{i-1}, h_{n-i})
\]
由于枢轴地方能够在 1~n 之间自由取值且概率相等,因而 BST
的平分高度(即中度的想望)为:
\[
E(h_n)=\frac{1}{n}\sum_{i=1}^{n}\lbrack 1+\max(h_{i-1}, h_{n-i})
\rbrack \]
我们令 $ Y_n=2^{h_n} $,可得:
\[ Y_n=2\times\max(Y_{i-1},Y_{n-i})
\]
我们把 $ Y_n $ 代入,可得:
\[ \begin{align*} E(Y_n)
&=\sum_{i=1}^{n}\frac{1}{n}E\lbrack2\times\max(Y_{i-1},
Y_{n-i})\rbrack\\
&=\frac{2}{n}\sum_{i=1}^{n}E\lbrack\max(Y_{i-1},Y_{n-i})\rbrack\\
\end{align*} \]
接下去我们去掉最大值运算,遵照最大值的品质,下式明显创造:
\[ E\lbrack\max(X,Y)\rbrack\le
E\lbrack\max(X,Y)+\min(X,Y)\rbrack=E\lbrack X+Y\rbrack=E\lbrack
X\rbrack+E\lbrack Y\rbrack \]
代入可得:
\[ E(Y_n)
\le\frac{2}{n}\sum_{i=1}^{n}(E\lbrack Y_{i-1}\rbrack + E\lbrack
Y_{n-i} \rbrack) =\frac{2}{n}\sum_{i=0}^{n-1}2E\lbrack
Y_i\rbrack =\frac{4}{n}\sum_{i=0}^{n-1}E\lbrack Y_i\rbrack
\]
高低为 0 的数组构成的 BST 的冲天显著为 0,大家设 $ Y_0=0 $
。接下来用1个组合数公式来布局上界:
\[ \begin{align*} 0&=Y_0=E\lbrack Y_0
\rbrack\le
\frac{1}{4}\begin{pmatrix}3\\3\end{pmatrix}=\frac{1}{4}\\
1&=Y_1=E\lbrack Y_1 \rbrack\le\frac
{1}{4}\begin{pmatrix}3+1\\3\end{pmatrix}=1 \\ \vdots \\ Y_i
&=E\lbrack
Y_i\rbrack\le\frac{1}{4}\begin{pmatrix}i+3\\3\end{pmatrix}
\end{align*} \]
只顾那里的结合数公式为:
\[
\begin{pmatrix}n\\r\end{pmatrix}=\frac{r!}{r!(n-r)!} \]
代入可得:
\[ \begin{align*} E(Y_n) &\le
\frac{4}{n}\sum_{i=0}^{n-1}E\lbrack Y_i\rbrack \\
&\le\frac{4}{n}\sum_{i=0}^{n-1}\frac{1}{4}\begin{pmatrix}i+3\\3\end{pmatrix}
\\
&=\frac{1}{n}\sum_{i=0}^{n-1}\begin{pmatrix}i+3\\3\end{pmatrix}
\end{align*} \]
接下去大家去掉求和符号,首先依照组合数的品质,有以下等式创制
\[ \begin{align*}
\begin{pmatrix}n\\k\end{pmatrix}&=\begin{pmatrix}n-1\\k-1\end{pmatrix}+\begin{pmatrix}n-1\\k\end{pmatrix}
\\ \begin{pmatrix}n\\n\end{pmatrix}&=1 \end{align*}
\]
大家把求和式展开得到:
\[ \begin{align*}
\sum_{i=0}^{n-1}\begin{pmatrix}i+3\\3\end{pmatrix}
&=\begin{pmatrix}3\\3\end{pmatrix} +
\begin{pmatrix}4\\3\end{pmatrix}+\cdots+\begin{pmatrix}n+2\\3\end{pmatrix}
\\ &=\begin{pmatrix}4\\4\end{pmatrix} +
\begin{pmatrix}4\\3\end{pmatrix}+\cdots+\begin{pmatrix}n+2\\3\end{pmatrix}
\\ &=\begin{pmatrix}n+3\\4\end{pmatrix} \end{align*}
\]
代入可得:
\[ \begin{align*} E(Y_n)
&\le\frac{1}{n}\sum_{i=0}^{n-1}\begin{pmatrix}i+3\\3\end{pmatrix}\\
&=\frac{1}{n}\begin{pmatrix}n+3\\4 \end{pmatrix} \\
&=\frac{1}{n}\cdot\frac{(n+3)!}{4!(n-1)!} \\
&=\frac{1}{4}\cdot\frac{(n+3)!}{3!n!} \\
&=\frac{(n+1)(n+2)(n+3)}{24} \\ &=\frac{n^3+6n^2+11n+6}{24}
\end{align*} \]
由于 \(Y_n=2^{h_n}\) ,因此 \(E\lbrack Y_n \rbrack=E\lbrack 2^{h_n}
\rbrack\)。
由于 \(f(x)=2^x\)
是个凸函数,可以动用Jensen不等式(凸函数的割线一定在函数上方),即 \(2^{E\lbrack h_n\rbrack}\le E\lbrack
Y_n\rbrack\)。
于是乎获得结论:
\[ 2^{E\lbrack h_n\rbrack} \le
\frac{n^3+6n^2+11n+6}{24} \\ E\lbrack h_n \rbrack\le
\log(\frac{n^3+6n^2+11n+6}{24}) \]

3.1数论

E.
Wwj’s work

另请参阅

敏捷排序的递归树能够视为 BST 的定论能够在上边那一个 PPT 的第 5 页找到。
QuickSort-London高校
《算法导论》中关于自由 BST 中度的辨证(P321 Theorem12.4)
Introduction to
Algorithms
也足以参考下边这些链接获得更详细的解释。
Proof that a randomly built binary search tree has logarithmic
height-StackExchange

3.1.1扩张欧几里德算法

第壹我们有欧几Reade算法:

\[gcd(a, b) = gcd(a\ mod\ b,
b)\]

扩张欧几Reade算法化解了这么的标题:

\[ ax + by = gcd(a,b)\]

咱俩先考察一种特殊的事态:

当\(b=0\)时,大家直接可以有解:
\[ \begin{eqnarray} \left\{
\begin{array}{lll} x = 1 \\ y = 0 \end{array} \right.
\end{eqnarray} \]
一般地,我们令\(c = a\ mod \
b\),递归地解上面包车型客车方程:

\[bx^{‘}+cy^{‘}=gcd(b,c)\]

传说欧几Reade算法,有

\[bx^{‘}+cy^{‘}=gcd(a,b)\]

根据\(mod\)的概念大家得以有

\[c = a –
b\lfloor\frac{a}{b}\rfloor\]

指引原式

\[bx^{‘}+(a –
b\lfloor\frac{a}{b}\rfloor)y^{‘}=gcd(a,b)\]

为了体现与\(a,b\)的关系

\[ay^{‘}+b(x^{‘}-\lfloor\frac{a}{b}\rfloor
y^{‘})=gcd(a,b)\]

于是那样就完事了回看。

那个算法的沉思呈现在了上边包车型大巴顺序里。

void gcd(int a, int b, int &d, int &x, int &y) {
  if(!b) {d = a; x = 1; y = 0; }
  else { gcd(b, a%b, d, y, x); y -= x * (a/b); }
}

问题疏忽:这题是HDOJ
4622. Reincarnation原题,有且仅有多少是友善造的。。

2.3.14

3.1.2线性筛与积性函数

基本思路:求二个字符串的子串数目,标准的后缀自动机。当然就如也足以往缀数组、后缀xxx什么的乱搞。

题目

表明在用急忙排序处理大小为 N 的不重复数组时,
正如第 i 大和第 j 大成分的可能率为 2/(j – i + 1),并用该结论证明命题 K。

3.1.2.1线性筛素数

首先给出线性筛素数的顺序。

void get_su(int n) {
  tot = 0;
  for(int i = 2; i <= n; i++) {
    if(!check[i]) prime[tot++] = i;
    for(int j = 0; j < tot; j++) {
      if(i * prime[j] > n) break;
      check[i * prime[j]] = true;
      if(i % prime[j] == 0) break;
    }
  }
}

可以表达的是,每一个合数都仅仅会被她的最小质因数筛去,那段代码的年华复杂度是\(\Theta (n)\)的,也便是所谓线性筛。

证明:设合数 class=”math inline”>\(n\)最小的质因数为 class=”math inline”>\(p\),它的另一个大于 class=”math inline”>\(p\)的质因数为 class=”math inline”>\(p^{‘}\),另 class=”math inline”>\(n = pm=p^{‘}m^{‘}\)。
观望地点的主次片段,可以发现 class=”math inline”>\(j\)循环到质因数 class=”math inline”>\(p\)时合数n第1回被标记(若循环到 class=”math inline”>\(p\)以前已经跳出循环,表明 class=”math inline”>\(n\)有更小的质因数),若也被 class=”math inline”>\(p^{‘}\)标记,则是在那以前(因为 class=”math inline”>\(m^{‘}<m\)),考虑 class=”math inline”>\(i\)循环到 class=”math inline”>\(m^{‘}\),注意到 class=”math inline”>\(n=pm=p^{‘}m^{‘}\)且 class=”math inline”>\(p,p^{‘}\)为不一致的质数,由此 class=”math inline”>\(p|m^{‘}\),所以当j循环到质数p后完工,不会循环到 class=”math inline”>\(p^{‘}\),那就印证 class=”math inline”>\(n\)不会被 class=”math inline”>\(p^{‘}\)筛去。

参考代码:(参考kuangbin的模板,和代码)

解答

普通话版标题有误,详见官方修正页面:

假设 $ i < j $ 。
第叁,在火速排序中,要是八个因素要发生交流,意味着其中三个要素被选为枢轴。
并且数组中的成分各不同,那么八个特定的因素的可比最多发生贰遍。

那便是说先考虑2个独特别情报形,$ i = 1, j = n $
,即求最大值和最小值相比的可能率。
那会儿,一旦枢轴不是那多少个因素之一,
最大值和纤维值会被分到五个差别的子数组,不大概产生相比较。
所以在那种特例下第 $ i $ 大的要素和第 $ j $ 大的成分发生相比较的可能率为 $
\frac{2}{n} = \frac{2}{j-i+1} $ 。

接下去考虑一般景况,假设枢轴采用了第 $ i $ 到第 $ j $ 大之外的元素,
这便是说第 $ i $ 大和第 $ j $
大的元素会被分到同贰个子数组里,重复上述进度。
故而我们所求的概率只和从第 $ i $ 大到第 $ j $ 大时期的成分有关,概率为
\(\frac{2}{j-i+1}\)。
(举个例子,3个箱子里有 2 个红球、三个蓝球和 七个白球,未来摸球而不放回。
万一摸到白球能够再摸2回,直到摸到红球或蓝球停止。
分明在这么的平整下摸到红球或蓝球的票房价值为 1,即白球对可能率没有影响。)

当今我们已经得到了某四个因素相比的可能率 \(E(X_{ij})\),接下去我们求每多个因素比较的可能率$ E(X) $。
\[ \begin{align*} E(X) &=
\sum_{i=1}^{n}\sum_{j=i+1}^{n}E(X_{ij})\\
&=\sum_{i=1}^{n}2(\frac{1}{2}+\frac{1}{3}+\cdots+\frac{1}{n-i+1})
\\ &=2n(\frac{1}{2}+\frac{1}{3}+\cdots+\frac{1}{n-i+1})
\end{align*} \]
据书上说调和级数的性质($ \ln (n) < 1+ \frac{1}{2}+ \cdots +
\frac{1}{n} < 1+\ln(n) $),能够获得结论:
\[ E(X) \le 2n \ln(n) \]

3.1.2.2积性函数
  • 考虑一个定义域为\(\mathbb{N}^{+}\)的函数\(f\)(数论函数),对于自由四个互质的正整数\(a,b\),均满足

\[f(ab) =
f(a)f(b)\],则函数f被称为积性函数。

  • 如果对于随意七个正整数\(a,b\),都有\(f(ab)=f(a)f(b)\),那么就被称作完全积性函数。

简单见到,对于任意积性函数,\(f(1)=1\)。

  • 设想八个不止1的正整数\(N\),设\(N
    = \prod p_{i}^{a_i}\),那么

\[f(N)=f(\prod p_i^{a_i})=\prod
f(p_i^{a_i})\],如果\(f\)还满意完全积性,那么

\[f(N)=\prod f(p_i)^{a_i}\]

  • 如果\(f\)是八个无限制的函数,它使得和式\(g(m) =
    \sum_{d|m}f(d)\)为积性函数,那么\(f\)也决然是积性函数。
  • 积性函数的Dirichlet前缀和也是积性函数。那几个定律是地点定理的反命题。
  • 多少个积性函数的Dirichlet卷积也是积性函数。

美高梅开户网址 30美高梅开户网址 31

另请参阅

上面那个链接里的 3.4.2 节给出精通法。
lect0906 –
Carnegie梅隆高校
假诺照旧不可能知道为啥屡次切分不影响可能率,能够参照三门题材的解释:
蒙提霍尔难点 –
维基百科
蒙提霍尔难题(又称三门题材、山羊小车难点)的正解是怎样?-
和讯

3.1.2.3欧拉函数\(\varphi\)
  • \(\varphi(n)\)表示\(1..n\)中与\(n\)互质的平头个数。
  • 我们有欧拉定理:

\[n^{\varphi(m)}\equiv 1 \pmod m\ \ \
\ n\perp m\]

能够选择那个定律总括逆元。

  • 如果\(m\)是3个素数幂,则简单总括\(\varphi(m)\),因为有$n \perp p^{k}
    \Leftrightarrow p \nmid n $ 。在\(\{0,1,…,p^k-1\}\)中的\(p\)的倍数是\(\{0, p, 2p, …,
    p^k-p\}\),从而有\(p^{k-1}\)个,剩下的计入\(\varphi(p^k)\)

\[\varphi(p^k) =
p^k-p^{k-1}=(p-1)p^{k-1}\]

  • 由地方的推理大家简单得出欧拉函数一般的意味:

\[\varphi(m) =
\prod_{p|m}(p^{m_p}-p^{m_p-1}) = m
\prod_{p|m}(1-\frac{1}{p})=\prod(p-1)p^{m_p-1}\]

  • 利用Mobius反演,简单得出\(\sum_{d|n}\varphi(d) = n\)。
  • 当\(n>1\)时,\(1..n\)中与\(n\)互质的平头和为\(\frac{n\varphi(n)}{2}\)
  • 降幂大法\[A^B\ mod\ C=A^{B\ mod\
    \varphi(C)+\varphi(C)}\ mod\ C\]
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int CHAR = 26;
 7 const int MAXN = 2020;
 8 struct SAM_Node {
 9     SAM_Node *fa, *next[CHAR];
10     int len;
11     int id,pos;
12     SAM_Node() {}
13     SAM_Node(int _len) {
14         fa = 0;
15         len = _len;
16         memset(next,0,sizeof(next));
17     }
18 };
19 SAM_Node SAM_node[MAXN*2], *SAM_root, *SAM_last;
20 int SAM_size;
21 SAM_Node *newSAM_Node(int len) {
22     SAM_node[SAM_size] = SAM_Node(len);
23     SAM_node[SAM_size].id = SAM_size;
24     return &SAM_node[SAM_size++];
25 }
26 SAM_Node *newSAM_Node(SAM_Node *p) {
27     SAM_node[SAM_size] = *p;
28     SAM_node[SAM_size].id = SAM_size;
29     return &SAM_node[SAM_size++];
30 }
31 void SAM_init() {
32     SAM_size = 0;
33     SAM_root = SAM_last = newSAM_Node(0);
34     SAM_node[0].pos = 0;
35 }
36 void SAM_add(int x,int len) {
37     SAM_Node *p = SAM_last, *np = newSAM_Node(p->len+1);
38     np->pos = len;
39     SAM_last = np;
40     for(; p && !p->next[x]; p = p->fa)
41         p->next[x] = np;
42     if(!p) {
43         np->fa = SAM_root;
44         return;
45     }
46     SAM_Node *q = p->next[x];
47     if(q->len == p->len + 1) {
48         np->fa = q;
49         return;
50     }
51     SAM_Node *nq = newSAM_Node(q);
52     nq->len = p->len + 1;
53     q->fa = nq;
54     np->fa = nq;
55     for(; p && p->next[x] == q; p = p->fa)
56         p->next[x] = nq;
57 }
58 
59 int sub[MAXN][MAXN];
60 char S[MAXN];
61 void read() {
62     memset(sub, 0, sizeof(sub));
63     scanf("%s", S);
64     int len = strlen(S);
65     for(int i=0; i<len; i++) {
66         SAM_init();
67         for(int j=i; j<len; j++)
68             SAM_add(S[j]-'a', j-i+1);
69         for(int j=1; j<SAM_size; j++)
70             sub[i][ SAM_node[j].pos+i-1 ]
71                 += SAM_node[j].len - SAM_node[j].fa->len;
72         for(int j=i+1; j<len; j++)
73             sub[i][j] += sub[i][j-1];
74     }
75 }
76 void work() {
77     int Q, l, r;
78     scanf("%d", &Q);
79     while(Q--) {
80         scanf("%d%d", &l, &r);
81         printf("%d\n", sub[l-1][r-1]);
82     }
83 }
84 int main() {
85     int T;
86     scanf("%d", &T);
87     while(T--) {
88         read();
89         work();
90     }
91     return 0;
92 }

2.3.15

3.1.2.4线性筛法求解积性函数
  • 积性函数的重庆大学是怎样求\(f(p^k)\)。
  • 观望线性筛法中的步骤,筛掉n的还要还拿走了他的细微的质因数\(p\),我们期望可以领略\(p\)在\(n\)中的次数,这样就能够运用\(f(n)=f(p^k)f(\frac{n}{p^k})\)求出\(f(n)\)。
  • 令\(n=pm\),由于\(p\)是\(n\)的最小质因数,若\(p^2|n\),则\(p|m\),并且\(p\)也是\(m\)的最小质因数。那样在筛法的同时,记录每种合数最小质因数的次数,就能算出新筛去合数最小质因数的次数。
  • 只是那样还不够,大家还要能够飞快求解\(f(p^k)\),那时一般就要结合\(f\)函数的个性来设想。
  • 比如说欧拉函数\(\varphi\),\(\varphi(p^k)=(p-1)p^{k-1}\),由此开始展览筛法求\(\varphi(p*m)\)时,如果\(p|m\),那么\(p*m\)中\(p\)的次数不为1,所以我们得以从\(m\)中解释出\(p\),那么\(\varphi(p*m) = \varphi(m) *
    p\),否则\(\varphi(p * m)
    =\varphi(m) * (p-1)\)。

  • 再例如默比乌斯函数\(\mu\),只有当\(k=1\)时\(\mu(p^k)=-1\),否则\(\mu(p^k)=0\),和欧拉函数一样依据\(m\)是否被\(p\)整除判断。

    void Linear_Shaker(int N) {
    phi[1] = mu[1] = 1;
    for (int i = 2; i <= N; i++) {

    if (!phi[i]) {
      phi[i] = i - 1;
      mu[i] = -1;
      prime[cnt++] = i;
    }
    for (int j = 0; j < cnt; j++) {
      if (prime[j] * i > N)
        break;
      if (i % prime[j] == 0) {
        phi[i * prime[j]] = phi[i] * prime[j];
        mu[i * prime[j]] = 0;
        break;
      } else {
        phi[i * prime[j]] = phi[i] * (prime[j] - 1);
        mu[i * prime[j]] = -mu[i];
      }
    }
    

    }
    for (int i = 2; i <= N; i++) {

    phi[i] += phi[i - 1];
    mu[i] += mu[i - 1];
    

    }
    }

E. Wwj’s work

题目

螺丝钉和螺帽。(G.J.E.Rawlins)
假如有 N 个螺丝和 N 个螺帽混在一堆,你须要急迅将它们配对。
三个螺丝钉只会合营四个螺帽,三个螺帽也只会合作2个螺钉。
你能够试着把叁个螺丝和3个螺帽拧在协同看看哪个人大了,但无法直接比较多个螺丝可能七个螺帽。
付给3个缓解这么些题材的有用方法。

3.1.2.5线性筛逆元

令\(f(i)\)为\(i\)在\(mod\
p\)意义下的逆元。鲜明那几个函数是积性函数,大家能够利用线性筛求。可是实际上并未那么劳碌。

我们设\(p = ki+r\),那么\(ki+r \equiv 0 (mod\
p)\),两边同时乘\(i^{-1}r^{-1}\),有\(kr^{-1}+i^{-1}\equiv 0\),那么\(i^{-1} \equiv -kr^{-1}=-\lfloor \frac {p}{i}
\rfloor * (p \ mod\ i)^{-1}\),那样就足以递推了。

void getinv(int n) {
  inv[1] = 1;
  for(int i = 2; i <= x; i++)
    inv[i] = (long long)(p - p/i)*inv[p % i] % p;
}

有了逆元,大家就能够预处理阶乘的逆元

\[n!^{-1} \equiv \prod_{k=1}^n k^{-1}\
mod \ p\]

 

解答

实际上只要求修改飞快排序的切分方法,分一遍举办切分。
率先选第一个螺母作为枢轴,找到呼应的螺丝钉($ O(n)
$)放到第三个人,对螺丝数组进行切分。
然后再用找到的螺丝钉对螺母数组进行切分。

螺母类,完毕了对螺丝类的 IComparable 接口

/// <summary>
/// 螺母类。
/// </summary>
public class Nut<T> : IComparable<Bolt<T>> where T : IComparable<T>
{
    /// <summary>
    /// 螺母的值。
    /// </summary>
    public T Value { get; set; }

    /// <summary>
    /// 螺母的构造函数。
    /// </summary>
    /// <param name="value">螺母的值。</param>
    public Nut(T value) => this.Value = value;

    /// <summary>
    /// 比较方法,螺母只能和螺丝比较。
    /// </summary>
    /// <param name="other">需要比较的螺丝。</param>
    /// <returns></returns>
    public int CompareTo(Bolt<T> other)
    {
        return this.Value.CompareTo(other.Value);
    }
}

就如的有螺丝类。

/// <summary>
/// 螺丝类。
/// </summary>
public class Bolt<T> : IComparable<Nut<T>> where T : IComparable<T>
{
    /// <summary>
    /// 螺丝的值。
    /// </summary>
    public T Value { get; set; }

    /// <summary>
    /// 螺丝的默认构造函数。
    /// </summary>
    /// <param name="value">螺丝的值。</param>
    public Bolt(T value) => this.Value = value;

    /// <summary>
    /// 比较方法,螺丝只能和螺母比较。
    /// </summary>
    /// <param name="other">需要比较的螺母。</param>
    /// <returns></returns>
    public int CompareTo(Nut<T> other)
    {
        return this.Value.CompareTo(other.Value);
    }
}

3.1.3默比乌斯反演与狄利克雷卷积

F.
防AK题,dfs+高斯消元

代码

修改后的排序方法。

using System;

namespace _2._3._15
{
    /// <summary>
    /// 用快排的方式解决螺母和螺帽的问题。
    /// </summary>
    public class BoltsAndNuts
    {
        private readonly Random random = new Random();

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public BoltsAndNuts() { }

        /// <summary>
        /// 对螺丝和螺母排序。
        /// </summary>
        /// <typeparam name="T">需要排序的元素类型。</typeparam>
        /// <param name="bolts">螺母数组。</param>
        /// <param name="nuts">螺丝数组。</param>
        public void Sort<T>(Bolt<T>[] bolts, Nut<T>[] nuts) where T : IComparable<T>
        {
            if (bolts.Length != nuts.Length)
                throw new ArgumentException("数组长度必须一致");

            Shuffle(bolts);
            Shuffle(nuts);
            Sort(bolts, nuts, 0, bolts.Length - 1);
        }

        /// <summary>
        /// 对螺丝和螺母排序。
        /// </summary>
        /// <typeparam name="T">需要排序的元素类型。</typeparam>
        /// <param name="bolts">螺母数组。</param>
        /// <param name="nuts">螺丝数组。</param>
        /// <param name="lo">起始下标。</param>
        /// <param name="hi">终止下标。</param>
        public void Sort<T>(Bolt<T>[] bolts, Nut<T>[] nuts, int lo, int hi) where T : IComparable<T>
        {
            if (hi <= lo)
                return;
            int j = Partition(bolts, nuts, lo, hi);
            Sort(bolts, nuts, lo, j - 1);
            Sort(bolts, nuts, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="bolts">螺母数组。</param>
        /// <param name="nuts">螺丝数组。</param>
        /// <param name="lo">起始下标。</param>
        /// <param name="hi">终止下标。</param>
        /// <returns>切分位置。</returns>
        private int Partition<T>(Bolt<T>[] bolts, Nut<T>[] nuts, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            Bolt<T> pivotB = bolts[lo];
            // 找到对应螺丝
            for (int k = lo; k <= hi; k++)
            {
                if (nuts[k].CompareTo(pivotB) == 0)
                {
                    Exch(nuts, k, lo);
                    break;
                }
            }
            // 先用螺母去套螺丝
            while (true)
            {
                while (nuts[++i].CompareTo(pivotB) < 0)
                    if (i == hi)
                        break;
                while (pivotB.CompareTo(nuts[--j]) < 0)
                    if (j == lo)
                        break;

                if (i >= j)
                    break;
                Exch(nuts, i, j);
            }
            Exch(nuts, lo, j);

            // 再用螺丝去比较螺母
            Nut<T> pivotN = nuts[j];
            i = lo;
            j = hi + 1;
            while (true)
            {
                while (bolts[++i].CompareTo(pivotN) < 0)
                    if (i == hi)
                        break;
                while (pivotN.CompareTo(bolts[--j]) < 0)
                    if (j == lo)
                        break;

                if (i >= j)
                    break;

                Exch(bolts, i, j);
            }
            Exch(bolts, lo, j);

            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + this.random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }

        /// <summary>
        /// 交换两个元素。
        /// </summary>
        /// <typeparam name="T">元素类型。</typeparam>
        /// <param name="a">需要交换的第一个元素。</param>
        /// <param name="b">需要交换的第二个元素。</param>
        private void Exch<T>(T[] a, int lo, int hi)
        {
            T t = a[lo];
            a[lo] = a[hi];
            a[hi] = t;
        }
    }
}
3.1.3.1初等积性函数\(\mu\)

\(\mu\)正是容斥周密。
\[ \mu(n) = \begin{cases} 0, & \text{if
$\exists x^2|n$} \\ (-1)^k&n=\prod_\limits{i=1}^{k}p_i \\
\end{cases} \]
\(\mu\)函数也是二个积性函数。

下边包车型地铁公式能够从容斥的角度精通。
\[ \sum_{d|n}\mu(d)=[n=1] \]

难题疏忽:我没看,看不懂。

另请参阅

上面这几个网站提交了那道题的解法,还交到了另一种公开场地算法(非随机的算法)的诗歌链接。
Matching Nuts and Bolts –
Solution

3.1.3.2默比乌斯反演

第③付诸Mobius反演的公式:

\[ F(n)=\sum_{d|n}f(d) \Rightarrow
f(n)=\sum_{d|n}\mu(\frac{n}{d})F(d) \]
有二种常见的验证,一种是行使Dirichlet卷积,一种是选用初等方式。

证明:
\[ \sum_{d|n}\mu(d)F(\frac{n}{d}) =
\sum_{d|n}\mu(\frac{n}{d})F(d)=\sum_{d|n}\mu(\frac{n}{d})\sum_{k|d}f(k)\\=\sum_{d|n}\sum_{k|d}\mu(\frac{n}{d})f(k)=\sum_{k|n}\sum_{d|\frac{n}{k}}\mu(\frac{n}{kd})f(k)\\
=\sum_{k|n}\sum_{d|\frac{n}{k}}\mu(d)f(k)=\sum_{k|n}[\frac{n}{k}
= 1]f(k)=f(n) \]
默比乌斯反演的另一种样式:
\[ F(n)=\sum_{n|d}f(d)\Rightarrow
f(n)=\sum_{n|d}\mu(\frac{d}{n})F(d) \]
这么些姿势的注解与上式六安小异,笔者在那里写一下关键步骤
\[
\sum_{n|d}\sum_{d|k}\mu(\frac{d}{n})f(k)=\sum_{n|k}\sum_{d|\frac{k}{n}}\mu(d)f(k)=f(n)
\]
对此一些函数\(f(n)\),假若大家很难间接求出他的值,而简单求出倍数和恐怕因数和\(F(n)\),那么我们能够透过默比乌斯反演来求得\(f(n)\)的值

基本思路:我不会。

2.3.16

3.1.3.3狄利克莱卷积

概念四个数论函数\(f(x)\),\(g(x)\)的\(Dirichlet\)卷积
\[ (f*g)(n)=\sum_{d|n}f(d)g(\frac nd)
\]
Dirichlet卷积满足交流律,结合律,分配律,单位元。

选用狄利Klay卷积简单注脚默比乌斯反演。

参考代码:找Czj去。

题目

极品状态。
编排一段程序来生成使算法 2.5 中的 sort()
方法突显最棒的数组(无重复成分):
数组大小为 N 且不含有重复成分,每趟切分后三个子数组的大大小小最多差 1
(子数组的深浅与含蓄 N 个一样成分的数组的切分景况一样)。
(对于这道演练,大家不必要在排序开端时打乱数组。)

3.1.4积性函数求和与杜教筛

 

解答

合法完毕见:

看似于高效排序的组织,只要中式点心的两边都是一级状态,那么全体数组正是一级状态了。
具体方法是:
先是构造叁个不变数组,
接下来找到中式点心(作为枢轴),
对中间左右两侧子数组进行布局,
将选用的枢轴放到开端处(a[lo])。

3.1.4.1概述

假如能透过狄利克雷卷积构造一个更好计算前缀和的函数,且用于卷积的另三个函数也易计算,则能够简化计算进度。

设\(f(n)\)为3个数论函数,须要总计\(S(n)=\sum_{i=1}^n f(i)\)。

遵照函数\(f(n)\)的性质,构造1个\(S(n)\)关于\(S(\lfloor \frac ni
\rfloor)\)的递推式,如下例:

找到一個合适的数论函数\(g(x)\)
\[ \sum_{i=1}^n\sum_{d|i}f(d)g(\frac
id)=\sum_{i=1}^ng(i)S(\lfloor\frac ni\rfloor) \]
能够收获递推式
\[
g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S(\lfloor
\frac{n}{i}\rfloor) \]
在递推总结\(S(n)\)的进度中,要求被计算的\(S(\lfloor \frac ni \rfloor)\)只有\(O(\sqrt n)\)种。

G. Not
Easy Math Problem

代码

用以组织最好数组的类。

namespace Quick
{
    /// <summary>
    /// 构建快速排序最佳情况的类。
    /// </summary>
    public class QuickBest
    {
        /// <summary>
        /// 构造函数,这个类不应该被实例化。
        /// </summary>
        private QuickBest() { }

        /// <summary>
        /// 构造适用于快速排序的最佳数组。
        /// </summary>
        /// <param name="n">数组长度。</param>
        /// <returns></returns>
        public static int[] Best(int n)
        {
            int[] a = new int[n];
            for (int i = 0; i < n; i++)
            {
                a[i] = i;
            }
            Best(a, 0, n - 1);
            return a;
        }

        /// <summary>
        /// 递归的构造数组。
        /// </summary>
        /// <param name="a">需要构造的数组。</param>
        /// <param name="lo">构造的起始下标。</param>
        /// <param name="hi">构造的终止下标。</param>
        private static void Best(int[] a, int lo, int hi)
        {
            if (hi <= lo)
                return;
            int mid = lo + (hi - lo) / 2;
            Best(a, lo, mid - 1);
            Best(a, mid + 1, hi);
            Exch(a, lo, mid);
        }

        /// <summary>
        /// 交换数组中的两个元素。
        /// </summary>
        /// <typeparam name="T">数组的元素类型。</typeparam>
        /// <param name="a">包含要交换元素的数组。</param>
        /// <param name="x">需要交换的第一个元素下标。</param>
        /// <param name="y">需要交换的第二个元素下标。</param>
        private static void Exch(int[] a, int x, int y)
        {
            int t = a[x];
            a[x] = a[y];
            a[y] = t;
        }
    }
}

用来测试的法门

using System;
using Quick;

namespace _2._3._16
{
    /*
     * 2.3.16
     * 
     * 最佳情况。
     * 编写一段程序来生成使算法 2.5 中的 sort() 方法表现最佳的数组(无重复元素):
     * 数组大小为 N 且不包含重复元素,
     * 每次切分后两个子数组的大小最多差 1
     * (子数组的大小与含有 N 个相同元素的数组的切分情况相同)。
     * (对于这道练习,我们不需要在排序开始时打乱数组。)
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSortAnalyze quick = new QuickSortAnalyze
            {
                NeedShuffle = false,            // 关闭打乱
                NeedPath = true                 // 显示排序轨迹
            };
            int[] a = QuickBest.Best(10);
            for (int i = 0; i < 10; i++)
            {
                Console.Write(a[i] + " ");
            }
            Console.WriteLine();
            quick.Sort(a);
            for (int i = 0; i < 10; i++)
            {
                Console.Write(a[i] + " ");
            }
            Console.WriteLine();
        }
    }
}
3.1.4.1欧拉函数求前缀和

利用\(\varphi *
I=id\)的性质,可以有:
\[
S(n)=\sum_{i=1}^ni-\sum_{i=2}^nS(\lfloor \frac ni\rfloor)
\]

难题疏忽:$F(m,n)=\left\{\begin{matrix}\begin{aligned}&B*2^{m-1},&n=0\\&\sum_{i=1}^m
F(i,
n-1),&n>0\end{aligned}\end{matrix}\right.$,其中$m<10^6$,$n<10^3$,$B<10$,求$F(m,n)\%(1E8+7)$。

另请参阅

Quick

3.1.4.2默比乌斯函数前缀和

利用\(\mu * I =
e\)的性质,可以有:
\[ S(n)=1-\sum_{i=2}^nS(\lfloor\frac
ni\rfloor) \]

基本思路

2.3.17

3.1.4.3模板
ll get_p(int x) { return (x <= m) ? phi[x] : p[n / x]; };
ll get_q(int x) { return (x <= m) ? mu[x] : q[n / x]; };
void solve(ll x) {
  if (x <= m)
    return;
  int i, last = 1, t = n / x;
  if (vis[t])
    return;
  vis[t] = 1;
  p[t] = ((x + 1) * x) >> 1;
  q[t] = 1;
  while (last < x) {
    i = last + 1;
    last = x / (x / i);
    solve(x / i);
    p[t] -= get_p(x / i) * (last - i + 1);
    q[t] -= get_q(x / i) * (last - i + 1);
  }
}
//注:本代码为了避免数组过大,p[]和q[]记录的是分母的值。

  递推法

题目

哨兵。
修改算法 2.5,去掉内循环 while 中的边界检查。
是因为切分成分本人正是二个哨兵(v 不容许低于
a[lo]),右边边界检查是多余的。
要去掉另三个反省,能够在打乱数组后将数组的最大要素方法 a[length – 1]
中。
该因素永远不会移动(除非和卓绝的成分交流),能够在有着包涵它的子数组中成为哨兵。
留神:在拍卖内部子数组时,右子数组中最左边的成分得以看做左子数组左边界的哨兵。

3.1.5中华剩余定理

中华剩余定理给出了以下的一元线性同余方程组有解的判定条件:
\[ \left\{ \begin{array}{c} x \equiv
a_1 \pmod {m_1}\\ x \equiv a_2 \pmod {m_2}\\ \vdots \\ x
\equiv a_n \pmod {m_n} \end{array} \right. \]
中华剩余定理建议,倘若模数两两互质,那么方程组有解,并且通解能够协会获得:

  1. 设\(M = \prod_{i=1}^n
    m_i\),并设\(M_i=\frac{M}{m_i}\)。
  2. 设\(t_i=M_i^{-1} \pmod
    {m_i}\)。
  3. 那么通解\(x = \sum_{i=1}^n
    a_it_iM_i\)。

    先考察前方若干行若干列,发现各样全面构成杨辉三角。代多少个数进去发现 $\displaystyle
F(m,n)=B*\sum_{i=0}^{m-1}C_{m-i+n-2}^{n-1}*2^i$。

解答

鲁人持竿题意修改代码即可,在调用 Suffle()
之后添加一段用于寻找最大值的法门($ O(n) $)。

/// <summary>
/// 用快速排序对数组 a 进行升序排序。
/// </summary>
/// <typeparam name="T">需要排序的类型。</typeparam>
/// <param name="a">需要排序的数组。</param>
public override void Sort<T>(T[] a)
{
    Shuffle(a);

    // 把最大元素放到最后一位
    int maxIndex = 0;
    for (int i = 0; i < a.Length; i++)
    {
        if (Less(a[maxIndex], a[i]))
            maxIndex = i;
    }
    Exch(a, maxIndex, a.Length - 1);

    Sort(a, 0, a.Length - 1);
    Debug.Assert(IsSorted(a));
}

3.1.6高斯消元

Gauss消元法正是选取初等行列式变换把原矩阵转化为上三角矩阵然后回套求解。给定2个矩阵现在,大家着眼每2个变量,找到它的周到最大的一行,然后依据这一行去排除别的的行。

double a[N][N]
void Gauss(){
    for(int i=1;i<=n;i++){
        int r=i;
        for(int j=i+1;j<=n;j++)
            if(abs(a[j][i])>abs(a[r][i])) r=j;
        if(r!=i) for(int j=1;j<=n+1;j++) swap(a[i][j],a[r][j]);

        for(int j=i+1;j<=n;j++){
            double t=a[j][i]/a[i][i];
            for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*t;
        }
    }
    for(int i=n;i>=1;i--){
        for(int j=n;j>i;j--) a[i][n+1]-=a[j][n+1]*a[i][j];
        a[i][n+1]/=a[i][i];
    }
}

对于xor运算,大家得以行使相同的不二法门消元。

其它,xor的话能够使用bitset压位以加快求解。

    $O(M)$肯定会TLE啊,算算算 $\displaystyle
\frac{2F(m,n)-F(m,n)}{B}=C_{n-1}^{n-1}*2^m-C_{m+n-2}^{n-1}*2^0+\sum_{i=1}^{m-1}\left(C_{m-i+n-1}^{n-1}-C_{m-i+n-2}^{n-1}\right)*2^i$

代码

修改后的便捷排序类。

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._17
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortX : BaseSort
    {
        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortX() { }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);

            // 把最大元素放到最后一位
            int maxIndex = 0;
            for (int i = 0; i < a.Length; i++)
            {
                if (Less(a[maxIndex], a[i]))
                    maxIndex = i;
            }
            Exch(a, maxIndex, a.Length - 1);

            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;
            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v)) ;
             //     if (i == hi)
             //         break;
                while (Less(v, a[--j])) ;
             //     if (j == lo)
             //         break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

主方法。

using System;
using Quick;

namespace _2._3._17
{
    /*
     * 2.3.17
     * 
     * 哨兵。
     * 修改算法 2.5,去掉内循环 while 中的边界检查。
     * 由于切分元素本身就是一个哨兵(v 不可能小于 a[lo]),
     * 左侧边界检查是多余的。
     * 要去掉另一个检查,可以在打乱数组后将数组的最大元素方法 a[length - 1] 中。
     * 该元素永远不会移动(除非和相等的元素交换),
     * 可以在所有包含它的子数组中成为哨兵。
     * 注意:在处理内部子数组时,
     * 右子数组中最左侧的元素可以作为左子数组右边界的哨兵。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSort quick = new QuickSort();
            QuickSortX quickSortX = new QuickSortX();
            int arrayLength = 1000000;
            int[] a = SortCompare.GetRandomArrayInt(arrayLength);
            int[] b = new int[arrayLength];
            a.CopyTo(b, 0);

            double time1 = SortCompare.Time(quick, a);
            double time2 = SortCompare.Time(quickSortX, b);
            Console.WriteLine("QSort\tQSort with Sentinels\t");
            Console.WriteLine(time1 + "\t" + time2 + "\t");
        }
    }
}

3.1.7大步小步BSGS算法

大步小步算法用于缓解:

已知\(A,B,C\),求\(x\)使得, 其中\(C\ is\ a\ prime\).
\[ A^x\equiv B\pmod C \]
我们令\(x=i\times m-\frac{j}{m}=\lceil
\sqrt C \rceil, i \in [1,m], j \in [0,m]\)

那就是说原式就变成了\[A^{im}=A^j\times
B\] .我们先枚举\(j\),把\(A^j
\times B\)参与哈希表,然后枚举\(i\),在表中检索\(A^{im}\),假使找到了,就找到了贰个解。时间复杂度为\(\Theta (\sqrt n)\)。

ll BSGS(ll A, ll B, ll C) {
    mp.clear();
    if(A % C == 0) return -2;
    ll m = ceil(sqrt(C));
    ll ans;
    for(int i = 0; i <= m; i++) {
        if(i == 0) {
            ans = B % C;
            mp[ans] = i;
            continue;
        }
        ans = (ans * A) % C;
        mp[ans] = i;
    }
    ll t = pow(A, m, C); 
    ans = t;
    for(int i = 1; i <= m; i++) {
        if(i != 1)ans = ans * t % C;
        if(mp.count(ans)) {
            int ret = i * m % C - mp[ans] % C;
            return (ret % C + C)%C;
        }
    }
    return -2;
} 

    发现可以行使组合数性情,令$\displaystyle
T(n-1)=\sum_{i=0}^{m-1}C_{m-i+n-2}^{n-1}*2^i$,$\displaystyle
T(n-2)=\sum_{i=0}^{m-1}C_{m-i+n-2}^{n-2}*2^i$

另请参阅

Quick

3.2重组数学

    则$\displaystyle
T(n-1)=C_{n-1}^{n-1}*2^m-C_{m+n-2}^{n-1}+T(n-2)-C_{m+n-2}^{n-2}$

2.3.18

3.2.1组合计数与二项式定理

    递推下去得$\displaystyle
T(n-1)=2^{n-1}*2^m-2*\sum_{i=0}^{n-2}C_{m+n-2}^i-C_{m+n-2}^{n-1}$

题目

三取样切分。
为火速排序达成正文所述的三取样切分(参见 2.3.3.2
节)。运营双倍测试来确认那项改动的功用。

3.2.1.1二项式定理

\[ (a+b)^n=\sum_{i=0}^n C_n^i
a^{i}b^{n-i} \]

其中\(C_n^m\)为二项式周全,满意多少个结论:
\[ C_n^i=C_n^{n-i} \]

\[ C_{n+1}^m=C_n^m+C_n^{m-1} \]

\[ \sum_{i=0}^nC_n^i=2^n \]

\[ C_n^k=\frac{k}{n}C_{n-1}^{k-1}
\]

    来个高速幂,再预处理下逆元和组合数,$O(log(M+N)+N)$跑得飞速($log$里面包车型大巴$N$能够在日前预处理组合数的轮回去掉,丑就是了)。

解答

每一遍切分时都取前八个要素的中位数作为枢轴,那能够带动约 5%~百分之十的天性进步。
此处透过三遍相比将前八个数排序,然后把八个数中的中位数放到数组起首,最大值放到数组末尾。
最大值被停放了最终,枢轴不容许不止末尾的那个数,由此左侧界判断能够去掉。
并且鉴于枢轴不恐怕低于自己,因而左侧界判断也足以去掉。
这般就能够把切分中的四个边界判断整个去掉了。
终极对于大小为 2 的数组做尤其处理,通过三遍比较平素排序并赶回。

测试结果:
美高梅开户网址 32

3.2.1.2排列组合
  • 隔板法与插空法

  • n成分集合的循环r排列的数量是\(\frac{P_n^r}r\)

  • 多重集合全排列\[\frac{n!}{\prod_i
    n_i!}\]

  • 多重汇集的组成,无限重复数,设S是有k类别型对象的类别集合,r组合的个数为\[C_{r+k-1}^r=C_{r+k-1}^{k-1}\]。

  • \(Lucas\)定理(p为素数) :
    \[ C_n^m \equiv C_{n / p}^{m/p}
    \times C_{n \ mod\ p}^{m\ mod\ p} \pmod p \]

    int C(int n, int m, int P) {

    if (n < m) return 0;
    return (ll)fac[n] * inv(fac[n-m], P) % P * inv(fac[m], P)%P;
    

    }
    int lucas(int n, int m, int P) {

    if(!n && !m) return 1;
    return (ll)lucas(n/P, m/P, P) * C(n%P, m%P, P) % P;
    

    }

  数学总结法

代码

QuickSortMedian3

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._18
{
    /// <summary>
    /// 三取样快速排序
    /// </summary>
    public class QuickSortMedian3 : BaseSort
    {
        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortMedian3() {}

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;

            // 只有两个元素的数组直接排序
            if (hi == lo + 1)
            {
                if (Less(a[hi], a[lo]))
                    Exch(a, lo, hi);

                return;
            }

            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;

            if (Less(a[lo + 1], a[lo]))
                Exch(a, lo + 1, lo);
            if (Less(a[lo + 2], a[lo]))
                Exch(a, lo + 2, lo);
            if (Less(a[lo + 2], a[lo + 1]))
                Exch(a, lo + 1, lo + 2);

            Exch(a, lo, lo + 1);        // 中位数放最左侧
            Exch(a, hi, lo + 2);        // 较大的值放最右侧作为哨兵

            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v)) ;
                while (Less(v, a[--j])) ;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

测试用例

using System;
using Quick;

namespace _2._3._18
{
    /*
     * 2.3.18
     * 
     * 三取样切分。
     * 为快速排序实现正文所述的三取样切分(参见 2.3.3.2 节)。
     * 运行双倍测试来确认这项改动的效果。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSort quickNormal = new QuickSort();
            QuickSortMedian3 quickMedian = new QuickSortMedian3();
            int arraySize = 200000;                         // 初始数组大小。
            const int trialTimes = 4;                       // 每次实验的重复次数。
            const int trialLevel = 5;                       // 双倍递增的次数。

            Console.WriteLine("n\tmedian\tnormal\tratio");
            for (int i = 0; i < trialLevel; i++)
            {
                double timeMedian = 0;
                double timeNormal = 0;
                for (int j = 0; j < trialTimes; j++)
                {
                    int[] a = SortCompare.GetRandomArrayInt(arraySize);
                    int[] b = new int[a.Length];
                    a.CopyTo(b, 0);
                    timeNormal += SortCompare.Time(quickNormal, b);
                    timeMedian += SortCompare.Time(quickMedian, a);

                }
                timeMedian /= trialTimes;
                timeNormal /= trialTimes;
                Console.WriteLine(arraySize + "\t" + timeMedian + "\t" + timeNormal + "\t" + timeMedian / timeNormal);
                arraySize *= 2;
            }
        }
    }
}

3.2.2科学普及数列

  • 错位排列 \[D_n = (n-1) * (D_{n-1} +
    D_{n-2})\]
  • Catanlan数
    \[C(n) = \sum (C(n-I) *
    C(I))\]
    总计公式:
    \[C(n) =
    \frac{C(2n,n)}{n+1}\]
    应用:
    满足递推关系的均可代表成catalan数,比如:
    出栈顺序,二叉树种类个数,门票难题,格子难点(不通过对角线),括号配对难题等等。

    参见
hdoj. 5490 题解。总结公式为$\displaystyle
F(m,n)=\frac{q*F(m,n-1)-B*C_{m+n-2}^{n-1}}{q-1}$,然后递推,时间复杂度$O(log(M)+N)$(若是用高速幂算$2^{m-1}$的话)。

另请参阅

Quick

3.2.3置换群与\(Polya\)定理

  • \(Burnside\)引理(\(Z_k\):\(k\)不动置换类,\(c_1(a)\):一阶循环的个数:
    \[l=\frac1{|G|}\sum_{k=1}^n|Z_k|=\frac1{|G|}\sum_{j=1}^gc_1(a_j)\]
  • 安装换群G功用于有限集合χ上,用k种颜色涂染集合χ中的成分,则χ在G功用下等价类的数码为(\(m(f)\)为置换\(f\)的循环节):\[N=\frac 1{|G|}\sum_{f \in
    G}k^{m(f)}\]

参考代码(作者的终将比标算雅观):

2.3.19

3.2.4容斥原理

\[
|\cup_{i=1}^nA_i|=\sum_{i=1}^nA_i-\sum_{i,j:i\not=j}|A_i \cap
A_j|+\sum_{i,j,k:i\not=j\not=k}|A_i \cap A_j \cap A_k|-\cdots
\pm |A_1 \cap \cdots \cap A_n| \]

void calc(){
    for(int i = 1; i < (1 << ct); i++) {
        //do sth
        for(int j = 0; j < ct; j++) {
            if(i & (1 << j)) {
                cnt++;
                //do sth 
            }
        }
        if(cnt & 1) ans += tmp;
        else ans -= tmp;
    }
}

美高梅开户网址 33美高梅开户网址 34

题目

五取样切分。
兑现一种基于随机抽取子数组中 5 个成分并取中位数实行切分的长足排序。
将取样成分放在数组的旁边以保障只有中位数成分参加了切分。
运维双倍测试来明确那项改动的效力,并和正规的快捷排序以及三取样的迅猛排序(请见上一道演练)实行相比较。
附加题:找到一种对于自由输入都只须求简单 7 次比较的五取样算法。

3.3常见结论与技术

 1 #include <stdio.h>
 2 const int MOD = 100000007;
 3 inline int add(int a, int b) { return (a%MOD+b%MOD)%MOD; }
 4 inline int sub(int a, int b) { return ((a-b)%MOD+MOD)%MOD; }
 5 inline int mul(int a, int b) { return int((long long)a%MOD*(b%MOD)%MOD); }
 6 inline int pow(int x, int n) {
 7     int res = 1;
 8     while(n) {
 9         if(n&1) res = mul(res, x);
10         x = mul(x, x);
11         n >>= 1;
12     }
13     return res;
14 }
15 int inv[1001]={1,1};
16 void init() {
17     for(int i=2; i<=1000; i++)
18         inv[i] = mul(inv[MOD%i], MOD-MOD/i);
19 }
20 int B, M, N;
21 void read() {
22     scanf("%d%d%d", &B, &M, &N);
23 }
24 int Binomial[1001]={1};
25 void work() {
26     for(int i=1; i<N; i++)
27         Binomial[i] = mul(mul(Binomial[i-1], M+N-i-1), inv[i]);
28     int res = pow(2, M+N-1);
29     for(int i=0; i<N; i++)
30         res = sub(res, mul(Binomial[i], 2));
31     res = mul(add(res, Binomial[N-1]), B);
32     printf("%d\n", res);
33 }
34 int main() {
35     int T; init();
36     scanf("%d", &T);
37     while(T--) {
38         read();
39         work();
40     }
41     return 0;
42 }
解答

重点介绍一下那些点儿七遍相比的五取样算法。
率先若是多少个数字为 a b c d e
对 b c 排序,d e 排序。(三遍比较)
正如 b 和 d,把较小那一组换来 b c 的职位上去。(1回相比)
那会儿会有 b < c, b < d < e。
换到 a, b,重新对 b c 排序。(二遍相比较)
再度相比较 b 和 d,把较小的那一组换成 b c 的职位上。(三次相比)
最终比较 c 和 d,较小的那些即为中位数。(三遍比较)
累计需求 6 次相比较,严苛小于 7 次。

抽样完毕后,a b 是最小值和次小值(那里没有对应提到,a
也足以是次小值)。
d 和 e 是最大值和次大值(同样没有对应提到)。
我们把 d 和 e 放到数组的结尾作为哨兵,去掉左侧界的判断。
再者让左右两侧指针都向中档移动两位,收缩不必要的比较。

测试结果,比较普通快排品质提高约 十分之一,和三取样快排差异相当小。
美高梅开户网址 35

3.3.1裴蜀定理

若a,b是整数,且(a,b)=d,那么对于自由的整数x,y,ax+by都必然是d的翻番,尤其地,一定期存款在整数x,y,使ax+by=d创建。

它的3个重中之重推论是:a,b互质的充要条件是存在整数x,y使ax+by=1.

比标算美观一小点的 G.

代码

五取样快排

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._19
{
    /// <summary>
    /// 五取样快速排序
    /// </summary>
    public class QuickSortMedian5 : BaseSort
    {
        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortMedian5() {}

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;

            // 少于五个元素的数组直接进行插入排序
            if (hi - lo + 1 < 5)
            {
                int n = hi - lo + 1;
                for (int i = lo; i - lo < n; i++)
                {
                    for (int k = i; k > 0 && Less(a[k], a[k - 1]); --k)
                    {
                        Exch(a, k, k - 1);
                    }
                }

                return;
            }

            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;

            // 假设为 a b c d e 五个数字
            // 首先对 b c 排序
            if (Less(a[lo + 2], a[lo + 1]))
                Exch(a, lo + 2, lo + 1);
            // 然后再排序 d e
            if (Less(a[lo + 4], a[lo + 3]))
                Exch(a, lo + 4, lo + 3);

            // 这时满足 b < c, d < e
            // 比较 b d,把较小的一组放到 b c 的位置上去
            if (Less(a[lo + 3], a[lo + 1]))
            {
                Exch(a, lo + 1, lo + 3);
                Exch(a, lo + 2, lo + 4);
            }

            // 这时满足 b < c, b < d < e,即 b 是 b c d e 中的最小值
            // 交换 a 和 b
            Exch(a, lo, lo + 1);

            // 重新排序 b c
            if (Less(a[lo + 2], a[lo + 1]))
                Exch(a, lo + 2, lo + 1);

            // 这时再次满足 b < c, d < e
            // 比较 b d,把最小的一组放到 b c 的位置上去
            if (Less(a[lo + 3], a[lo + 1]))
            {
                Exch(a, lo + 1, lo + 3);
                Exch(a, lo + 2, lo + 4);
            }

            // 这时 a 和 b 为五个数中的最小值和次小值(顺序不固定,a 也可以是次小值)
            // 最后比较 c 和 d,较小的那一个即为中位数(即第三小的数)
            if (Less(a[lo + 3], a[lo + 2]))
                Exch(a, lo + 3, lo + 2);

            // 此时 c 即为中位数
            Exch(a, lo, lo + 2);

            // d e 放到数组末尾充当哨兵
            Exch(a, lo + 3, hi);
            Exch(a, lo + 4, hi - 1);

            // 调整指针位置,前两位和后两位都已经在合适位置了
            j -= 2;
            i += 2;

            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v)) ;
                while (Less(v, a[--j])) ;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

三取样快排

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._19
{
    /// <summary>
    /// 三取样快速排序
    /// </summary>
    public class QuickSortMedian3 : BaseSort
    {
        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortMedian3() {}

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;

            // 少于五个元素的数组直接进行插入排序
            if (hi - lo + 1 < 5)
            {
                int n = hi - lo + 1;
                for (int i = lo; i - lo < n; i++)
                {
                    for (int k = i; k > 0 && Less(a[k], a[k - 1]); --k)
                    {
                        Exch(a, k, k - 1);
                    }
                }

                return;
            }

            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;

            if (Less(a[lo + 1], a[lo]))
                Exch(a, lo + 1, lo);
            if (Less(a[lo + 2], a[lo]))
                Exch(a, lo + 2, lo);
            if (Less(a[lo + 2], a[lo + 1]))
                Exch(a, lo + 1, lo + 2);

            Exch(a, lo, lo + 1);        // 中位数放最左侧
            Exch(a, hi, lo + 2);        // 较大的值放最右侧作为哨兵

            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v)) ;
                while (Less(v, a[--j])) ;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

测试用例

using System;
using Quick;

namespace _2._3._19
{
    /*
     * 2.3.19
     * 
     * 五取样切分。
     * 实现一种基于随机抽取子数组中 5 个元素并取中位数进行切分的快速排序。
     * 将取样元素放在数组的一侧以保证只有中位数元素参与了切分。
     * 运行双倍测试来确定这项改动的效果,
     * 并和标准的快速排序以及三取样的快速排序(请见上一道练习)进行比较。
     * 附加题:找到一种对于任意输入都只需要少于 7 次比较的五取样算法。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSort quickNormal = new QuickSort();
            QuickSortMedian3 quickMedian3 = new QuickSortMedian3();
            QuickSortMedian5 quickMedian5 = new QuickSortMedian5();
            int arraySize = 200000;                         // 初始数组大小。
            const int trialTimes = 4;                       // 每次实验的重复次数。
            const int trialLevel = 6;                       // 双倍递增的次数。

            Console.WriteLine("n\tmedian5\tmedian3\tnormal\tmedian5/normal\t\tmedian5/median3");
            for (int i = 0; i < trialLevel; i++)
            {
                double timeMedian3 = 0;
                double timeMedian5 = 0;
                double timeNormal = 0;
                for (int j = 0; j < trialTimes; j++)
                {
                    int[] a = SortCompare.GetRandomArrayInt(arraySize);
                    int[] b = new int[a.Length];
                    int[] c = new int[a.Length];
                    a.CopyTo(b, 0);
                    a.CopyTo(c, 0);
                    timeNormal += SortCompare.Time(quickNormal, a);
                    timeMedian3 += SortCompare.Time(quickMedian3, b);
                    timeMedian5 += SortCompare.Time(quickMedian5, c);
                }
                timeMedian5 /= trialTimes;
                timeMedian3 /= trialTimes;
                timeNormal /= trialTimes;
                Console.WriteLine(arraySize + "\t" + timeMedian5 + "\t" + timeMedian3 + "\t" + timeNormal + "\t" + timeMedian5 / timeNormal + "\t" + timeMedian5/timeMedian3);
                arraySize *= 2;
            }
        }
    }
}

3.3.2底和顶

  • 若接二连三且干燥增的函数\(f(x)\)满足当\(f(x)\)为整数时可推出\(x\)为整数,则\[\lfloor f(x) \rfloor = \lfloor
    f(\lfloor x \rfloor) \rfloor\]和\(\lceil f(x) \rceil = \lceil f(\lceil
    x\rceil) \rceil\)
  • \[\lfloor \frac {\lfloor\frac{x}{a}
    \rfloor}{b}\rfloor = \lfloor \frac{x}{ab}\rfloor\]
  • 对于\(i\),\(\lfloor \frac{n}{\lfloor
    \frac{n}{i}\rfloor}\rfloor\)是与\(i\)被\(n\)除并下取整取值相同的一段距离的右端点

 

另请参阅

Quick

Code to calculate “median of five” in
C#

3.3.3和式

H.
Party!

2.3.20

3.3.3.1三大定律
  • 分配律 \[\sum_{k \in K} c a_k = c
    \sum_{k \in K} a_k\]
  • 结合律\[\sum_{k \in K}(a_k +
    b_k)=\sum_{k \in K}a_k+\sum_{k \in K}b_k\]
  • 交换律\[\sum_{k \in
    K}a_k=\sum_{p(k) \in K}
    a_{p(k)}\]里面p(k)是n的一个排列
  • 麻痹大意的沟通律:若对于每1个平头\(n\),都恰好存在二个整数\(k\)使得\(p(k)=n\),那么沟通律同样创制。

    ##### 3.3.3.2求解技巧

  • 扰动法,用于总括3个和式,其思维是从三个未知的和式起初,并记他为\(S_n\):\[S_n=\sum_{0 \leqslant k \leqslant n}
    a_k\],然后,通过将她的最后一项和第二项分离出来,用三种办法重写\(S_{n+1}\),那样大家就赢得了3个关于\(S_n\)的方程,就足以博得其查封情势了。

  • 一个大面积的置换
    \[\sum_{d|n}f(d)=\sum_{d|n}f(\frac{n}{d})\]

    ##### 3.3.3.3多重和式

  • 沟通求和次序:

\[
\sum_j\sum_ka_{j,k}[P(j,k)]=\sum_{P(j,k)}a_{j,k}=\sum_k\sum_ja_{j,k}[P(j,k)]
\]

  • 一般分配律:\[\sum_{j \in J, k \in
    K}a_jb_k=(\sum_{j \in J}a_j)(\sum_{k \in K}b_k)\]

  • \(Rocky\ Road\)
    \[ \sum_{j \in J}\sum_{k \in
    K(j)}a_{j,k}=\sum_{k \in K^{‘}}\sum_{j \in J^{‘}}a_{j,k}
    \]

\[ [j \in J][k \in K(j)]=[k \in
K^{‘}][j \in J^{‘}(k)] \]

实际上,那样的因数分解放区救济总会是恐怕的:大家得以设\(J=K^{‘}\)是持有整数的集纳,而\(K(j)\)和\(J^{‘}(K)\)是与操控二重和式的性情\(P(j,k)\)相呼应的集聚。上面是二个特意有效的演讲。

\[[1\leqslant j \leqslant n][j
\leqslant k \leqslant n] = [1 \leqslant j \leqslant k \leqslant
n] = [1 \leqslant k \leqslant n][1 \leqslant j \leqslant
k]\]

  • 一个大规模的表达
    \[
    \sum_{d|n}\sum_{k|d}=\sum_{k|m}\sum_{d|\frac{m}{k}}
    \]

  • 1个技能

要是大家有2个含有\(k+f(j)\)的二重和式,用\(k-f(j)\)替换\(k\)并对\(j\)求和相比好。

难题大意

题目

非递归的短平快排序。
贯彻一个非递归的神速排序,使用2个循环来将弹出栈的子数组切分并将结果子数组重新压入栈。
在意:先将较大的子数组压入栈,那样就足以确定保证栈最四只会有 lgN 个要素。

3.3.4数论难点的求解技巧

  • \(\{\lfloor \frac{n}{i} \rfloor|i
    \in [1,n]\}\)只有\(O(\sqrt
    n)\)种取值。所以能够利用这么些结论下落复杂度。

譬如,在bzoj230第11中学,大家末掌握出了\[f(n,
m)=\sum_{1 \leqslant d \leqslant min(n, m)}\mu(d)\lfloor \frac
{n}{d} \rfloor \lfloor \frac {m}{d}
\rfloor\]大家就能够应用杜教筛总结出默比乌斯函数的前缀和,计算出商与除以i相同的最多延伸到哪儿,下三回直接跳过这一段就好了。上面是其一题的一段程序。

int calc(int n, int m) {
    int ret = 0, last;
    if(n > m) std::swap(n, m);
    for(int i = 1; i <= n; i = last + 1) { //i就相当于原式中的d
        last = min(n / (n/i), m / (m/i));  //last计算了商与除以i相同的最多延伸到哪里,不难证明这样计算的正确性
        ret += (n / i) * (m / i) * (sum[last] - sum[i-1]);
    }
    return ret;
}

  Wwj和她的女对象们一起$N$个人去开趴体。每一个人假若还没把团结的礼品送出去的话,就能够从别人手中接受礼物。第$i$个人得到第$j$人礼物的同时,也会收获一个喜欢指数$H_{i,\
j}$。求全体人的欢愉指数的总额的最大值。

解答

实质上便是用3个栈保存每便切分后的子数组下标。
根本代码如下,这里运用的栈(链栈)是在 1.3 中创设的:

/// <summary>
/// 用快速排序对数组 a 进行升序排序。
/// </summary>
/// <typeparam name="T">需要排序的类型。</typeparam>
/// <param name="a">需要排序的数组。</param>
public override void Sort<T>(T[] a)
{
    Shuffle(a);
    Stack<int> stack = new Stack<int>();
    stack.Push(0);
    stack.Push(a.Length - 1);

    while (!stack.IsEmpty())
    {
        // 压入顺序是先 lo,再 hi,故弹出顺序是先 hi 再 lo
        int hi = stack.Pop();
        int lo = stack.Pop();

        if (hi <= lo)
            continue;

        int j = Partition(a, lo, hi);

        // 让较大的子数组先入栈(先排序较小的子数组)
        if (j - lo > hi - j)
        {
            stack.Push(lo);
            stack.Push(j - 1);

            stack.Push(j + 1);
            stack.Push(hi);
        }
        else
        {
            stack.Push(j + 1);
            stack.Push(hi);

            stack.Push(lo);
            stack.Push(j - 1);
        }
    }
    Debug.Assert(IsSorted(a));
}

鉴于栈操作比函数调用操作消耗费时间间更长,由此测试后的结果会比原来快排慢 20%左右。
美高梅开户网址 36

3.3.5片段只怕会用到的定律

基本思路

代码

QuickSortNonRecursive

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._20
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortNonRecursive : BaseSort
    {
        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortNonRecursive() { }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Stack<int> stack = new Stack<int>();
            stack.Push(0);
            stack.Push(a.Length - 1);

            while (!stack.IsEmpty())
            {
                // 压入顺序是先 lo,再 hi,故弹出顺序是先 hi 再 lo
                int hi = stack.Pop();
                int lo = stack.Pop();

                if (hi <= lo)
                    continue;

                int j = Partition(a, lo, hi);

                // 让较大的子数组先入栈(先排序较小的子数组)
                if (j - lo > hi - j)
                {
                    stack.Push(lo);
                    stack.Push(j - 1);

                    stack.Push(j + 1);
                    stack.Push(hi);
                }
                else
                {
                    stack.Push(j + 1);
                    stack.Push(hi);

                    stack.Push(lo);
                    stack.Push(j - 1);
                }
            }
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

测试用例

using System;
using Quick;

namespace _2._3._20
{
    /*
     * 2.3.20
     * 
     * 非递归的快速排序。
     * 实现一个非递归的快速排序,
     * 使用一个循环来将弹出栈的子数组切分并将结果子数组重新压入栈。
     * 注意:
     * 先将较大的子数组压入栈,这样就可以保证栈最多只会有 lgN 个元素。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSort quickNormal = new QuickSort();
            QuickSortNonRecursive quickNonRecursive = new QuickSortNonRecursive();
            int arraySize = 200000;                         // 初始数组大小。
            const int trialTimes = 4;                       // 每次实验的重复次数。
            const int trialLevel = 5;                       // 双倍递增的次数。

            Console.WriteLine("n\tnon-recursive\tnormal\tratio");
            for (int i = 0; i < trialLevel; i++)
            {
                double timeRecursive = 0;
                double timeNormal = 0;
                for (int j = 0; j < trialTimes; j++)
                {
                    int[] a = SortCompare.GetRandomArrayInt(arraySize);
                    int[] b = new int[a.Length];
                    a.CopyTo(b, 0);
                    timeNormal += SortCompare.Time(quickNormal, b);
                    timeRecursive += SortCompare.Time(quickNonRecursive, a);

                }
                timeRecursive /= trialTimes;
                timeNormal /= trialTimes;
                Console.WriteLine(arraySize + "\t" + timeRecursive + "\t\t" + timeNormal + "\t" + timeRecursive / timeNormal);
                arraySize *= 2;
            }
        }
    }
}

用到的栈的落到实处

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace _2._3._20
{
    /// <summary>
    /// 栈类。
    /// </summary>
    /// <typeparam name="Item">栈中存放的元素类型。</typeparam>
    public class Stack<Item> : IEnumerable<Item>
    {
        private Node<Item> first;
        private int count;

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public Stack()
        {
            this.first = null;
            this.count = 0;
        }

        /// <summary>
        /// 复制构造函数。
        /// </summary>
        /// <param name="s"></param>
        public Stack(Stack<Item> s)
        {
            if (s.first != null)
            {
                this.first = new Node<Item>(s.first);
                for (Node<Item> x = this.first; x.next != null; x = x.next)
                {
                    x.next = new Node<Item>(x.next);
                }
            }
            this.count = s.count;
        }

        /// <summary>
        /// 检查栈是否为空。
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            return this.first == null;
        }

        /// <summary>
        /// 返回栈内元素的数量。
        /// </summary>
        /// <returns></returns>
        public int Size()
        {
            return this.count;
        }

        /// <summary>
        /// 将一个元素压入栈中。
        /// </summary>
        /// <param name="item">要压入栈中的元素。</param>
        public void Push(Item item)
        {
            Node<Item> oldFirst = this.first;
            this.first = new Node<Item>();
            this.first.item = item;
            this.first.next = oldFirst;
            this.count++;
        }

        /// <summary>
        /// 将一个元素从栈中弹出,返回弹出的元素。
        /// </summary>
        /// <returns></returns>
        public Item Pop()
        {
            if (IsEmpty())
                throw new InvalidOperationException("Stack Underflow");
            Item item = this.first.item;
            this.first = this.first.next;
            this.count--;
            return item;
        }

        /// <summary>
        /// 返回栈顶元素(但不弹出它)。
        /// </summary>
        /// <returns></returns>
        public Item Peek()
        {
            if (IsEmpty())
                throw new InvalidOperationException("Stack Underflow");
            return this.first.item;
        }

        /// <summary>
        /// 将两个栈连接。
        /// </summary>
        /// <param name="s1">第一个栈。</param>
        /// <param name="s2">第二个栈(将被删除)。</param>
        /// <returns></returns>
        public static Stack<Item> Catenation(Stack<Item> s1, Stack<Item> s2)
        {
            if (s1.IsEmpty())
            {
                s1.first = s2.first;
                s1.count = s2.count;
            }
            else
            {
                Node<Item> last = s1.first;
                while (last.next != null)
                {
                    last = last.next;
                }
                last.next = s2.first;
                s1.count += s2.count;
            }
            s2 = null;
            return s1;
        }

        /// <summary>
        /// 创建栈的浅表副本。
        /// </summary>
        /// <returns></returns>
        public Stack<Item> Copy()
        {
            Stack<Item> temp = new Stack<Item>();
            temp.first = this.first;
            temp.count = this.count;
            return temp;
        }

        public override string ToString()
        {
            StringBuilder s = new StringBuilder();
            foreach (Item n in this)
            {
                s.Append(n);
                s.Append(' ');
            }
            return s.ToString();
        }

        public IEnumerator<Item> GetEnumerator()
        {
            return new StackEnumerator(this.first);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        private class StackEnumerator : IEnumerator<Item>
        {
            private Node<Item> current;
            private Node<Item> first;

            public StackEnumerator(Node<Item> first)
            {
                this.current = new Node<Item>();
                this.current.next = first;
                this.first = this.current;
            }

            Item IEnumerator<Item>.Current => this.current.item;

            object IEnumerator.Current => this.current.item;

            void IDisposable.Dispose()
            {
                this.current = null;
                this.first = null;
            }

            bool IEnumerator.MoveNext()
            {
                if (this.current.next == null)
                    return false;

                this.current = this.current.next;
                return true;
            }

            void IEnumerator.Reset()
            {
                this.current = this.first;
            }
        }
    }
}
3.3.5.1费马小定理

\[ a^{p-1}\equiv1\pmod p \]

条件:\(p\ is\ prime\ and\
(a,p)=1\)

  诶?有人用贪心做?有人用搜索做?听新闻说还有人用最小生成树做?诶等等那多少个用无向图MST的什么情感?那不是有向图?(小编的天呐.jpg)

另请参阅

Quick

Generics

3.3.5.2欧拉定理

\[ a^{\varphi(p)}\equiv 1\pmod p
\]

条件:\(a,p \in \mathbb{Z^+}, (a,
p)=1\)

  那题可以跑三个网络流,当然也可以DP。什么人告诉你给3个矩阵就自然是图论题了?mdzz。

2.3.21

3.3.5.3威尔逊定理

\[ (p-1)!\equiv-1\pmod p \Leftrightarrow
p\ is\ prime \]

  能DP作者当然不写互连网流,考虑收礼品状态矩阵$M$,$M_{i,\
j}$代表第$i$个人收没收第$j$人的礼物($i\neq
j$),收了为$1$,没收为$0$。出于各类人只有一份礼物,他不得不送给一位,所以在相同列内最四只有$1$个$1$,所以状态矩阵$M$能够一贯拍扁。好的,$N\leq
10$,大家得以愉悦地气象压缩了,状态数$2^N-1$,dp时间复杂度$O(2^N
N^2)$。

题目

将再一次元素排序的可比次数的下界。
成功命题 M 的表达的首先片段。
参考命题 I 的求证并注意当有 $ k $ 个主键值时全部因素存在 $
N!/f_1!f_2!…f_k!$ 种差异的排列,
里面第 $ i $ 个主键值出现的票房价值为 $ f_1 $(即 $ N_{p_i} $,遵照命题 M
的记法),且 $ f_1+… +f_k=N $。

3.3.5.4Pique定理

\[ S=n+\frac s2-1 \]

(其中\(n\)表示多边形内部的罗列,\(s\)表示多边形边界上的罗列,\(S\)表示多边形的面积)

  考虑任意状态state,假若第$i\geq
0$位为$1$(即$state\&(1<<i)!=0$),则注脚该情状下第$i$个人已经把她的赠品给出去了(当然也不恐怕发生给协调的动静)。对每2个气象求最大畅快指数,再取全体处境的最大值。

解答

率先引入命题 I
的定论,对于分歧的主键值,基于相比的排序算法的下界等于所形成的可比树的中度,即:
\[ h \ge \log_2{N!} \]
那么大家标题即可转接为说明
\[ h \ge \log_2
(\frac{N!}{f_1!f_2!\cdots f_k!}) \ge \log_2 N! \]
这里的 $ f_i $ 为某些主键值出现的频率,即某些主键值出现的次数,因而
\(f_i\ge 1\) 。
按照难题给出的尺度,即使主键互不重复,此时 $ k=N $,且 $
f_1=f_2=\cdots=f_k=1 $ 。
那么 $ f_1!f_2!\cdots f_k!=1 $ ,待证式子即为命题 I 的定论。

那正是说当主键有再一次时,此时 $ k < N $,为使 $ f_1+f_2+ \cdots +
f_k=N $ ,至少存在2个 $ f_m \ge 2 $。
故此时:
\[ f_1!f_2!\cdots f_k!
>1\Rightarrow \frac{N!}{f_1!f_2!\cdots f_k!}<N! \Rightarrow
\\ h \ge \log_2 (\frac{N!}{f_1!f_2!\cdots f_k!}) \ge \log_2
N! \ \blacksquare \]
得证。

3.4别的数学工具

参照代码

另请参阅

lower bounds of sorting-The University of
Maryland

3.4.1快速乘

inline ll mul(ll a, ll b) {
  ll x = 0;
  while (b) {
    if (b & 1)
      x = (x + a) % p;
    a = (a << 1) % p;
    b >>= 1;
  }
  return x;
}

美高梅开户网址 37美高梅开户网址 38

2.3.22

3.4.2快速幂

inline ll pow(ll a, ll b, ll p) {
  ll x = 1;
  while (b) {
    if (b & 1)
      x = mul(x, a);
    a = mul(a, a);
    b >>= 1;
  }
  return x;
}
 1 #include <stdio.h>
 2 
 3 int N, H[11][11];
 4 inline void getMax(int&n, int x) { if(n<x) n=x; }
 5 void read() {
 6     scanf("%d", &N);
 7     for(int i=0; i<N; i++)
 8         for(int j=0; j<N; j++)
 9             scanf("%d", H[i]+j);
10 }
11 void work() {
12     int maxState = 1<<N, dp[maxState]={0};
13     for(int state=0; state<maxState; state++)
14         for(int i=0; i<N; i++) if(!(state&(1<<i)))
15             for(int j=0; j<N; j++) if(i!=j&&!(state&(1<<j)))
16                 getMax(dp[state+(1<<j)], dp[state]+H[i][j]);
17     int res = 0;
18     for(int state=0; state<maxState; state++)
19         getMax(res, dp[state]);
20     printf("%d\n", res);
21 }
22 int main() {
23     int T;
24     scanf("%d",&T);
25     while(T--) {
26         read();
27         work();
28     }
29     return 0;
30 }
题目

一点也不慢三向切分。(J.Bently,D.McIlroy)
用将再也成分放置于子数组两端的情势贯彻2个音信量最优的排序算法。
选取八个索引 p 和 q,使得 a[lo…p-1] 和 a[q+1..hi] 的因素都和
a[lo] 相等。
接纳其余多个索引 i 和 j,使得 a[p…i-1] 小于 a[lo],a[j+i..q]
大于 a[lo]。
在内循环中参加代码,在 a[i] 和 v 相等时将其与 a[p] 交换(并将 p 加
1),
在 a[j] 和 v 相等且 a[i] 和 a[j] 尚未和 v 举办比较前面将其与
a[q] 交换。
添加在切分循环截止后将和 v 相等的因素沟通到正确地方的代码,如图 2.3.6
所示。
请注意:
此处完毕的代码和正文中付出的代码时等价的,
因为那里额外的沟通用于和切分成分相等的成分,
而本文中的代码将附加的交流用于和切分成分不等的因素。

3.4.3更相减损术

率先步:任意给定五个正整数;判断它们是或不是都以偶数。假如,则用2约简;若不是则实施第壹步。

其次步:以较大的数减较小的数,接着把所得的差与较小的数相比较,并以大数减小数。继续这么些操作,直到所得的减数和差相等截止。

则率先步中约掉的几何个2与第②步中等数的乘积正是所求的最大公约数。

H. Party!

解答

合法达成见:

快捷三向切分

舆论引用见「另请参阅」部分。
算法演示
美高梅开户网址 39

Ninther 算法

官方实现中用到了 Ninther 算法用于选择近似中位数(作为枢轴),
该算法由 John Tukey 在 一九七七 年提议,随想引用见「另请参阅」部分。
以此算法的思索实际非常粗略,要是大家有七个数 $ y_1, y_2, y_3 $
,那么内部位数为:
\[ y_A= {\rm median}\lbrace
y_1,y_2,y_3 \rbrace \]
未来对于7个数,大家以多个为一组,取五当中位数:
\[ y_A= {\rm median}\lbrace
y_1,y_2,y_3 \rbrace \\ y_B= {\rm median}\lbrace y_4,y_5,y_6
\rbrace \\ y_C= {\rm median}\lbrace y_7,y_8,y_9 \rbrace
\]
接下去取那四个中位数的中位数,有:
\[ y_E= {\rm median}\lbrace
y_A,y_B,y_C \rbrace \]
大家把上述进程封装成函数,即 $ y_E= {\rm ninther}\lbrace
y_1,y_2,\cdots,y_9 \rbrace $ 。
于是乎大家赢得的 $ y_E $ 即为近似中位数,借使 $ \lbrace
y_1,y_2,\cdots,y_9 \rbrace $ 是干燥数列,那么 $ y_E $ 正是中位数。

收获多少个数中的中位数

实则,大家能够直接画出多个数排列的具有可能,获得决策树。
美高梅开户网址 40
下一场依照决策树写出取中位数的算法:

private int Median3<T>(T[] a, int i, int j, int k) where T : IComparable<T>
{
    return
        (Less(a[i], a[j]) ?
        (Less(a[j], a[k]) ? j : Less(a[i], a[k]) ? k : i) :
        (Less(a[k], a[j]) ? j : Less(a[k], a[i]) ? k : i));
}

测试结果

增强约 十分二 左右的习性。
美高梅开户网址 41

3.4.4逆元

依照费马小定理(p是质数),

int inv(int x, int P){return pow(x, P-2, P);}

或采取扩张欧几Reade:

int inv(int x, int P) {
  int d, a, b;
  gcd(x, P, d, a, b);
  return d == 1 ? (a+P)%P : -1;
}

 

代码

QuickBentleyMcIlroy

using System;
using System.Diagnostics;

namespace Quick
{
    public class QuickBentleyMcIlroy : BaseSort
    {
        /// <summary>
        /// 小于这个数值的数组调用插入排序。
        /// </summary>
        private readonly int INSERTION_SORT_CUTOFF = 8;

        /// <summary>
        /// 小于这个数值的数组调用中位数作为枢轴。
        /// </summary>
        private readonly int MEDIAN_OF_3_CUTOFF = 40;

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickBentleyMcIlroy() { }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 对指定范围内的数组进行排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序的起始下标。</param>
        /// <param name="hi">排序的终止下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int n = hi - lo + 1;

            if (n <= this.INSERTION_SORT_CUTOFF)
            {
                InsertionSort(a, lo, hi);
                return;
            }
            else if (n <= this.MEDIAN_OF_3_CUTOFF)
            {
                // 对于较小的数组,直接选择左中右三个元素中的中位数作为枢轴。
                int m = Median3(a, lo, lo + n / 2, hi);
                Exch(a, m, lo);
            }
            else
            {
                // 对于较大的数组使用 Turkey Ninther 作为枢轴。
                int eps = n / 8;
                int mid = lo + n / 2;
                int m1 = Median3(a, lo, lo + eps, lo + eps + eps);
                int m2 = Median3(a, mid - eps, mid, mid + eps); 
                int m3 = Median3(a, hi - eps - eps, hi - eps, hi);
                int ninther = Median3(a, m1, m2, m3);
                Exch(a, ninther, lo);
            }

            // 三向切分
            int i = lo, j = hi + 1;
            int p = lo, q = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;

                if (i == j && IsEqual(a[i], v))
                    Exch(a, ++p, i);
                if (i >= j)
                    break;

                Exch(a, i, j);
                if (IsEqual(a[i], v))
                    Exch(a, ++p, i);
                if (IsEqual(a[j], v))
                    Exch(a, --q, j);
            }

            i = j + 1;
            for (int k = lo; k <= p; k++)
                Exch(a, k, j--);
            for (int k = hi; k >= q; k--)
                Exch(a, k, i++);

            Sort(a, lo, j);
            Sort(a, i, hi);
        }

        /// <summary>
        /// 判断两个元素是否值相等。
        /// </summary>
        /// <typeparam name="T">需要判断的元素类型。</typeparam>
        /// <param name="a">进行比较的第一个元素。</param>
        /// <param name="b">进行比较的第二个元素。</param>
        /// <returns>两个元素的值是否相等。</returns>
        private bool IsEqual<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b) == 0;
        }

        /// <summary>
        /// 用插入排序对指定范围内的数组排序。
        /// </summary>
        /// <typeparam name="T">数组的元素类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序的起始下标。</param>
        /// <param name="hi">排序的终止下标。</param>
        private void InsertionSort<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            for (int i = lo; i <= hi; i++)
            {
                for (int j = i; j > lo && Less(a[j], a[j - 1]); j--)
                {
                    Exch(a, j, j - 1);
                }
            }
        }

        /// <summary>
        /// 获取三个元素中的中位数。
        /// </summary>
        /// <typeparam name="T">用于排序的元素。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="i">第一个待选元素的下标。</param>
        /// <param name="j">第二个待选元素的下标。</param>
        /// <param name="k">第三个待选元素的下标。</param>
        /// <returns></returns>
        private int Median3<T>(T[] a, int i, int j, int k) where T : IComparable<T>
        {
            return
               (Less(a[i], a[j]) ?
               (Less(a[j], a[k]) ? j : Less(a[i], a[k]) ? k : i) :
               (Less(a[k], a[j]) ? j : Less(a[k], a[i]) ? k : i));
        }
    }
}

测试用例

using System;
using Quick;

namespace _2._3._22
{
    /*
     * 2.3.22
     * 
     * 快速三向切分。(J.Bently,D.McIlroy)
     * 用将重复元素放置于子数组两端的方式实现一个信息量最优的排序算法。
     * 使用两个索引 p 和 q,使得 a[lo...p-1] 和 a[q+1..hi] 的元素都和 a[lo] 相等。
     * 使用另外两个索引 i 和 j,
     * 使得 a[p...i-1] 小于 a[lo],a[j+i..q] 大于 a[lo]。
     * 在内循环中加入代码,在 a[i] 和 v 相当时将其与 a[p] 交换(并将 p 加 1),
     * 在 a[j] 和 v 相等且 a[i] 和 a[j] 尚未和 v 进行比较之前将其与 a[q] 交换。
     * 添加在切分循环结束后将和 v 相等的元素交换到正确位置的代码,如图 2.3.6 所示。
     * 请注意:
     * 这里实现的代码和正文中给出的代码时等价的,
     * 因为这里额外的交换用于和切分元素相等的元素,
     * 而正文中的代码将额外的交换用于和切分元素不等的元素。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSort quickNormal = new QuickSort();
            QuickBentleyMcIlroy quickBentleyMcIlroy = new QuickBentleyMcIlroy();
            int arraySize = 800000;                         // 初始数组大小。
            const int trialTimes = 1;                       // 每次实验的重复次数。
            const int trialLevel = 8;                       // 双倍递增的次数。

            Console.WriteLine("n\t\t3way\tnormal\tratio");
            for (int i = 0; i < trialLevel; i++)
            {
                double timeBentleyMcIlroy = 0;
                double timeNormal = 0;
                for (int j = 0; j < trialTimes; j++)
                {
                    int[] a = SortCompare.GetRandomArrayInt(arraySize);
                    int[] b = new int[a.Length];
                    a.CopyTo(b, 0);
                    timeNormal += SortCompare.Time(quickNormal, b);
                    timeBentleyMcIlroy += SortCompare.Time(quickBentleyMcIlroy, a);

                }
                timeBentleyMcIlroy /= trialTimes;
                timeNormal /= trialTimes;
                if (arraySize < 10000000)
                    Console.WriteLine(arraySize + "\t\t" + timeBentleyMcIlroy + "\t" + timeNormal + "\t" + timeBentleyMcIlroy / timeNormal);
                else
                    Console.WriteLine(arraySize + "\t" + timeBentleyMcIlroy + "\t" + timeNormal + "\t" + timeBentleyMcIlroy / timeNormal);
                arraySize *= 2;
            }
        }
    }
}

3.4.5博弈论

  • Nim游戏
  • SG函数

定义\[SG(x)=mex(S)\],其中\(S\)是\(x\)的后继状态的\(SG\)函数集合,\(mex(S)\)表示不在\(S\)内的细微非负整数。

  • SG定理

结合游戏和的\(SG\)函数等于各子游戏\(SG\)函数的\(Nim\)和。

I.
Square

另请参阅

至于那种高速排序算法的源于以及八个数的中位数的抉择算法,请参阅上边那篇
一九九五 年的舆论:
Bentley J L, McIlroy M D. Engineering a sort function[J]. Software:
Practice and Experience, 1993, 23(11):
1249-1265.
下边那份 二零零零 年的 PPT 详细分解和分析了法定落成代码的笔触和性质:
Sedgewick R, Bentley J. Quicksort is optimal[J]. Knuthfest, Stanford
University, Stanford,
2002.
至于选取中位数 Ninther 算法,请参阅上边这篇 一九七八 年的舆论:
Tukey J W. The ninther, a technique for low-effort robust (resistant)
location in large samples[M]//Contributions to Survey Sampling and
Applied Statistics. 1978:
251-257.
以及依照规矩给出本题用到的类库链接:
Quick

3.4.6可能率与数学期望

  • 全可能率公式\[P(A)=P(A|B_1)*P(B_1)+P(A|B_2)*P(B_2)+\cdots+P(A|B_n)*P(B_n)\]
  • 数学期望

题材大意:$N\times
N$的矩阵,各种格子要填$0$或$1$,须要每行每列中$1$的个数就算奇数个。

2.3.23

3.4.7高效傅立叶变换

基本思路:不考虑范围标准,有$2^{N^2}$种方案对吧?能乱填对吧?那什么确认保证每行每列中$1$的个数是奇数个?在边际加多一行加多一列(即$(N+1)\times(N+1)$),对于每一行每一列,假设$1$的个数是偶数个,再填个$1$,不然填$0$进去。啥?剩下这些格子怎么做?会一边奇数一边偶数?嗯,由于是$N\times
N$,所以是不容许的。由此$S_N=2^{(N-1)^2}$,神速幂或然意料之外的优化即可。

题目

Java 的排序库函数。
在演习 2.3.22 的代码中应用 Tukey’s ninther
方法来找出切分成分——选用三组,
每组多少个要素,分别取三组成分的中位数,然后取多在那之中位数的中位数作为切分成分,
且在排序小数组时切换成插入排序。

3.4.7.1核心概念

快速傅立叶变换(FFT)用于求解多项式的卷积.

  • 单位根:单位圆的\(n\)等分点为终点,作\(n\)个向量,所得的幅角为正且最小的向量对应的复数为\(\omega_n\),称为\(n\)次单位根.
    \[\omega_n^k=cosk\frac{2\pi}{n}+isin\
    k\frac{2\pi}n\]
  • 单位根的质量:\[\omega_{2n}^{2k}=\omega_n^k\]
  • 单位根的特性:\[\omega_{n}^{k+\frac
    nk}=-\omega _n^k\]

参照代码

解答

法定实现见:
见 2.3.22 的解答,在那之中已经包罗了那么些改变。

3.4.7.2快速傅立叶变换

设想将多项式\(A_1(x)=a_0+a_2x^2+a_4x^4+\cdots+a_{n-2}x^{\frac
n2 -1}\)

\[A_2(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac
n2 -1}\]

则有\[A(x)=A_1(x)+xA_2(x)\]

有\[A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})\]

有\[A(\omega_n^{k+\frac
n2})=A_1{\omega_\frac n2^k-\omega_n^kA_2(\omega_\frac n2 ^
k)}\].

注意到,当\(k\)取遍\([0,\frac n2 -1]\)时,\(k\)和\(k+\frac n2\)取遍了\([0,n-1]\),也正是说,假设已知\(A_1(x)\)和\(A_2(x)\)在\(\omega_{n/2}^0,\omega_{n/2}^1,\cdots,\omega_{n/2}^{n/2-1}\)处的点值,就足以在\(O(n)\)的时刻内求得\(A(x)\)在\(\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1}\)处的取值。而关于
\(A_1(x)\) 和 \(A_2(x)\)
的难点都以相对于原难题规模压缩了百分之五十的子难点,分治的边际为二个常数项\(a_0\).

该算法的复杂度为\(O(nlogn)\).

合法代码(分块处理,把47改成15,不用long
long用int也是足以的,当然时间就差个2.5倍咯):

代码

QuickBentleyMcIlroy

using System;
using System.Diagnostics;

namespace Quick
{
    public class QuickBentleyMcIlroy : BaseSort
    {
        /// <summary>
        /// 小于这个数值的数组调用插入排序。
        /// </summary>
        private readonly int INSERTION_SORT_CUTOFF = 8;

        /// <summary>
        /// 小于这个数值的数组调用中位数作为枢轴。
        /// </summary>
        private readonly int MEDIAN_OF_3_CUTOFF = 40;

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickBentleyMcIlroy() { }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 对指定范围内的数组进行排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序的起始下标。</param>
        /// <param name="hi">排序的终止下标。</param>
        private void Sort<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int n = hi - lo + 1;

            if (n <= this.INSERTION_SORT_CUTOFF)
            {
                InsertionSort(a, lo, hi);
                return;
            }
            else if (n <= this.MEDIAN_OF_3_CUTOFF)
            {
                // 对于较小的数组,直接选择左中右三个元素中的中位数作为枢轴。
                int m = Median3(a, lo, lo + n / 2, hi);
                Exch(a, m, lo);
            }
            else
            {
                // 对于较大的数组使用 Turkey Ninther 作为枢轴。
                int eps = n / 8;
                int mid = lo + n / 2;
                int m1 = Median3(a, lo, lo + eps, lo + eps + eps);
                int m2 = Median3(a, mid - eps, mid, mid + eps); 
                int m3 = Median3(a, hi - eps - eps, hi - eps, hi);
                int ninther = Median3(a, m1, m2, m3);
                Exch(a, ninther, lo);
            }

            // 三向切分
            int i = lo, j = hi + 1;
            int p = lo, q = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v)) ;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;

                if (i == j && IsEqual(a[i], v))
                    Exch(a, ++p, i);
                if (i >= j)
                    break;

                Exch(a, i, j);
                if (IsEqual(a[i], v))
                    Exch(a, ++p, i);
                if (IsEqual(a[j], v))
                    Exch(a, --q, j);
            }

            i = j + 1;
            for (int k = lo; k <= p; k++)
                Exch(a, k, j--);
            for (int k = hi; k >= q; k--)
                Exch(a, k, i++);

            Sort(a, lo, j);
            Sort(a, i, hi);
        }

        /// <summary>
        /// 判断两个元素是否值相等。
        /// </summary>
        /// <typeparam name="T">需要判断的元素类型。</typeparam>
        /// <param name="a">进行比较的第一个元素。</param>
        /// <param name="b">进行比较的第二个元素。</param>
        /// <returns>两个元素的值是否相等。</returns>
        private bool IsEqual<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b) == 0;
        }

        /// <summary>
        /// 用插入排序对指定范围内的数组排序。
        /// </summary>
        /// <typeparam name="T">数组的元素类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序的起始下标。</param>
        /// <param name="hi">排序的终止下标。</param>
        private void InsertionSort<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            for (int i = lo; i <= hi; i++)
            {
                for (int j = i; j > lo && Less(a[j], a[j - 1]); j--)
                {
                    Exch(a, j, j - 1);
                }
            }
        }

        /// <summary>
        /// 获取三个元素中的中位数。
        /// </summary>
        /// <typeparam name="T">用于排序的元素。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="i">第一个待选元素的下标。</param>
        /// <param name="j">第二个待选元素的下标。</param>
        /// <param name="k">第三个待选元素的下标。</param>
        /// <returns></returns>
        private int Median3<T>(T[] a, int i, int j, int k) where T : IComparable<T>
        {
            return
               (Less(a[i], a[j]) ?
               (Less(a[j], a[k]) ? j : Less(a[i], a[k]) ? k : i) :
               (Less(a[k], a[j]) ? j : Less(a[k], a[i]) ? k : i));
        }
    }
}
3.4.7.3傅立叶逆变换

设\((y_0,y_1,\cdots,y_{n-1})\)为\((a_0,\cdots,a_{n-1})\)的迅猛傅立叶变换.
考虑另一个向量\((c_0,\cdots,c_{n-1})\)满足

\[c_k=\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i\].

即多项式\(B(x)=y_0+y_1x+\cdots+y_{n-1}x^{n-1}\)在\(\omega_n^0,\cdots,\omega_n^{-(n-1)}\)处的点值表示.

可以博得(申明略)

\[a_i=\frac 1n c_i\].

故此,使用单位根的尾数代替单位根,做二次接近连忙傅里叶变换的进度,再将结果每种数除以\(n\),即为傅里叶逆变换的结果。

美高梅开户网址 42美高梅开户网址 43

另请参阅

Quick

3.7.4.4代码达成

FFT有三种普遍的代码实现:
递归版本和迭代版本,一般来讲递归功效很差,但鉴于自家特别菜,一轮省选就先选取递归版本骗分.迭代版本之后会更新.

const double PI = acos(-1);
bool inversed = false;
inline std::complex<double> omega(const int n, const int k) {
  if (!inversed)
    return std::complex<double>(cos(2 * PI / n * k), sin(2 * PI / n * k));
  else
    return std::conj(
        std::complex<double>(cos(2 * PI / n * k), sin(2 * PI / n * k)));
}
void fft(std::complex<double> *a, std::complex<double> *ans, int n) {
  if (n == 1) {
    ans[0] = a[0];
    return;
  }
  std::complex<double> buf[maxn];
  int m = n >> 1;
  for (int i = 0; i < m; i++) {
    buf[i] = a[i * 2];
    buf[i + m] = a[i * 2 + 1];
  }
  std::complex<double> tmp[maxn];
  fft(buf, tmp, m);
  fft(buf + m, tmp + m, m);
  for (int i = 0; i < m; i++) {
    std::complex<double> x = omega(n, i);
    ans[i] = tmp[i] + x * tmp[i + m];
    ans[i + m] = tmp[i] - x * tmp[i + m];
  }
}
void solve() {
  //sth
  while (N < n + 1)
    N <<= 1;
  N <<= 1;
  fft(a, ans1, N);
  fft(b, ans2, N);
  std::complex<double> ans3[maxn];
  for (int i = 0; i < N; i++)
    ans3[i] = ans1[i] * ans2[i];
  std::complex<double> tmp[maxn];
  inversed = true;
  fft(ans3, tmp, N);
  for (int i = 0; i < N; i++)
    c[i] = tmp[i].real() / N;
  //sth
}

$\mathtt{COPYRIGHT}© \mathtt{2017,KONJAC,MIT LICENSE} $

 1 #include <stdio.h>
 2 #define ULL unsigned long long
 3 int main() {
 4     ULL res;
 5     int n;
 6     int T;
 7     scanf("%d",&T);
 8     while(T--) {
 9         scanf("%d",&n);
10         res=1;
11         for(ULL i = 0; i<(ULL)(n-1)*(n-1)/47; i++)
12             res=(res<<47)%100007;
13         for(ULL i = 0; i<(ULL)(n-1)*(n-1)%47; i++)
14             res=(res*2)%100007;
15         printf("%d\n",res);
16     }
17     return 0;
18 }

2.3.24

I. Square

题目

抽样排序。(W.Frazer,A.McKellar)
落到实处2个火速排序,取样大小为 2^k-1。
先是将取样获得的成分排序,然后在递归函数中运用样品的中位数切分。
分为两部分的别样样品成分无需重新排序并能够独家采用于原数组的两个子数组。
那种算法称为取样排序。

非官方代码(快捷幂,怎么说也是log的复杂度,比地方竟然的优化要快正是了):

解答

抽样排序的想法非常的粗略:
常规快排的枢轴唯有3个。
假诺用叁个数组来充当枢轴,依据排序地点的两样机关选择相应的枢轴,
远近著名能够更好的估计中位数,以求更好的切分效果。
于是引入了「取样」的概念,假诺大家从源数组中专擅取了 三个要素并对其排序,
那正是说那 三个元素的中位数能够看作第二遍切分的枢轴,剩余七个因素则足以担任切分后八个子数组的枢轴。
那就是说当取样成分到达三个适宜的数量时,就能落得进步切分功效的靶子。

大体思路如下:
第二先从输入数组里随机取一些要素,作为「取样数组」。
用随意排序算法(比如快排)对取样数组开始展览排序。
(由于取样数组平日都比较小,这一步的时辰开销平时不会潜移默化属性)
取出取样数组里面包车型大巴中位数,当作枢轴对余下的数组举办切分。
随后的切分中,依据排序区间在剩余数组中的相对位置,
用取样数组中对应地点的数作为枢轴,直到一切排序完结。

杂文里提到了三种达成方式。
率先种办法
抽样数组和剩余数组是分手保存的。
每趟切分完毕后,并不把枢轴放入剩余数组中,
而是等到剩余数组全体排序实现之后再用二遍归并(merge)操作将取样数组和剩余数组归并。
第二种方法
抽样数组和剩余数组保存在一样片空间里,那也是那份题解所完结的措施。
美高梅开户网址 44
在打乱输入数组之后,取前 2^k-1 个因素作为取样数组,用快排对其排序。
接下来把取样数组的后半有个别放到任何数组的终极。
如此操作的结果是输入数组分为了多少个部分:
有序的抽样数组、取样数组的中位数、冬季的剩余数组、有序的抽样数组。
中位数则放在第三有个别的最后,我们将其看做枢轴对剩余数组实行切分,数组变为:
一如既往的取样数组、小于中位数的有的、枢轴、大于中位数的有的、有序的抽样数组
接下去大家再对第二个部分取半,放到中位数以前;对最后一有的取半,放到中位数之后:
0 ~ 百分之二十五 取样数组、小于中位数、百分之二十五 ~ 3/6 取样数组、枢轴、二分之一~75%取样数组、大于中位数、四分之三~1 取样数组
你会意识枢轴前后又分别变回了开端标准,递归执行上述操作,便能对一切数组排序。
注意当取样数组用完的时候,直接变回普通的快排。

当代的取样排序
此处的「现代」并不代表更好,只是让取样排序能更好的适应八线程排序。
第2依旧是取样,取样的数目往往取决于线程的数码,比如说取了 p-2个,就将数组分为 p 份。
对取样数组开始展览排序,获得 p 个区间(桶)。
遍历输入的数组,把成分扔到对应的桶里面。
把每一个桶和相应的枢轴送到相应的线程举办排序。
集中各种桶中的结果,排序完结。

测试结果:
美高梅开户网址 45
大体能升高 5%~10% 的性能。

美高梅开户网址 46美高梅开户网址 47

代码
using System;
using System.Diagnostics;

namespace Quick
{
    /// <summary>
    /// 取样排序类。
    /// </summary>
    public class SampleSort : QuickSort
    {
        /// <summary>
        /// 取样数组长度 2^k - 1 的阶数。
        /// </summary>
        public int K { get; set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public SampleSort()
        {
            this.K = 8;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            if (a.Length < Math.Pow(2, this.K + 1))
            {
                // 小于 2^(k+1) 的数组直接进行快排
                base.Sort(a);
                return;
            }

            Shuffle(a);
            int samplehi = (int)Math.Pow(2, this.K) - 2;
            // 利用快速排序对取样数组进行排序
            base.Sort(a, 0, samplehi);
            // 找到取样数组的中位数
            int sampleMedian = samplehi / 2;
            // 将取样数组后半部分放到数组末尾
            int i = samplehi, j = a.Length - 1;
            while (i != sampleMedian)
                Exch(a, i--, j--);
            // 根据取样数组进行排序
            Sort(a, 0, sampleMedian, j, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="samplelo">取样数组的起始下标。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        /// <param name="samplehi">取样数组的终止下标。</param>
        private void Sort<T>(T[] a, int samplelo, int lo, int hi, int samplehi) where T : IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;

            int j = Partition(a, lo, hi);
            // 将前部的有序取样数组取半,后半部分放在枢轴前面。
            if (lo - samplelo > 1)
            {
                // p 应该始终指向有序部分的最后一项
                // v 应该始终指向有序部分的前面一项
                int p = lo - 1, v = j - 1;
                for (int i = 0; i < (lo - samplelo) / 2; i++)
                {
                    Exch(a, p--, v--);
                }
                Sort(a, samplelo, p, v, j - 1);
            }
            else
            {
                // 取样数组已经用完,退化为普通 Quicksort
                base.Sort(a, samplelo, j - 1);
            }

            // 将尾部有序取样数组取半,前半部分放在枢轴后面。
            if (samplehi - hi > 1)
            {
                // p 应该始终指向有序部分的前面一项
                // v 应该始终指向有序部分的最后一项
                int p = hi, v = j;
                for (int i = 0; i < (samplehi - hi) / 2; i++)
                {
                    Exch(a, ++p, ++v);
                }
                Sort(a, j + 1, v, p, samplehi);
            }
            else
            {
                // 取样数组已用完,退化为普通 Quicksort
                base.Sort(a, j + 1, samplehi);
            }
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

测试用例:

using System;
using Quick;

namespace _2._3._24
{
    /*
     * 2.3.24
     * 
     * 取样排序。(W.Frazer,A.McKellar)
     * 实现一个快速排序,
     * 取样大小为 2^k-1。首先将取样得到的元素排序,
     * 然后在递归函数中使用样品的中位数切分。
     * 分为两部分的其余样品元素无需再次排序并可以分别应用于原数组的两个子数组。
     * 这种算法称为取样排序。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSort quickNormal = new QuickSort();
            SampleSort sampleSort = new SampleSort();
            int arraySize = 1600000;                        // 初始数组大小。
            const int kSteps = 10;                          // 取样 k 值的递增次数。
            const int trialTimes = 1;                       // 每次实验的重复次数。
            const int trialLevel = 2;                       // 双倍递增的次数。

            Console.WriteLine("k\tn\t\tsample\tnormal\tratio");
            for (int i = 0; i < kSteps; i++)
            {
                int array = arraySize;
                for (int j = 0; j < trialLevel; j++)
                {
                    double timeSample = 0;
                    double timeNormal = 0;
                    for (int k = 0; k < trialTimes; k++)
                    {
                        int[] a = SortCompare.GetRandomArrayInt(array);
                        int[] b = new int[a.Length];
                        a.CopyTo(b, 0);
                        timeNormal += SortCompare.Time(quickNormal, b);
                        timeSample += SortCompare.Time(sampleSort, a);

                    }
                    timeSample /= trialTimes;
                    timeNormal /= trialTimes;
                    if (arraySize < 10000000)
                        Console.WriteLine(sampleSort.K + "\t" + array + "\t\t" + timeSample + "\t" + timeNormal + "\t" + timeSample / timeNormal);
                    else
                        Console.WriteLine(sampleSort.K + "\t" + array + "\t" + timeSample + "\t" + timeNormal + "\t" + timeSample / timeNormal);
                    array *= 2;
                }
                sampleSort.K++;
            }

        }
    }
}
 1 #include <stdio.h>
 2 const int MOD = 100007;
 3 long long pow(long long x, int n) {
 4     long long res = 1LL;
 5     while(n) {
 6         if(n&1) res = res*x%MOD;
 7         x = x*x%MOD;
 8         n >>= 1;
 9     }
10     return res;
11 }
12 int main() {
13     int T, N;
14     scanf("%d",&T);
15     while(T--) {
16         scanf("%d",&N);
17         --N; N *= N;
18         printf("%d\n", pow(2, N));
19     }
20     return 0;
21 }
另请参阅

至于取样排序的舆论(1966 年):
Frazer W D, McKellar A C. Samplesort: A sampling approach to minimal
storage tree sorting[J]. Journal of the ACM (JACM), 1970, 17(3):
496-507.
维基百科中的取样排序:
Samplesort-Wikipedia
焦点用到的类库链接:
Quick

I. Square

2.3.25

 

题目

切换来插入排序。
完毕四个高速排序,在子数组成分少于 M 时切换来插入排序。
用高速排序处理大小 N 分别为 10^三 、10^四 、10^5 和 10^6 的即兴数组,
依照经验给出使其在你的环境中运转速度最快的 M 值。
将 M 从 0 变化到 30 的每一个值所获得的平分运营时刻绘成曲线。
留意:你供给为算法 2.2 添加三个亟待两个参数的 sort() 方法以使
Insertion.sort(a, lo, hi) 将子数组 a[lo…hi] 排序。

J.
Rotate and skew

解答

切换来插入排序的落到实处相比简单,在类内添加一个成员变量 M,在 Sort
方法里添加如下代码:

protected void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
{
    if (hi <= lo)                   // 别越界
        return;
    if (hi - lo <= this.M)
    {
        // 调用插入排序
        for (int i = lo; i <= hi; i++)
            for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                Exch(a, k, k - 1);
        return;
    }
    int j = Partition(a, lo, hi);
    Sort(a, lo, j - 1);
    Sort(a, j + 1, hi);
}

上边放上实验结果:
N=1000
美高梅开户网址 48
N=10000
美高梅开户网址 49
N=100000
美高梅开户网址 50
N=1000000
美高梅开户网址 51

低于 8 的 M 值会比较合适。

难点疏忽

代码

此地运用了 Background Worker 来预防程序失去响应,更加多音讯方可看
「另请参阅」部分。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Quick;

namespace _2._3._25
{
    public partial class Form2 : Form
    {
        /// <summary>
        /// 测试数组大小。
        /// </summary>
        public int N = 100;

        public Form2(int n)
        {
            InitializeComponent();
            this.N = n;
        }

        /// <summary>
        /// 启动页面时启动后台测试。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form2_Shown(object sender, EventArgs e)
        {
            this.Text = "正在绘图";
            this.backgroundWorker1.RunWorkerAsync();
        }

        /// <summary>
        /// 后台测试方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            QuickSortInsertion quickSortInsertion = new QuickSortInsertion();
            double[] timeRecord = new double[31];
            for (int i = 0; i <= 30; i++)
            {
                worker.ReportProgress(i * 3);
                quickSortInsertion.M = i;
                int[] data = SortCompare.GetRandomArrayInt(this.N);
                timeRecord[i] = SortCompare.Time(quickSortInsertion, data);
            }
            e.Result = timeRecord;
        }

        /// <summary>
        /// 更新后台进度方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.Text = "正在绘图,已完成 " + e.ProgressPercentage + " %";
        }

        /// <summary>
        /// 测试完毕,进行绘图的方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            double[] result = e.Result as double[];

            Graphics graphics = this.CreateGraphics();

            // 获得绘图区矩形。
            RectangleF rect = this.ClientRectangle;
            float unitX = rect.Width / 10;
            float unitY = rect.Width / 10;

            // 添加 10% 边距作为文字区域。
            RectangleF center = new RectangleF
                (rect.X + unitX, rect.Y + unitY,
                rect.Width - 2 * unitX, rect.Height - 2 * unitY);

            // 绘制坐标系。
            graphics.DrawLine(Pens.Black, center.Left, center.Top, center.Left, center.Bottom);
            graphics.DrawLine(Pens.Black, center.Left, center.Bottom, center.Right, center.Bottom);
            graphics.DrawString(result.Max().ToString(), this.Font, Brushes.Black, rect.Location);
            graphics.DrawString(result.Length.ToString(), this.Font, Brushes.Black, center.Right, center.Bottom);
            graphics.DrawString("0", this.Font, Brushes.Black, rect.Left, center.Bottom);

            // 初始化点。
            PointF[] bluePoints = new PointF[result.Length];
            unitX = center.Width / result.Length;
            unitY = center.Height / (float)result.Max();

            for (int i = 0; i < result.Length; i++)
            {
                bluePoints[i] = new PointF(center.Left + unitX * (i + 1), center.Bottom - (float)(result[i] * unitY) - 10);
            }

            // 绘制点。
            for (int i = 0; i < result.Length; i++)
            {
                graphics.FillEllipse(Brushes.Blue, new RectangleF(bluePoints[i], new Size(10, 10)));
            }

            graphics.Dispose();

            this.Text = "绘图结果";
            int min = 0;
            for (int i = 0; i < result.Length; i++)
            {
                if (result[i] < result[min])
                    min = i;
            }
            string report = "M " + min + "\r\ntime " + result[min];
            MessageBox.Show(report, "最优结果");
        }
    }
}

高速排序类

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._25
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortInsertion : BaseSort
    {
        /// <summary>
        /// 切换到插入排序的阈值。
        /// </summary>
        public int M { get; set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortInsertion()
        {
            this.M = 8;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        protected void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;
            if (hi - lo <= this.M)
            {
                // 调用插入排序
                for (int i = lo; i <= hi; i++)
                    for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                        Exch(a, k, k - 1);
                return;
            }
            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

  windows系统里面有个“画图”工具,相信我们肯定不会目生。但中间没有转动任意$x$角度的功用,唯有“扭曲”的职能。如逆时针旋转$28^\circ$,大家发现可以先对$x$轴扭曲$-14^\circ$,再对$y$轴扭曲$25^\circ$,再对$x$轴扭曲$-14^\circ$,就马到成功辣!问给定角度$x$,输出三回扭曲的角度。

另请参阅

BackgroundWorker 组件 | Microsoft
Docs
Quick

基本思路

2.3.26

  好多同桌恐怕一开端先取个基向量,比如$\vec
b=(0,1)$,然后想$x$轴扭曲$-14^\circ$应该是$\vec{b’}=(tan14^\circ,1)$,$y$轴再反过来$25^\circ$应该是$\vec{b”}=(tan14^\circ,
1-tan25^\circ)$,再反过来一下……再$arc
tan$一下……咦?怎么出来的不是$28^\circ$了?

题目

子数组的大小。
编辑三个程序,在神速排序处理大小为 N 的数组的经过中,
当子数组的大小小于 M 时,排序方法供给切换为插入排序。
将子数组的大大小小绘制成直方图。
用 N=10^5,M=10、20 和 50 测试你的顺序。

  Naive。假使是如此那还叫扭曲吗?那叫拉伸!你倒是把$x$乘进去啊!把$y$乘进去啊!

解答

在切换为插入排序以前先记下一下脚下子数组的轻重。
在排序类内添加二个大大小小为 M+1 的数组,用于记录每一个数组大小出现的次数。

结果如下(N=一千00):
M=10
美高梅开户网址 52
M=20
美高梅开户网址 53
M=50
美高梅开户网址 54

  • 率先考虑基向量$\vec
    a=(1,0)$。设旋转角度$\theta$。先水平扭曲任意角度,不变。垂直扭曲$\varphi$,$\vec{a’}=(1,
    tan\varphi)$,再水平扭曲3回得$\vec{a”}=(1-tan(x),
    tan\varphi)$,和$tan\theta$解一下发现$x=\frac\theta2$,于是知道了水平扭曲角度。
  • 正解(1):考虑向量$\vec
    x=\begin{bmatrix}x\\y\end{bmatrix}$,水平扭曲矩阵$A=\begin{bmatrix}1&tan(-\frac\theta2)\\0&1\end{bmatrix}$, ($A\vec
    x=\begin{bmatrix}x+ytan(-\frac\theta2)\\y\end{bmatrix}$,看不懂的学线代去)

    • 四个扭曲矩阵相乘应该是$M=ABA$,在这之中我们渴求的是中等的垂直扭曲矩阵$B$。
    • 已知旋转矩阵$M=\begin{bmatrix}cos\theta&-sin\theta\\sin\theta&cos\theta\end{bmatrix}$,解得$B=\begin{bmatrix}1&0\\sin\theta&1\end{bmatrix}$。
    • 而是大家的垂直扭曲矩阵应该要长大$B=\begin{bmatrix}1&0\\tan\varphi&1\end{bmatrix}$的样子。
    • 由此大家须要用$tan$正切值去模拟$sin$正弦值(本来正是供给用扭曲模拟旋转)。
  • 正解(2):再考虑基向量$\vec
    b=(0,1)$。

    • 联立化简
      $\left\{\begin{matrix}\begin{aligned}&x_1=x_0+y_0*tan(-\frac\theta2)\\&y_1=y_0+x_1*tan(\varphi)\\&x_2=x_1+y_1*tan(-\frac\theta2)\end{aligned}\end{matrix}\right.$,$\left\{\begin{matrix}\begin{aligned}&x_0=0,\
      y_0=1\\&tan(-\theta)=\frac{x_2}{y_1}\end{aligned}\end{matrix}\right.$
       得
       $\displaystyle\frac{tan(\frac\theta2)[sec(\theta)tan(\varphi)-tan(\theta)]}{tan(\frac\theta2)tan(\varphi)-1}=0$

    • $sec(\theta)tan(\varphi)=tan(\theta)$ 得笔直扭曲角度
      $\varphi=tan^{-1}sin(\theta)$
    • 啥?你还要$+k\pi$?你还要分母不为$0$?$\theta$不为$0$?那都要笔者解,你咋不上天呢。
  • 意识题目对精度供给不高,反正切取个整即可。也得以一向打个垂直扭曲角度的表。
  • 更加多请参见
     Matrix67—线性代数的妙用:怎么样在Windows画图软件中贯彻28度旋转?
代码
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Quick;

namespace _2._3._26
{
    public partial class Form2 : Form
    {
        private int M;
        private int N;

        public Form2(int m, int n)
        {
            InitializeComponent();
            this.M = m;
            this.N = n;
        }

        /// <summary>
        /// 启动页面时启动后台测试。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form2_Shown(object sender, EventArgs e)
        {
            this.Text = "正在绘图";
            this.backgroundWorker1.RunWorkerAsync();
        }

        /// <summary>
        /// 后台测试方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            QuickSortInsertion quickSortInsertion = new QuickSortInsertion
            {
                M = this.M
            };
            int[] data = SortCompare.GetRandomArrayInt(this.N);
            worker.ReportProgress(50);
            quickSortInsertion.Sort(data);
            e.Result = quickSortInsertion.Counts;
        }

        /// <summary>
        /// 更新后台进度方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.Text = "正在绘图,已完成 " + e.ProgressPercentage + " %";
        }

        /// <summary>
        /// 测试完毕,进行绘图的方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            //新建画布
            Graphics graphics = this.CreateGraphics();

            //翻转默认坐标系
            graphics.TranslateTransform(0, this.Height);
            graphics.ScaleTransform(1, -1);

            int[] countsOrigin = e.Result as int[];
            int[] counts = new int[countsOrigin.Length - 1];
            for (int i = 0; i < counts.Length; i++)
            {
                counts[i] = countsOrigin[i + 1];
            }

            //获取最大值
            double max = counts.Max();
            //计算间距
            double unit = this.Width / (3.0 * counts.Length + 1);
            double marginTop = 100;
            //计算直方图的矩形
            Rectangle[] rects = new Rectangle[counts.Length];
            rects[0].X = (int)unit;
            rects[0].Y = 0;
            rects[0].Width = (int)(2 * unit);
            rects[0].Height = (int)((counts[0] / max) * (this.Height - marginTop));
            for (int i = 1; i < counts.Length; ++i)
            {
                rects[i].X = (int)(rects[i - 1].X + 3 * unit);
                rects[i].Y = 0;
                rects[i].Width = (int)(2 * unit);
                rects[i].Height = (int)((counts[i] / (max + 1)) * (this.Height - marginTop));
            }

            //绘图
            graphics.FillRectangles(Brushes.Black, rects);

            //释放资源
            graphics.Dispose();

            this.Text = "绘图结果,最高次数:" + counts.Max() + " 最低次数:" + counts.Min();
        }
    }
}

不慢排序类

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._26
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortInsertion : BaseSort
    {
        /// <summary>
        /// 切换到插入排序的阈值。
        /// </summary>
        public int M { get; set; }

        public int[] Counts;

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortInsertion()
        {
            this.M = 8;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            this.Counts = new int[this.M + 1];
            for (int i = 0; i < this.M + 1; i++)
            {
                this.Counts[i] = 0;
            }
            Shuffle(a);
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        protected void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;
            if (hi - lo <= this.M)
            {
                this.Counts[hi - lo]++;
                // 调用插入排序
                for (int i = lo; i <= hi; i++)
                    for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                        Exch(a, k, k - 1);
                return;
            }
            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

参考代码

另请参阅

BackgroundWorker 组件 | Microsoft
Docs
Quick

对基向量$\vec
b=(0,1)$的模拟:

2.3.27

 1 #include <stdio.h>
 2 #include <math.h>
 3 const double PI = acos(-1.L);
 4 int main() {
 5     double x = 0.0, y = 1.0;
 6     x = x + tan(-14.*PI/180.) * y;
 7     y = y + tan(25.*PI/180.) * x;///注意这里是tan25模拟sin28,若这里直接用sin28则最后atan回来的结果是28.0整
 8     x = x + tan(-14.*PI/180.) * y;
 9     printf("%f\n", atan(x/y)*180./PI);
10     return 0;
11 }
题目

马虎小数组。
用试验相比较之下处理小数组的法门和演习 2.3.25 的处理形式的功能:
在高效排序中央直机关接忽略小数组,仅在飞速排序甘休后运营一次插入排序。
小心:能够因此那几个试验臆度出电脑的缓存大小,因为当数组大小超出缓存时那种办法的习性也许会骤降。

官方代码:

解答

尝试结果如下:
美高梅开户网址 55
P.S. 测试机上的缓存是 L1 128K,L2 512K,L3 4MB。

1 // http://scarky.com/widget/getiframe/PLNRB5QG/
2 #include <stdio.h>
3 int y[]={0,2,4,6,8,10,12,14,15,17,19,21,22,24,25,27,28,29,30,32,33,34,35};
4 int main(int i) {
5   while(~scanf("%d", &i))
6     i/=2, printf("%d %d %d\n", -i, i>0?y[i]:-y[-i], -i);
7   return 0;
8 }
代码

QuickSortIgnore

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._27
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortIgnore : BaseSort
    {
        /// <summary>
        /// 切换到插入排序的阈值。
        /// </summary>
        public int M { get; set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortIgnore()
        {
            this.M = 10;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Sort(a, 0, a.Length - 1);

            // 插入排序处理小数组
            for (int i = 0; i < a.Length; i++)
                for (int j = i; j > 0 && Less(a[j], a[j - 1]); j--)
                    Exch(a, j, j - 1);

            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        protected void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;
            if (hi - lo <= this.M)
            {
                return;     // 直接忽略
            }
            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

QuickSortInsertion

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._27
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortInsertion : BaseSort
    {
        /// <summary>
        /// 切换到插入排序的阈值。
        /// </summary>
        public int M { get; set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortInsertion()
        {
            this.M = 10;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        protected void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;
            if (hi - lo <= this.M)
            {
                // 调用插入排序
                for (int i = lo; i <= hi; i++)
                    for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                        Exch(a, k, k - 1);
                return;
            }
            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

测试用例

using System;
using Quick;

namespace _2._3._27
{
    /*
     * 2.3.27
     * 
     * 忽略小数组。
     * 用实验对比以下处理小数组的方法和练习 2.3.25 的处理方法的效果:
     * 在快速排序中直接忽略小数组,仅在快速排序结束后运行一次插入排序。
     * 注意:
     * 可以通过这些实验估计出电脑的缓存大小,
     * 因为当数组大小超出缓存时这种方法的性能可能会下降。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSortInsertion insertion = new QuickSortInsertion();
            QuickSortIgnore ignore = new QuickSortIgnore();
            int arraySize = 20000;                          // 初始数组大小。
            const int mSteps = 1;                           // M 值的递增次数。
            const int trialTimes = 4;                       // 每次实验的重复次数。
            const int trialLevel = 10;                      // 双倍递增的次数。

            Console.WriteLine("M\tn\t\tignore\tinsert\tratio");
            for (int i = 0; i < mSteps; i++)
            {
                int array = arraySize;
                for (int j = 0; j < trialLevel; j++)
                {
                    double timeIgnore = 0;
                    double timeInsertion = 0;
                    for (int k = 0; k < trialTimes; k++)
                    {
                        int[] a = SortCompare.GetRandomArrayInt(array);
                        int[] b = new int[a.Length];
                        a.CopyTo(b, 0);
                        timeInsertion += SortCompare.Time(insertion, b);
                        timeIgnore += SortCompare.Time(ignore, a);

                    }
                    timeIgnore /= trialTimes;
                    timeInsertion /= trialTimes;
                    if (arraySize < 10000000)
                        Console.WriteLine(ignore.M + "\t" + array + "\t\t" + timeIgnore + "\t" + timeInsertion + "\t" + timeIgnore / timeInsertion);
                    else
                        Console.WriteLine(ignore.M + "\t" + array + "\t" + timeIgnore + "\t" + timeInsertion + "\t" + timeIgnore / timeInsertion);
                    array *= 2;
                }
                ignore.M++;
            }
        }
    }
}

非官方代码:

另请参阅

Quick

1 #include <stdio.h>
2 #include <math.h>
3 const double PI = acos(-1.L);
4 int main() {
5     int x;
6     while(~scanf("%d", &x))
7         printf("%d %.f %d\n", -x/2, round(atan(sin(x*PI/180.))*180./PI), -x/2);
8     return 0;
9 }

2.3.28

 

题目

递归深度。
用经验性的钻研估摸切换阈值为 M 的短平快排序在将大小为 N
的不重复数组排序时的平分递归深度,
其中 M=10、20 和 50,N=10^3、10^4、10^5 和 10^6。

 

解答

对 Sort 方法做修改,添加三个薄薄传递的 depth 参数,每加一层 depth
就加一,停止时取左右较大的 depth 再次来到。

protected int Sort<T>(T[] a, int lo, int hi, int depth) where T: IComparable<T>
{
    if (hi <= lo)                   // 别越界
        return depth;
    if (hi - lo <= this.M)
    {
        // 调用插入排序
        for (int i = lo; i <= hi; i++)
            for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                Exch(a, k, k - 1);
        return depth;
    }
    int j = Partition(a, lo, hi);
    int left = Sort(a, lo, j - 1, depth + 1);
    int right = Sort(a, j + 1, hi, depth + 1);
    return Less(left, right) ? right : left;
}

测试结果
美高梅开户网址 56

——原创by
BlackStorm,转发请表明出处。

代码
using System;
using System.Diagnostics;
using Quick;

namespace _2._3._28
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortInsertion : BaseSort
    {
        /// <summary>
        /// 切换到插入排序的阈值。
        /// </summary>
        public int M { get; set; }

        /// <summary>
        /// 上一次排序的最大递归深度。
        /// </summary>
        public int Depth { get; private set; }

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortInsertion()
        {
            this.M = 10;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <returns>递归深度。</returns>
        public override void Sort<T>(T[] a)
        {
            Shuffle(a);
            this.Depth = Sort(a, 0, a.Length - 1, 0);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        protected int Sort<T>(T[] a, int lo, int hi, int depth) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return depth;
            if (hi - lo <= this.M)
            {
                // 调用插入排序
                for (int i = lo; i <= hi; i++)
                    for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                        Exch(a, k, k - 1);
                return depth;
            }
            int j = Partition(a, lo, hi);
            int left = Sort(a, lo, j - 1, depth + 1);
            int right = Sort(a, j + 1, hi, depth + 1);
            return Less(left, right) ? right : left;
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        private void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}

测试用例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _2._3._28
{
    /*
     * 2.3.28
     * 
     * 递归深度。
     * 用经验性的研究估计切换阈值为 M 的快速排序
     * 在将大小为 N 的不重复数组排序时的平均递归深度,
     * 其中 M=10、20 和 50,N=10^3、10^4、10^5 和 10^6。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("M\tN\tDepth");
            Trial(10);
            Trial(20);
            Trial(50);
        }

        /// <summary>
        /// 进行一次测试。
        /// </summary>
        /// <param name="m">要使用的阈值</param>
        static void Trial(int m)
        {
            QuickSortInsertion sort = new QuickSortInsertion();
            int trialTime = 5;

            // 由于排序前有 Shuffle,因此直接输入有序数组。
            // M=10
            sort.M = m;
            int totalDepth = 0;
            for (int N = 1000; N < 10000000; N *= 10)
            {
                for (int i = 0; i < trialTime; i++)
                {
                    int[] a = new int[N];
                    for (int j = 0; j < N; j++)
                    {
                        a[j] = j;
                    }
                    sort.Sort(a);
                    totalDepth += sort.Depth;
                }
                Console.WriteLine(sort.M + "\t" + N + "\t" + totalDepth / trialTime);
            }
        }
    }
}

正文地址:http://www.cnblogs.com/BlackStorm/p/5380872.html

另请参阅

Quick

2.3.29

题目

随机化。
用经验性的商量相比较随机挑选切分成分和正文所述的一始发就将数组随机化这两种政策的成效。
在子数组大小为 M 时举行切换,
将大小为 N 的不重复数组排序,在那之中 M=十 、20 和 50,N=10^叁 、10^四 、10^5 和
10^6。

解答

在快排类内部添加三个即兴数爆发器,每回随机取枢轴并交流至第③人展开切分。

private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
{
    int i = lo, j = hi + 1;
    int pivot = this.RandomGenerator.Next(hi - lo) + lo;
    Exch(a, pivot, lo);
    T v = a[lo];
    while (true)
    {
        while (Less(a[++i], v))
            if (i == hi)
                break;
        while (Less(v, a[--j]))
            if (j == lo)
                break;
        if (i >= j)
            break;
        Exch(a, i, j);
    }
    Exch(a, lo, j);
    return j;
}

测试结果:
美高梅开户网址 57

代码

运用随机枢轴的快排

using System;
using System.Diagnostics;
using Quick;

namespace _2._3._29
{
    /// <summary>
    /// 快速排序类。
    /// </summary>
    public class QuickSortRandomPivot : BaseSort
    {
        /// <summary>
        /// 切换到插入排序的阈值。
        /// </summary>
        public int M { get; set; }

        /// <summary>
        /// 随机数发生器。
        /// </summary>
        private readonly Random RandomGenerator = new Random();

        /// <summary>
        /// 默认构造函数。
        /// </summary>
        public QuickSortRandomPivot()
        {
            this.M = 10;
        }

        /// <summary>
        /// 用快速排序对数组 a 进行升序排序。
        /// </summary>
        /// <typeparam name="T">需要排序的类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        public override void Sort<T>(T[] a)
        {
            Sort(a, 0, a.Length - 1);
            Debug.Assert(IsSorted(a));
        }

        /// <summary>
        /// 用快速排序对数组 a 的 lo ~ hi 范围排序。
        /// </summary>
        /// <typeparam name="T">需要排序的数组类型。</typeparam>
        /// <param name="a">需要排序的数组。</param>
        /// <param name="lo">排序范围的起始下标。</param>
        /// <param name="hi">排序范围的结束下标。</param>
        protected void Sort<T>(T[] a, int lo, int hi) where T: IComparable<T>
        {
            if (hi <= lo)                   // 别越界
                return;
            if (hi - lo <= this.M)
            {
                // 调用插入排序
                for (int i = lo; i <= hi; i++)
                    for (int k = i; k > lo && Less(a[k], a[k - 1]); k--)
                        Exch(a, k, k - 1);
                return;
            }
            int j = Partition(a, lo, hi);
            Sort(a, lo, j - 1);
            Sort(a, j + 1, hi);
        }

        /// <summary>
        /// 对数组进行切分,返回枢轴位置。
        /// </summary>
        /// <typeparam name="T">需要切分的数组类型。</typeparam>
        /// <param name="a">需要切分的数组。</param>
        /// <param name="lo">切分的起始点。</param>
        /// <param name="hi">切分的末尾点。</param>
        /// <returns>枢轴下标。</returns>
        private int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
        {
            int i = lo, j = hi + 1;
            int pivot = this.RandomGenerator.Next(hi - lo) + lo;
            Exch(a, pivot, lo);
            T v = a[lo];
            while (true)
            {
                while (Less(a[++i], v))
                    if (i == hi)
                        break;
                while (Less(v, a[--j]))
                    if (j == lo)
                        break;
                if (i >= j)
                    break;
                Exch(a, i, j);
            }
            Exch(a, lo, j);
            return j;
        }
    }
}

测试用例

using System;
using Quick;

namespace _2._3._29
{
    /*
     * 2.3.29
     * 
     * 随机化。
     * 用经验性的研究对比随机选择切分元素和正文所述的一开始就将数组随机化这两种策略的效果。
     * 在子数组大小为 M 时进行切换,将大小为 N 的不重复数组排序,
     * 其中 M=10、20 和 50,N=10^3、10^4、10^5 和 10^6。
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("M\tN\tshuffle\trandom\tshuffle/random");
            Trial(10);
            Trial(20);
            Trial(50);
        }

        /// <summary>
        /// 进行一次测试。
        /// </summary>
        /// <param name="m">要使用的阈值</param>
        static void Trial(int m)
        {
            QuickSortInsertion withShuffle = new QuickSortInsertion();
            QuickSortRandomPivot randomPivot = new QuickSortRandomPivot();
            int trialTime = 5;

            // M=10
            withShuffle.M = m;
            randomPivot.M = m;
            double timeShuffle = 0;
            double timeRandomPivot = 0;
            for (int N = 1000; N < 10000000; N *= 10)
            {
                for (int i = 0; i < trialTime; i++)
                {
                    int[] a = new int[N];
                    int[] b = new int[N];
                    for (int j = 0; j < N; j++)
                    {
                        a[j] = j;
                    }
                    Shuffle(a);
                    a.CopyTo(b, 0);
                    timeShuffle += SortCompare.Time(withShuffle, a);
                    timeRandomPivot += SortCompare.Time(randomPivot, b);
                }
                timeShuffle /= trialTime;
                timeRandomPivot /= trialTime;
                Console.WriteLine(withShuffle.M + "\t" + N + "\t" + timeShuffle + "\t" + timeRandomPivot + "\t" + timeShuffle / timeRandomPivot);
            }
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <typeparam name="T">需要打乱的数组类型。</typeparam>
        /// <param name="a">需要打乱的数组。</param>
        static void Shuffle<T>(T[] a)
        {
            Random random = new Random();
            for (int i = 0; i < a.Length; i++)
            {
                int r = i + random.Next(a.Length - i);
                T temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}
另请参阅

Quick

2.3.30

题目

最为气象。
用起来随机化和非开头随机化的敏捷排序测试练习 2.1.35 和练习 2.1.36
中描述的巨型非随机数组。
在将那么些大数组排序时,乱序对飞速排序的属性有啥影响?

解答

结果如下,在 N=四千000 时,随机选取枢轴会比事先打乱快一点。
美高梅开户网址 58

代码
using System;
using Quick;

namespace _2._3._30
{
    /*
     * 2.3.30
     * 
     * 极端情况。
     * 用初始随机化和非初始随机化的快速排序测试练习 2.1.35 和练习 2.1.36 中描述的大型非随机数组。
     * 在将这些大数组排序时,乱序对快速排序的性能有何影响?
     * 
     */
    class Program
    {
        static void Main(string[] args)
        {
            QuickSortInsertion insertionSort = new QuickSortInsertion();
            QuickSortRandomPivot randomSort = new QuickSortRandomPivot();
            int n = 5000000;

            // 高斯分布(正态分布)
            double[] arrayInsertion = SortCompare.GetNormalDistributionArray(n);
            double[] arraySelection = new double[n];

            arrayInsertion.CopyTo(arraySelection, 0);

            Console.WriteLine("Normal Distribution:");
            Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion));
            Console.WriteLine("Random Pivot: " + SortCompare.Time(randomSort, arraySelection));
            Console.WriteLine();

            // 泊松分布
            arrayInsertion = SortCompare.GetPossionDistributionArray(n);
            arrayInsertion.CopyTo(arraySelection, 0);

            Console.WriteLine("Poission Distribution:");
            Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion));
            Console.WriteLine("Random Pivot: " + SortCompare.Time(randomSort, arraySelection));
            Console.WriteLine();

            // 几何分布
            arrayInsertion = SortCompare.GetGeometricDistributionArray(n, 0.3);
            arrayInsertion.CopyTo(arraySelection, 0);

            Console.WriteLine("Geometric Distribution:");
            Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion));
            Console.WriteLine("Random Pivot: " + SortCompare.Time(randomSort, arraySelection));
            Console.WriteLine();

            // 离散分布
            arrayInsertion = SortCompare.GetDiscretDistributionArray(n, new double[] { 0.1, 0.2, 0.3, 0.1, 0.1, 0.1, 0.1 });
            arrayInsertion.CopyTo(arraySelection, 0);

            Console.WriteLine("Discret Distribution:");
            Console.WriteLine("Insertion: " + SortCompare.Time(insertionSort, arrayInsertion));
            Console.WriteLine("Random Pivot: " + SortCompare.Time(randomSort, arraySelection));
            Console.WriteLine();

            // 一半是 0 一半是 1
            int[] arrayNormalInsertion = HalfZeroHalfOne(n);
            int[] arrayRandomPivot = new int[n];
            arrayNormalInsertion.CopyTo(arrayRandomPivot, 0);

            Console.WriteLine("half 0 and half 1");
            Console.WriteLine("Insertion:" + SortCompare.Time(insertionSort, arrayNormalInsertion));
            Console.WriteLine("Random Pivot:" + SortCompare.Time(randomSort, arrayRandomPivot));
            Console.WriteLine();

            // 一半是 0, 1/4 是 1, 1/8 是 2……
            arrayNormalInsertion = HalfAndHalf(n);
            arrayNormalInsertion.CopyTo(arrayRandomPivot, 0);

            Console.WriteLine("half and half and half ...");
            Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, arrayNormalInsertion));
            Console.WriteLine("Random Pivot:" + SortCompare.Time(randomSort, arrayRandomPivot));
            Console.WriteLine();

            // 一半是 0,一半是随机 int 值
            arrayNormalInsertion = HalfZeroHalfRandom(n);
            arrayNormalInsertion.CopyTo(arrayRandomPivot, 0);

            Console.WriteLine("half 0 half random");
            Console.WriteLine("Insertion Sort:" + SortCompare.Time(insertionSort, arrayNormalInsertion));
            Console.WriteLine("Random Pivot:" + SortCompare.Time(randomSort, arrayRandomPivot));
        }

        /// <summary>
        /// 获取一半是 0 一半是 1 的随机 <see cref="int"/> 数组。
        /// </summary>
        /// <param name="n">数组大小。</param>
        /// <returns>一半是 0 一半是 1 的 <see cref="int"/>数组。</returns>
        static int[] HalfZeroHalfOne(int n)
        {
            int[] result = new int[n];
            Random random = new Random();
            for (int i = 0; i < n; i++)
            {
                if (random.NextDouble() >= 0.5)
                {
                    result[i] = 0;
                }
                else
                {
                    result[i] = 1;
                }
            }
            return result;
        }

        /// <summary>
        /// 生成 1/2 为 0, 1/4 为 1, 1/8 为 2 …… 的 <see cref="int"/> 数组。
        /// </summary>
        /// <param name="n">数组长度。</param>
        /// <returns>1/2 为 0, 1/4 为 1, 1/8 为 2 …… 的 <see cref="int"/> 数组。</returns>
        static int[] HalfAndHalf(int n)
        {
            int[] array = new int[n];
            HalfIt(0, 0, n / 2, array);
            Shuffle(array);
            return array;
        }

        /// <summary>
        /// 递归生成 1/2 为 0, 1/4 为 1, 1/8 为 2 …… 的 <see cref="int"/> 数组。
        /// </summary>
        /// <param name="start">填充起点。</param>
        /// <param name="number">起始编号。</param>
        /// <param name="length">填充长度</param>
        /// <param name="array">用于填充的数组。</param>
        /// <returns>一个 <see cref="int"/> 数组。</returns>
        static int[] HalfIt(int start, int number, int length, int[] array)
        {
            if (length == 0)
                return array;

            for (int i = 0; i < length; i++)
            {
                array[start + i] = number;
            }

            return HalfIt(start + length, number + 1, length / 2, array);
        }

        /// <summary>
        /// 生成一半是 0 一半是随机整数的 <see cref="int"/> 数组。
        /// </summary>
        /// <param name="n">数组大小。</param>
        /// <returns>生成一半是 0 一半是随机整数的 <see cref="int"/> 数组。</returns>
        static int[] HalfZeroHalfRandom(int n)
        {
            int[] array = new int[n];
            Random random = new Random();
            for (int i = 0; i < n / 2; i++)
            {
                array[i] = 0;
            }

            for (int i = n / 2; i < n; i++)
            {
                array[i] = random.Next();
            }

            Shuffle(array);

            return array;
        }

        /// <summary>
        /// 打乱数组。
        /// </summary>
        /// <param name="a">需要打乱的数组。</param>
        static void Shuffle(int[] a)
        {
            int N = a.Length;
            Random random = new Random();
            for (int i = 0; i < N; i++)
            {
                int r = i + random.Next(N - i);// 等于StdRandom.uniform(N-i)
                int temp = a[i];
                a[i] = a[r];
                a[r] = temp;
            }
        }
    }
}
另请参阅

Quick

2.3.31

题目

运作时刻直方图。
编写3个先后,接受命令行参数 N 和 T,
用高速排序对大小为 N 的人身自由浮点数数组举行 T 次排序,
并将拥有运转时刻绘制成直方图。
令 N=10^3、10^4、10^5 和 10^6,
为了使曲线更平整,T 值越大越好。
其一演练最根本的地点在于找到合适的比例绘制出实验结果。

解答

以下有所结果 T=70
N=1000
美高梅开户网址 59
N=10000
美高梅开户网址 60
N=100000
美高梅开户网址 61
N=1000000
美高梅开户网址 62

代码
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Quick;

namespace _2._3._31
{
    public partial class Form2 : Form
    {
        private int N;
        private int T;

        public Form2(int n, int t)
        {
            InitializeComponent();
            this.N = n;
            this.T = t;
        }

        /// <summary>
        /// 启动页面时启动后台测试。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form2_Shown(object sender, EventArgs e)
        {
            this.Text = "正在绘图";
            this.backgroundWorker1.RunWorkerAsync();
        }

        /// <summary>
        /// 后台测试方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            QuickSort quick = new QuickSort();

            double percentPerTrial = 100.0 / this.T;
            double[] totalTime = new double[this.T];
            for (int i = 0; i < this.T; i++)
            {
                double[] data = SortCompare.GetRandomArrayDouble(this.N);
                totalTime[i] = SortCompare.Time(quick, data);
                worker.ReportProgress((int)(percentPerTrial * i));
            }

            e.Result = totalTime;
        }

        /// <summary>
        /// 更新后台进度方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.Text = "正在测试,已完成 " + e.ProgressPercentage + " %";
        }

        /// <summary>
        /// 测试完毕,进行绘图的方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                MessageBox.Show(e.Error.Message);
            }
            //新建画布
            Graphics graphics = this.CreateGraphics();

            //翻转默认坐标系
            graphics.TranslateTransform(0, this.Height);
            graphics.ScaleTransform(1, -1);

            double[] counts = e.Result as double[];

            //获取最大值
            double max = counts.Max();
            //计算间距
            double unit = this.Width / (3.0 * counts.Length + 1);
            double marginTop = 100;
            //计算直方图的矩形
            Rectangle[] rects = new Rectangle[counts.Length];
            rects[0].X = (int)unit;
            rects[0].Y = 0;
            rects[0].Width = (int)(2 * unit);
            rects[0].Height = (int)((counts[0] / max) * (this.Height - marginTop));
            for (int i = 1; i < counts.Length; ++i)
            {
                rects[i].X = (int)(rects[i - 1].X + 3 * unit);
                rects[i].Y = 0;
                rects[i].Width = (int)(2 * unit);
                rects[i].Height = (int)((counts[i] / (max + 1)) * (this.Height - marginTop));
            }

            //绘图
            graphics.FillRectangles(Brushes.Black, rects);

            //释放资源
            graphics.Dispose();

            this.Text = "绘图结果,最高耗时:" + counts.Max() + " 最低耗时:" + counts.Min();
        }
    }
}
另请参阅

BackgroundWorker 组件 | Microsoft
Docs
Quick

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图