Fork me on GitHub
Sky of war

Beyond the spectacle of the sky


  • 首页

  • 关于

  • 标签60

  • 分类7

  • 归档63

  • Game2048

  • 搜索

『CodeVS 1369』xth砍树

发表于 2018-03-18 | 更新于 2018-09-15 | 分类于 题解

传送门

题目

题目描述 Description
在一个凉爽的夏夜,xth 和 rabbit 来到花园里砍树。为啥米要砍树呢?是这样滴,
小菜儿的儿子窄森要出生了。Xth这个做伯伯的自然要做点什么。于是他决定带着
rabbit 去收集一些木材,给窄森做一个婴儿车……(xth 早就梦想着要天天打菜儿
他儿窄森的小 pp,到时候在婴儿车里安装一个电子遥控手臂,轻轻一按,啪啪
啪……“乌卡卡——”xth 邪恶滴笑了,“不要告诉 rabbit,她会说我缺德的……”
xth 如是说)。
花园里共有$n$棵树。为了花园的整体形象,rabbit 要求 xth只能在$m$个区域砍伐,我
们可以将这$m$个区域看成$m$个区间,树的间距相等,都是$1$,我们将每个区间设为
$[x, y]$。那么长度为$k$的区间中就有$k$棵树。树木的高度不等。现在 xth 想测量一下,
每个区间树木砍伐后所得的木材量是多少,而且每次测量后他都会砍下标号为
$(x+y)/2$的那棵作为纪念。以方便他安排人手。(同一个区间的树木可以重复砍伐,我们认
为被砍过的树木高度为$0$)
每棵树的木材量=树的高度$* 3.14$(注意是$3.14$不是$\pi$)。

输入描述 Input Description
第一行,一个整数$n$。
第二行,共$n$个整数,表示每棵树的高度。
第三行,一个整数$m$,表示共$m$个区间。
以下$m$行,每个区间$[x, y]$的左右端点$x, y$。

输出描述 Output Description
共$m$行,每行一个数,表示每个区间的木材量。

结果精确到小数点后两位。

样例输入 Sample Input
5
1 2 3 4 5
2
1 4
2 4

样例输出 Sample Output
31.40
21.98

数据范围及提示 Data Size & Hint
对于30%的数据,有n ≤ 5000,m ≤ 5000;
对于100%的数据,有n ≤ 200000,m ≤ 200000;

Solution

这是我见过的废话最多的题目之一。。。。
树状数组或线段树均可以过,反正都是单点修改区间查询即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 200000 + 5
using namespace std;
int N, M;
int a[MAXN], c[MAXN];
inline int lowbit(int x)
{
return x & (-x);
}
inline void Modify(int pos, int val)
{
for (register int i = pos; i <= N; i += lowbit(i)) c[i] += val;
}
inline int query(int pos)
{
int ret = 0;
for (register int i = pos; i >= 1; i -= lowbit(i)) ret += c[i];
return ret;
}
int main(int argc, char **argv)
{
ios::sync_with_stdio(false);
cin >> N;
for (register int i = 1; i <= N; ++i)
{
cin >> a[i];
Modify(i, a[i]);
}
cin >> M;
while (M--)
{
int l, r;
cin >> l >> r;
printf("%.2lf\n",3.14 * (query(r) - query(l - 1)));
Modify((l + r) >> 1, -a[(l + r) >> 1]);
a[(l + r) >> 1] = 0;
}
return 0;
}

『HDU 1556』Color the ball

发表于 2018-03-17 | 更新于 2018-09-15 | 分类于 题解

传送门

题目描述

Problem Description
N个气球排成一排,从左到右依次编号为1,2,3….N.每次给定2个整数a b(a <= b),lele便为骑上他的“小飞鸽”牌电动车从气球a开始到气球b依次给每个气球涂一次颜色。但是N次以后lele已经忘记了第I个气球已经涂过几次颜色了,你能帮他算出每个气球被涂过几次颜色吗?

Input
每个测试实例第一行为一个整数N,(N <= 100000).接下来的N行,每行包括2个整数a b(1 <= a <= b <= N)。
当N = 0,输入结束。

Output
每个测试实例输出一行,包括N个整数,第I个数代表第I个气球总共被涂色的次数。

Sample Input
3
1 1
2 2
3 3
3
1 1
1 2
1 3
0

Sample Output
1 1 1
3 2 1

Solution

一道非常非常经典的题目,就是区间染色问题
一个线段树解决即可。
(突然发现以前写过这道题,但是这次写比以前快了若干毫秒。
代码风格差异不大吧。。。)
现在的代码:(没有注释)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define MAXN 100001
using namespace std;
struct Segt
{
int l, r, cnt;
};
class SegTree
{
public:
Segt Tree[MAXN * 4];
void Build(int ind, int l, int r)
{
Tree[ind].cnt = 0, Tree[ind].l = l, Tree[ind].r = r;
if (l >= r) return;
int mid = (l + r) >> 1;
Build(ind << 1, l, mid);
Build(ind << 1 | 1, mid + 1, r);
}
void Modify(int ind, int l, int r)
{
if (Tree[ind].l == l && Tree[ind].r == r)
{
Tree[ind].cnt++;
return;
}
if (Tree[ind].l == Tree[ind].r) return;
int mid = (Tree[ind].l + Tree[ind].r) >> 1;
if (mid < l)
{
Modify(ind << 1 | 1, l, r);
}
else
if (mid >= r)
{
Modify(ind << 1, l, r);
}
else
{
Modify(ind << 1, l, mid);
Modify(ind << 1 | 1, mid + 1, r);
}

}
int Calc(int ind, int l, int r)
{
if (Tree[ind].l == l && Tree[ind].r == r)
return Tree[ind].cnt;
if (Tree[ind].l == Tree[ind].r) return 0;
int mid = (Tree[ind].l + Tree[ind].r) >> 1;
if (mid >= r)
{
return Tree[ind].cnt + Calc(ind << 1, l, r);
}
else
if (mid < l)
{
return Tree[ind].cnt + Calc(ind << 1 | 1, l, r);
}
else
return Tree[ind].cnt + Calc(ind << 1, l, mid) + Calc(ind << 1 | 1, mid + 1, r);
}
};
SegTree A;
int main(int argc, char **argv)
{
ios::sync_with_stdio(false);
int N;
cin >> N;
while (N != 0)
{
A.Build(1, 1, N);
for (register int i = 1; i <= N; ++i)
{
int l, r;
cin >> l >> r;
A.Modify(1, l, r);
}
for (register int i = 1; i <= N; ++i)
{
cout << A.Calc(1, i, i);
i == N ? cout << endl : cout << ' ';
}
cin >> N;
}
return 0;
}

话说线段树空间一定要开满4倍。。。就因为这个于是就RE了一次(泪奔

这是以前的代码,注释还真挺多的,就是慢了(可能没有用位运算吧)(当时我要写给谁来着[黑人问号])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#define MAXN 100001
using namespace std;
struct SegTreeNode
{
int lc, rc, cnt;//cnt:被涂过的次数
} SegTree[MAXN * 4];
void Build(int root, int left, int right)
{
SegTree[root].cnt = 0, SegTree[root].lc = left, SegTree[root].rc = right;//初始化当前节点
if (left == right) return; //找到最小区间,退出递归
int mid = (left + right) / 2;//中间值mid,方便书写
Build(root * 2, left, mid);//递归初始化左子树
Build(root * 2 + 1, mid + 1, right);//递归初始化右子树
}
void Update(int root, int left, int right)//更新某一区间的值,即染色
{
if (SegTree[root].lc == left && SegTree[root].rc == right)//找到要更新的区间
{
SegTree[root].cnt++;
return;//更新该值,退出递归
}
if (SegTree[root].lc == SegTree[root].rc) return;//没有找到
int mid = (SegTree[root].lc + SegTree[root].rc) / 2;//注意不是(left + right) / 2
if (mid < left) Update(root * 2 + 1, left, right);//比当前最左边的范围还要小,去右子树找
else if (mid >= right) Update(root * 2, left, right);//比当前最右边的范围还要小,去左子树找
else {//中点在要查询区间的中间,两边都要找
Update(root * 2, left, mid);
Update(root * 2 + 1, mid + 1, right);
}
}
int Query(int root, int left, int right) //查询
{
if (SegTree[root].lc == left && SegTree[root].rc == right) return SegTree[root].cnt;//找到区间
if (SegTree[root].lc == SegTree[root].rc) return 0;//没有找到
int mid = (SegTree[root].lc + SegTree[root].rc) / 2;
if (mid >= right) return SegTree[root].cnt + Query(root * 2, left, right);//去左子树寻找
else if (mid < left) return SegTree[root].cnt + Query(root * 2 + 1, left, right);//去右子树寻找
else return SegTree[root].cnt + Query(root * 2, left, mid) + Query(root * 2 + 1, mid + 1, right);//中点在要查询区间的中间,两边都要找
}
int main()
{
ios::sync_with_stdio(false);
int N, A, B, sum;
cin >> N;
while (N)
{
Build(1, 1, N);
for (int i = 1; i <= N; i++)
{
int x, y;
cin >> x >> y;
Update(1, x, y);
}
for (int i = 1; i <= N; i++)
{
int ans = Query(1, i, i);
cout << ans;
if (i == N) cout << endl; else cout << " ";
}
cin >> N;
}
return 0;
}

线段统计 - 线段树懒标记

发表于 2018-03-17 | 更新于 2018-09-13 | 分类于 讲解

线段统计 - 线段树懒标记

Problem

题目描述
在数轴上进行一系列操作。每次操作有两种类型,一种是在线段[a,b]上涂上颜色,另一种将[a,b]上的颜色擦去。问经过一系列的操作后,有多少条单位线段[k,k+1]被涂上了颜色。

输入格式
第1行:2个整数n(0<=n<=60000)和m(1<=m<=60000)分别表示数轴长度和进行操作的次数。
接下来m行,每行3个整数i,a,b, 0 <=a<=b<=60000,若i=1表示给线段[a,b]上涂上颜色,若i=2表示将[a,b]上的颜色擦去。

输出格式
文件输出仅有一行为1个整数,表示有多少条单位线段[k,k+1]被涂上了颜色。

样例输入
10 5
1 2 8
2 3 6
1 1 10
2 4 7
1 1 5

样例输出
7

分析

最为经典的一道区间修改区间查询加懒标记的题了
懒标记的实质:推迟信息更新,避免无用操作
如果不加懒标记,线段树连暴力都不如。
对于每个非完全包含的区间,在修改和查询到的时候都要向下传标记。
比如此题,如果标记为全部有色,传下去儿子结点全部有色,全部无色亦然。
传完标记后需要将标记置为0表示儿子中有的有颜色有的无颜色。
因为建树方式不同,线段映射到点右端点需-1

Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
#define MAXN 1000005
using namespace std;
struct Segt
{
int l, r, colored, lazy;
//lazy=1表示该区间内全部染了色
};
class SegmentTree
{
public:
Segt Tree[MAXN * 4];
int sum;
void Build(int ind, int l, int r)
{
Tree[ind].l = l, Tree[ind].r = r;
Tree[ind].colored = 0, Tree[ind].lazy = 0;
if (l >= r) return;
int mid = (l + r) >> 1;
Build(ind << 1, l, mid);
Build(ind << 1 | 1, mid + 1, r);
}
void PD(int ind)
{
if (Tree[ind].lazy == -1)
{
Tree[ind << 1].lazy = Tree[ind << 1 | 1].lazy = -1;
Tree[ind << 1].colored = Tree[ind << 1 | 1].colored = 0;
Tree[ind].lazy = 0;
return;
}
if (Tree[ind].lazy == 1)
{
Tree[ind << 1].lazy = Tree[ind << 1 | 1].lazy = 1;
Tree[ind << 1].colored = Tree[ind << 1].r - Tree[ind << 1].l + 1;
Tree[ind << 1 | 1].colored = Tree[ind << 1 | 1].r - Tree[ind << 1 | 1].l + 1;
Tree[ind].lazy = 0;
return;
}
}
void PU(int ind)
{
Tree[ind].colored = Tree[ind << 1].colored + Tree[ind << 1 | 1].colored;
}
void Modify(int ind, int l, int r, int ope) //Ope:Operations:1->color,2->discolor
{
if (l >= r) return;
if (Tree[ind].l > r || Tree[ind].r < l) return;
if (Tree[ind].lazy == ope) return;
if (l <= Tree[ind].l && Tree[ind].r <= r)
{
Tree[ind].lazy = ope;
if (ope == -1 || ope == 0) Tree[ind].colored = 0;
else Tree[ind].colored = Tree[ind].r - Tree[ind].l + 1;
return;
}
PD(ind);
Modify(ind << 1, l, r, ope);
Modify(ind << 1 | 1, l, r, ope);
PU(ind);
}
};
SegmentTree st;
int N, M;
int main(int argc, char **argv)
{
ios::sync_with_stdio(false);
cin >> N >> M;
st.Build(1, 1, N);
for (register int i = 1; i <= M; ++i)
{
int ope, l, r;
cin >> ope >> l >> r;
if (ope == 1) st.Modify(1, l, r - 1, 1); //记得这边要是r - 1
else st.Modify(1, l, r - 1, -1);
}
cout << st.Tree[1].colored << endl;
return 0;
}

线性基

发表于 2018-03-12 | 更新于 2018-09-13 | 分类于 讲解

线性基

概念

设数集$T$的范围为$[1,2^n−1]$。
$T$的线性基是$T$的一个子集$A=\{a1,a2,a3,…,an\}$。
$A$中元素互相$xor$所形成的异或集合,等价于原数集$T$的元素互相$xor$形成的异或集合。
$A$可以看成是$T$的压缩。

性质

线性基有一些性质:

  • 线性基的异或集合中不存在$0$。也就是说,$A$是$T$中的极大线性无关组,同时$A⊆T$。
  • 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。这个与性质1其实是等价的。
  • 线性基的二进制最高位互不相同。
  • 如果线性基是满的,它的异或集合为$\{1,2,3…2^n-1\}$。
  • 线性基中元素互相异或,异或集合不变。

维护

插入

如果向线性基中插入数$x$,从高位到低位扫描它为1的二进制位。
扫描到第$i$位时,如果$a_i$不存在,就令$a_i=x$,否则$x=x⊗a_i$。
$x$的结局是,要么被扔进线性基,要么经过一系列操作过后,变成了$0$。

合并

将一个线性基暴力插入另一个线性基即可。

查询

最大值

从高位到低位扫描线性基。
如果异或后可以使得答案变大,就异或到答案中去。

最小值

最小值即为最低位上的线性基。

k小值

根据性质3。
我们要将线性基改造成每一位相互独立。
具体操作就是如果i<j,$a_j$的第$i$位是$1$,就将$a_j$异或上$a_i$。
经过一系列操作之后,对于二进制的某一位$i$。只有$a_i$的这一位是$1$,其他都是$0$。
所以查询的时候将$k$二进制拆分,对于$1$的位,就异或上对应的线性基。
最终得出的答案就是$k$小值。

模板

样例输入
一个整数N,后面N行待插入的数值
6
5
23
4
12
2
9
样例输出
一个最大的xor值和一个最小的xor值
31 1
下面给出完整模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
#include <queue>
#define Fordowni for (register int i = 60; i >= 0;i--)
#define Forupi for (register int i = 0; i <= 60;i++)
using namespace std;
class LinearBase
{

public:
long long d[61], p[61];
int cnt;
LinearBase()
{
memset(d,0,sizeof(d));
memset(p,0,sizeof(p));
cnt=0;
}
bool insert(long long val)
{
Fordowni
{
if (val & (1ll << i))
{
if (!d[i])
{
d[i] = val;
break;
}
val ^= d[i];
}
}
return val > 0;
}
long long Querymax()
{
long long res = 0;
Fordowni
{
if ((res ^ d[i]) > res)
res ^= d[i];
}
return res;
}
long long Querymin()
{
long long res = 0;
Forupi //这边不要写成Fordowni
if (d[i])
return d[i];
}
void rebuild()
{
Fordowni
{
for (register int j = i - 1;j >= 0;j--)
{
if (d[i] & (1ll << j))
{
d[i] ^= d[j];
}
}
}
Forupi
{
if (d[i])
p[cnt++] = d[i];
}
}
long long kthquery(long long k)
{
rebuild();
long long ret = 0;
if (k >= (1ll << cnt))
return -1;
Fordowni
if (k & (1ll << i)) //不同的写法:(k >> i) & 1
ret ^= p[i];
return ret;
}
};
LinearBase merge(const LinearBase &n1,const LinearBase &n2)
{
LinearBase ret = n1;
Fordowni
if (n2.d[i])
ret.insert(n2.d[i]);
return ret;
}
LinearBase A;
int N;
int main()
{
cin >> N;
for (register int i = 1; i <= N; ++i)
{
long long x;
cin >> x;
A.insert(x);
}
cout << A.Querymax() << " " << A.Querymin() << endl;
return 0;
}

『BZOJ 2006』[NOI2010]超级钢琴

发表于 2018-03-10 | 更新于 2018-09-15 | 分类于 题解

Problem

Description
小Z是一个小有名气的钢琴家,最近C博士送给了小Z一架超级钢琴,小Z希望能够用这架钢琴创作出世界上最美妙的音乐。 这架超级钢琴可以弹奏出$n$个音符,编号为$1$至$n$。第i个音符的美妙度为Ai,其中Ai可正可负。 一个“超级和弦”由若干个编号连续的音符组成,包含的音符个数不少于L且不多于R。我们定义超级和弦的美妙度为其包含的所有音符的美妙度之和。两个超级和弦被认为是相同的,当且仅当这两个超级和弦所包含的音符集合是相同的。 小Z决定创作一首由k个超级和弦组成的乐曲,为了使得乐曲更加动听,小Z要求该乐曲由k个不同的超级和弦组成。我们定义一首乐曲的美妙度为其所包含的所有超级和弦的美妙度之和。小Z想知道他能够创出来的乐曲美妙度最
大值是多少。

阅读全文 »

ST表详解

发表于 2018-03-08 | 更新于 2018-09-13 | 分类于 讲解

作用

  • ST算法是用来求解给定区间的最值的

优势

  • ST算法分成两部分:离线预处理 $O(nlogn)$和 在线查询$O(1)$。虽然还可以使用线段树、树状链表等求解区间最值,但是ST算法要比它们更快,而且适用于在线查询。

操作

预处理

  • 就是DP, 用于求解区间最值,并保存到一个二维数组中
  • 使用倍增的思想,每次增加2^i个长度
    使用$f[i,j]$表示以$i$为起点,区间长度为$2^j$的区间最值,此时区间为$[i,i + 2^j - 1]$。
    比如,$f[0,2]$表示区间$[0,3]$的最小值,即等于$4$,$f[2,2]$表示区间$[2,5]$的最小值,即等于$1$。
    在求解$f[i,j]$时,ST算法先对长度为$2^j$的区间$[i,i + 2^j - 1]$分成两等份,每份长度均为$2^{j - 1}$。之后在分别求解这两个区间的最值$f[i,j - 1]$和$f[i + 2^{j - 1},j - 1]$。,最后在结合这两个区间的最值,求出整个区间的最值。特殊情况,当$j = 0$时,区间长度等于$1$,即区间中只有一个元素,此时$f[i,0]$应等于每一个元素的值。
    举例:要求解$f[1,2]$的值,即求解区间$[1,4] = \{4,6,10,1\}$的最小值,此时需要把这个区间分成两个等长的区间,即为$[1,2]$和$[3,4]$,之后分别求解这两个区间的最小值。此时这两个区间最小值分别对应着$f[1,1]$ 和 $f[3,1]$的值。
    状态转移方程: $f[i,j] = min(f[i,j - 1],f[i + 2^{j-1},j - 1])$
    初始状态为:$f[i,0] = data[i]$

求最值

已知待查询的区间$[x,y]$,求最值

我们把待查询的区间分成两个小区间,这两个小区间满足两个条件:
(1)这两个小区间要能覆盖整个区间
(2)为了利用预处理的结果,要求小区间长度相等且都为$2^i$。注意两个小区间可能重叠。

我们可以直接得到两段区间的$i=log_2^{l-r+1}$

把待查询区间$[x,y]$分成两个小区间$[x,x + 2^i - 1]$ 和 $[y - 2^i + 1,y]$,其又分别对应着$f[x,i]$和$f[y - 2^i + 1,i]$,此时为了求解整个区间的最小值,我们只需求这两个值得最小值即可,此时复杂度是$O(1)$。

下面给出完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define Sparse_Table ST
using namespace std;
class Sparse_Table
{
#define MAXN 233333
#define MAXEXPO 32
//这边列表示2的幂次,32足够int类型使用
private:
int QMax[MAXN][MAXEXPO];
int QMin[MAXN][MAXEXPO];

public:
int N;
int data[MAXN];
inline double log2(double a)
{
return (double)log(a) / log(2);
}
void initST()
{
for (register int i = 1; i <= N; i++)
for (register int j = 1; (2 << j) << N; ++j)
{
for (register int i = 1; i + (1 << j) - 1 <= N; ++i)
{
QMax[i][j] = max(QMax[i][j - 1], QMax[i + (1 << (j - 1))][j - 1]); //一定要注意是(1<<(j-1)),表示2^(j-1)
QMin[i][j] = min(QMin[i][j - 1], QMin[i + (1 << (j - 1))][j - 1]);
}
}
}
int QueryMin(int l, int r)
{
int k = log2(abs(r - l + 1));
return min(QMin[l][k], QMin[r - (1 << k) + 1][k]);
}
int QueryMax(int l, int r)
{
int k = log2(abs(r - l + 1));
return max(QMax[l][k], QMax[r - (1 << k) + 1][k]);
}
};
ST A;

int main(int argc, char **argv)
{
ios::sync_with_stdio(false);
cin >> A.N;
for (register int i = 1; i <= A.N; ++i)
cin >> A.data[i];
A.initST();
int Queries;
cout << "Enter your number of queries:" << endl;
cin >> Queries;
for (register int i = 1; i <= Queries; ++i)
{
int x, y;
cin >> x >> y;
cout << "MAXINUM Between " << x << " and " << y << " is " << A.QueryMax(x, y) << endl;
cout << "MININUM Between " << x << " and " << y << " is " << A.QueryMin(x, y) << endl;
}
return 0;
}

C++求解N阶幻方

发表于 2018-03-07 | 更新于 2018-09-10 | 分类于 无聊

由一道数学题的联想
然后根据网上的做法瞎jb乱打了一下,居然对了
代码精心附上了注释,有兴趣的童鞋可以看一看。。
不说了,上代码!(自认为结构很清晰易懂)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#define MAXN 10000
using namespace std;

int matrix[MAXN][MAXN] = { 0 };
//生成奇数幻方
void CreateOddMagicSquare(int n)
{
int x = 0, y, mun = 1;
y = n / 2;
while (mun <= n * n)
{
matrix[x][y] = mun;

//通过x0、y0检测右上的是否已经填入数字
int x0 = x;
int y0 = y;
x0--;
y0++;
//超界处理
if (x0 < 0)
x0 += n;
if (y0 == n)
y0 = n - y0;
if (!matrix[x0][y0])
{
x = x0;
y = y0;
}
else
{
//若有数字填入之前数字的下方
x++;
if (x == n) x = 0;
}
mun++;
}
}


//生成双偶幻方
void CreateDoubleEvenMagicSqure(int n)
{
int num = 1;
//从1到n的平方依次赋值
for (int i = 0; i<n; i++)
for (int j = 0; j<n; j++)
matrix[i][j] = num++;

//小正方形的对角线上的数字取其补数
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
if (i % 4 == 0 && abs(i - j) % 4 == 0)
for (int k = 0; k<4; k++)
matrix[i + k][j + k] = abs(n * n + 1 - matrix[i + k][j + k]);
else if (i % 4 == 3 && (i + j) % 4 == 3)
for (int k = 0; k<4; k++)
matrix[i - k][j + k] = abs(n * n + 1 - matrix[i - k][j + k]);

}
}


//生成单偶幻方
void CreateSingleEvenMagicSqure(int n)
{
int k = n / 2;
CreateOddMagicSquare(k);
//赋初值,左上最小,右下其次,右上再次,左下最大
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++)
{
matrix[i + k][j + k] = matrix[i][j] + k * k;
matrix[i][j + k] = matrix[i][j] + k * k * 2;
matrix[i + k][j] = matrix[i][j] + k * k * 3;
}
//公式 n=4m+2
int m = (n - 2) / 4;
//交换x方向正中行的从左至右m-1个
for (int i = 0; i<m - 1; i++)
{
int buf = matrix[k / 2][i];
matrix[k / 2][i] = matrix[k / 2 + k][i];
matrix[k / 2 + k][i] = buf;
}
int buf = matrix[k / 2][k / 2];
//以及正中间的数
matrix[k / 2][k / 2] = matrix[k / 2 + k][k / 2];
matrix[k / 2 + k][k / 2] = buf;

//交换除x正中间行的其他行对应数字m个
for (register int i = 0; i<k; i++)
for (register int j = 0; j<k / 2; j++)
{
if (i != k / 2)
{
int buf = matrix[i][j];
matrix[i][j] = matrix[i + k][j];
matrix[i + k][j] = buf;
}
}

//交换最右边m-1个数字
for (register int i = 0; i < k; i++)
for (register int j = n - 1; j > n - 1 - (m - 1); j--)
swap(matrix[i][j], matrix[i + k][j]);


}
bool Check(int n)
{
int sum = (n*(n*n + 1)) / 2;
int SumA = 0, SumB = 0;

for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
SumA += matrix[i][j];
if (SumA != sum)
return false;
SumA = 0;
}

for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
SumA += matrix[j][i];
if (SumA != sum)
return false;
SumA = 0;
}

for (int i = 0; i<n; i++)
{
SumA += matrix[i][i];
SumB += matrix[i][n - i - 1];
}
if (SumA != sum || SumB != sum)
return false;

return true;

}

int main()
{
int n;
cin >> n;
if (n % 2 != 0)
CreateOddMagicSquare(n);
else if (n % 4 == 0)
CreateDoubleEvenMagicSqure(n);
else if (n % 2 == 0)
CreateSingleEvenMagicSqure(n);

for (int i = 0; i<n; i++)
{
for (int j = 0; j<n; j++)
cout << matrix[i][j] << "\t";
cout << endl;
}

if (!Check(n))
cout << "Failed to generate!" << endl;
getchar(), getchar();
return 0;
}

『BZOJ 1059』[ZJOI2007]矩阵游戏

发表于 2018-03-06 | 更新于 2018-09-15 | 分类于 题解

Problem

这题什么鬼。。
第一反应:大暴力?
(您的电脑怕不是128位量子计算机哦)
原题传送门

Description
 小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏——矩阵游戏。矩阵游戏在一个$N*N$黑白方阵进行(如同国际象棋一般,只是颜色是随意的)。每次可以对该矩阵进行两种操作:行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色)列交换操作:选择矩阵的任意行列,交换这两列(即交换对应格子的颜色)游戏的目标,即通过若干次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。对于某些关卡,小Q百思不得其解,以致他开始怀疑这些关卡是不是根本就是无解的!!于是小Q决定写一个程序来判断这些关卡是否有解。

阅读全文 »

『BZOJ 2818』GCD

发表于 2018-03-05 | 更新于 2018-09-15 | 分类于 题解

Problem

咋一看这题目名字怎么那么亲切,结果看到题面就觉得不亲切了(大雾)
原题传送门
Description
给定整数$N$,求$1<=x,y<=N$且$gcd(x,y)$为素数的数对$(x,y)$有多少对.

Input
一个整数$N$

Output
如题

Sample Input
4

Sample Output
4

HINT
对于样例$(2,2),(2,4),(3,3),(4,2)$

$1<=N<=10^7$

Solution

按照网上流传的说法,此题为欧拉函数练手题
解法确实也很好懂,下面做一下详细的解释:

首先,定义集合$P=\{x|x为素数\}$,接下来我们可以得出:

①若$k_1≠k_2$
令$k_1>k_2$
则满足调节的$k_1$的个数就为$φ(k_1)$
∴$k_1,k_2$所构成的组合的个数为$2·φ(k_1)$
②若$k_1=k_2$
当且仅当$k_1=k_2=1$时,满足条件

综上,满足条件的所有数对个数为$\Sigma_{p∈P}^{n}[(\Sigma_{i=1}^{\lfloor\frac{n}{p}\rfloor}2φ(i))+1]$

推到这一步,我们会惊讶地发现
woc这时间复杂度已经爆表了啊。。
没事,我们来看看哪里爆了。。。
诶?似乎没爆?

==>睁大眼睛好好看看$φ(N)$的计算公式:

时间复杂度为标准的$O(n\sqrt{n})$,完美超时
下面介绍3个很奇妙的东西

  • 当$p∈P$(也就是P为质数)时,$φ(p) = p-1$
  • 若$i\ mod\ p =\ 0$, 那么$φ(i·p)=p·φ(i)$
  • 若$i\ mod\ p ≠\ 0$, 那么$φ(i·p)=φ(i)·( p-1 )$

这些都是欧拉函数的一些很奇妙的性质(证明就不管它了)
利用这些性质,我们就可以在$O(n)$内搞出所有$φ$值了
算完了$φ$值,会发现还有一个和要维护,不然还是会T。这个简单,前缀和即可。
总时间复杂度$O(n)$
下面给出了带有详细注释的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <cstring>
#include <cstdlib>
#define MAXN 10000000 + 5
using namespace std;
int N;
long long Phi[MAXN], p[MAXN], pt;
bool flag[MAXN];
long long sum[MAXN];
inline void getphi()
{
pt = 0;
Phi[1] = 1;
for (register int i = 2; i <= N; i++)
{
if (!flag[i]) //这表明了i是素数
{
p[++pt] = i;
Phi[i] = i - 1; //如果i是素数,那么phi(i) = p - 1;
}
for (register int j = 1; j <= pt && p[j] * i <= N; j++)
{
int k = p[j] * i;
flag[k] = true; //k可以通过乘积的形式得到,当然就不是素数了。于是标记改为true
if (i % p[j] == 0)
{
Phi[k] = p[j] * Phi[i]; //如果i mod p = 0, 那么 phi(i * p) = p * phi(i)
break;
} else
{
Phi[k] = (p[j] - 1) * Phi[i]; //积性函数的性质 :若i mod p ≠0, 那么 phi(i * p)=phi(i) * (p - 1)
}
}
}
}
int main(int argc, char **argv)
{
ios::sync_with_stdio(false);
cin >> N;
getphi();
memset(sum, 0, sizeof sum);
sum[1] = 1;
for (register int i = 2; i <= N; ++i)
{
sum[i] = sum[i - 1] + 2 * Phi[i];
}
long long ans = 0;
for (register int i = 1; i <= pt; ++i)
{
ans += sum[(int)(N / p[i])];
}
cout << ans << endl;
return 0;
}

『POJ 3253』 Fence Repair

发表于 2018-02-27 | 更新于 2018-09-15 | 分类于 题解

Problem

题目大意:

给一块长木板,现要将其锯成$n$段,共需锯$n-1$次,每次锯的代价为所锯木板的长度,求最小总代价。

阅读全文 »
1…567
Sky of war

Sky of war

Why I gotta fly? The boundlessness precipitates me to do so.

63 日志
7 分类
60 标签
RSS
GitHub E-Mail Google Twitter FB Page YouTube
Links
  • 几何大师wuyudi
  • OI巨佬ExtendedAsh
  • 马上要碾压我的cz
  • galgame的神犇Highwind
  • Waterloo的CS大佬Jude
  • yww%%%
0%
© 2018 Sky of war
博客全站共46.3k字