什么是好的艺术作品:结合我的见闻谈谈理解

艺术本身是一个很抽象的东西。但是与之相对地,艺术作品却往往非常具象。原始人类文明分布在世界各地,却不约而同地发展出了几种相同的艺术形式,一定程度上说明了人类共享一些“艺术”和“好艺术”的标准。古时候,艺术的范畴相对狭窄;到了近现代,除了从新的技术衍生出的艺术(如电影、摄影),也有行为艺术等超脱特定技术的艺术手段。

从根本上说,艺术是手艺人的术。比如木匠使用自己的手艺雕刻,摄影师用自己的技术拍摄等等。但作品只是艺术的载体,艺术的核心在于表达。 好的艺术,首先要有好的内核去表达。很多人,包括我自己,常常过分注重于浮于表面的美学体验,而忽视了其中的表达。在我看来,美学体验决定了艺术的下限,而艺术表达决定了艺术的上限。好的美学体验确实能一把抓住他者的注意力,这在商业、或者爱好者的入门阶段,起到了一些关键性的作用。没有人会愿意去听一首五音不全的音乐,或者画的乱七八糟的油画。因此在这些场景,美学体验起到了举足轻重的作用。但我们也要意识到,美学体验可以被社会轻易塑造,这就意味着它的分量有时候并没有我们想象的那么历久弥新。一些潮流美学一旦过时,就变成了陈词滥调。比如口水歌、时装穿搭、流量电影电视剧等。无数事实证明,资本和权力都可以左右社会的审美,因此许多社会时代都有显著的、“一次性的”审美倾向。一些被众人追捧一时的艺术作品很快就会被人遗忘——它们当然称不上是艺术品。

说实话我对古典艺术的接触较少,因此下面就结合一些近现代艺术来谈谈我对好艺术的理解。最近在听宋东野的《莉莉安》,它是我喜欢的为数不多的华语民谣之一。首先从美学体验的角度来说,莉莉安的作词用到了古代诗歌常用的起兴手法:

在离这很远的地方,有一片海滩

孤独的人他就在海上,撑着船帆

如果你看到他,回到海岸

就请你告诉他你的名字,我的名字

莉莉安

几行歌词,字字不提想念,句句都是想念。起兴也好、和弦走向也好、和声唱法也好,全都在围绕孤独和想念这两个核心。这就是艺术作品表达的分量。手法服务于表达,而饱满的表达内核才是打动人心的关键。只有你表达的东西能打动人,它所依附的艺术作品才有说服力。

另一个最近令我赞叹的艺术作品是2017年的电影《爱乐之城》。这部电影是古典和现代的融合,它的形式是上个世纪流行的歌舞片,许多运镜手法也相当古典。站在历史发展的角度,一些技术、手法、形式的淘汰,必有其历史原因。贸然捡起来用,很可能竹篮打水一场空。但是爱乐之城让我看到了古典手法的蓬勃张力。很可惜我的笔法无法重现那种震撼,建议读者搜一些拉片或者解读,自行体会。总而言之,我丝毫不觉得这部片的古典味会对观众理解本片带来门槛。相反,导演充分利用了古典手法的特性,做到了强力的表达。

一些富有张力的表达甚至不需要严肃的手法。余华的《活着》带给我的就是这样的震撼。通读全文,《活着》没有什么撕心裂肺的控诉,只有平淡的直叙,甚至有几分像闲聊。它的成功在于所呈现的悲凉早已渗入字里行间,以至于作者一言不发,就能令读者无语凝噎、喘不过气。

另外我最近也开始涉猎摄影。摄影应该是普通人离大师最近的艺术形式了——不需要线条和色彩,不需要乐理和演奏技能,更不需要笔耕不辍的毅力,只需要举起手机拍照,就能收获一些不那么糟糕的照片。但是纵观大师的作品,就能发现虽然大家都是拍照,但是摄影师却能用照片做出表达——而不是单纯的记录。最近我正在实践布列松的“决定性瞬间”理论。这一理论的核心在于抓住画面的顶峰一刻。我认为决定性瞬间可以帮助像我一样的新人拍出有表达的照片。原因很简单,当你觉得某一刻能打动你,那么它就有可能打动别人。而相对的构图、色彩、光影等,目前只是作为我的辅助手段。因为这些要素相对可以被系统性地训练出来,而表达的能力则是需要长久的理解和实践得到的。

曾听过这么一句话,想要在艺术方面取得进步,就一定要“眼高手低”。因为只有你的审美上去了,你的作品才有提升的可能。而实际上,随着开始有意识地赏析大师的摄影作品,我自己的摄影表达也确实跟着提升了。从前我觉得广角就是神,可以最大限度地容纳美妙的风光;而现在我才发现一般人往往难以驾驭广角的构图,只有从长焦下手才能快速形成属于自己的表达。

我有点不知道如何收尾这篇文章,因为上面这些文字已经用尽了我对艺术的所有理解。我知道学术界、艺术家对艺术的理解一定比我深厚,因为他们经过了大量的艺术实践,能从更多角度、更多案例理解艺术。我很庆幸我能欣赏艺术,而不是像很多人一样被拉入快消内容的洪流无法自拔。但我更希望我能够与大师们产生更多的共鸣,甚至有朝一日创作出一样优秀的作品。虽不能至,心向往之。所以就这么戛然而止吧。

简单证明欧拉定理

证明这样的定理,最好还是执果索因。因为公式只是描述一个客观事实。充满好奇心的初学者看到这样简洁但毫无头绪的公式,很容易失去方向。要知道我们的目的是证明这个式子,而不是追究这个式子是怎么发明的不要觉得一个式子看起来简单,就可以用一个很简单的逻辑推算出来。例如这样的式子,高中生就能看懂其中所有的符号,但是想要推导它,则非学复变函数不可。

1. 正向推演

先看看欧拉定理的公式:

要证明这个式子,首先要回归 的定义。 的含义是小于、且与互质的数字的个数。比如令 ,则1、3、5、7都与之互质。那么

现在,我们把这个数的集合记作,即

现在把中的每个元素乘以,得到新的集合,即

可以证明意义下,也就是对。更具体地说,如果,那么

第二节会证明这个性质,现在先让我们继续。既然,那么A和B中的元素各自在模意义下相乘,得到的结果也是一样的。即

根据欧拉函数的定义,一定有,所以一定有模意义下的模逆元(第三节会做一个简单的证明)。用的模逆元消掉等号两边的,可以得到最终的式子:

2. 证明模意义下A=B

为了方便区分两个集合,我们记。其中的元素来自欧拉函数的定义,所以, 中的元素经过模运算,因此

证明的关键在于或者的性质。由于,而欧拉定理的前置条件约定,所以,自然(对于此有疑问的读者,可以思考一下辗转相除法成立的原理:)。又因为,所以恰好符合了的定义,也就是

但是这样只能证明,不能证明。要证明,就要证明两两不同,这样,A和B中就都拥有了个元素,那么成立。用数学语言表示,就是要证明

可以用反证法证明这一点。假如满足。由于,所以一定存在的模逆元。使用模逆元消去,就得到了。而前面提到, ,所以不存在的情况。因此假设不成立,原命题成立。

3. 证明只有时,才存在的模逆元,即

同样也是反证法,假如,也就是,使得,那么对同余式移项目得到

显然当且仅当的倍数,也就是时,同余式成立。对其进行简单的移项,得到。由于,所以我们从中分离公因数,剩下的部分记作。得到:

由于,所以。这个不等式显然没有正整数解,因此得证假设不成立。

原来以太网压根不需要交换机

计网学到过CSMA/CD策略。这是一种冲突检测算法,用来检测物理信道上的冲突,并智能进行回避。但是老师和课本都没提到CSMA/CD应用在哪里、起到了怎样的效果。因为以我浅薄的认知,以太网是通过交换机和路由器组建的,也从未听说有“冲突”的状况。不过应用在802.11上的CSMA/CA则好理解的多,因为无线电通信的干扰是很容易理解的,多个设备向路由器发射无线信号时,自然会发生冲突。

但是有线网络,冲突个什么劲儿呢?这就要从以太网(aka 802.3)说起了。以太网最初的标准诞生于1976年。彼时大行其道的网络协议还是令牌环。以太网与之相比,在性能上其实没有理论优势。一个4M的令牌环网络和一个10M的以太网数据传送率相当,一个16M的令牌环网络的数据传送率接近一个100M的以太网(维基百科)。但是以太网有一个很重要的特性:成本低廉。令牌环网络是环形拓扑,并且采用时分多址的原则进行多路复用。拿到令牌的节点无论是否有数据传输,都要经过获取令牌、交出令牌的动作。这些过程会导致网络的总吞吐下降。更重要的是,令牌环网并不够“端原则”,它需要专门的设备(多站访问部件MAU)来支撑网络。同时环形网络的可靠性严重依赖任何一个成员的可靠性。一些智能的MAU可以在环中跳过无效的节点,但【中央节点】的概念却依然客观存在,并且一个节点突然离开也可能造成网络短暂的不可用。

反观以太网,则是一种具有自组织性质的网络——这能有效降低部署小型网络的成本,对商用推广有很大的好处。以太网是总线结构,早年使用同轴电缆作为物理载体。众所周知,同轴电缆常用于电视广播信号,一根同轴电缆上的信号能被所有成员接收到。因此其天然就是互联的,且不需要任何中继的处理。也正因如此,冲突恐怕无法避免。于是以太网加入了CSMA/CD算法进行冲突检测和避免。当节点要发送消息时,会守听信道一段时间,确定没有人在传输数据。即使发生了冲突,节点间也会自动按一定的规则进行退避,等待其他节点完成发送。可以发现,从始至终,都不存在所谓的“中央节点”来协调网络。若干个节点只要接入同一个物理信道,就可以自行组织成一个有序传输的网络。

回归到开头的话题。CSMA/CD的作用,就是在以太网节点间有序共享同一物理信道。这就意味着,交换机并不是以太网的必选项。只要能让节点间连在一起,他们就能相互通信。集线器不必多言;哪怕把几根网线缠在一起,也能组织成一个可用的网络。

试想一下,假如你生活在上世纪,家里有几台电脑想互相连接。你是愿意买一个需要插电的MAU、还是不用插电的集线器、还是分文不花,把网线直接缠在一起了之呢?


本来想写一篇小短文记录自己的“发现”,但是想了想,这种问题从一开始就不应该存在。理论脱胎于实际,因此脱离实际去学习理论,无异于浮沙筑高塔。而只学理论不学实际,恐怕是国内高校教育的通病。其中最令人诟病的是高等数学、首当其冲的是线性代数。几乎所有高校老师上来就开始讲行列式的计算和100种变式的解决办法,连“大名鼎鼎”的《同济线代》也是如此。直到后来我自己感到有兴趣,稍稍深究了下它的前世今生,才发现原来行列式、矩阵的真正意义,领悟到它对人类上天入地有多么重要。相比之下,课本上、课堂上、考卷上的一道道计算题,仿佛在表达对它的不屑。

之前我的博客也发过一些类似的探究博文,但它们因为博客的蒸发而消散了。后面我有空就试试补档。因为我相信还有很多的像我一样曾经学了个一知半解,但是灵魂深处对它们“从哪儿来到哪儿去”有所渴求的人。

新的开始......再一次

发生了什么

我的博客蒸发了。

前因后果?

我向来根据主机商email的邮件交钱。但我没有收到今年一月份的账单。直到我2月8号看到一封2月4号的名为第三次收据逾期通知的邮件时,我知道一切都晚了。

上去一看:不出所料,主机已经被停权,数据也被删掉了。

自从被主机商坑了两次之后,我再也不想相信他们了。数据只有留在自己手上最安全,离开自己手的数据就像泼出去的水,能不能收回来都是未知数。

所以第三次开始,我选择静态博客。也就是你现在看到的这个页面。

备份了吗?

备份了,但用的是plesk自带的云备份功能。面板帮我上传到digiocean的云盘。本来这是一个相对稳妥的方案,老牌云厂商也相对靠谱。但是当年注册digiocean用的是Google账号,这个账号已经被Google限制了登录,要求验证手机才能继续。因此这个备份拿回来的希望不大。

以后打算怎么办?

只有因为没交钱被删过数据的用户,才能体会公有云毫不留情的残酷。想要长远发展,就要建立自己的私有云。我已经联系了几个志同道合的朋友组建了异地主机网络,并通过VPN实现互联。在此基础上,可以逐步配置备份方案,实现高可靠的容灾能力。目前,我的博客文章至少保留在两个地方:我自己的电脑和一台千里之外的树莓派上。后续我将增加更多的备份点。备份的终极奥义还是“异地多备”。

好消息是,由于各种搜索引擎的快照功能,我可以恢复一些以往的博客快照。但就像第一次遗失一样,只能像捡贝壳般收集一些公开的博文。那些留在草稿箱,或者设为私密的博文,恐怕再也回不来了。

最后,我将正式脱离动态博客,投入静态博客的怀抱。2023年,博客圈低迷的当下,已经不再需要花里胡哨的后台技术。绝大多数的基本功能都能靠纯前端代码,或hexo这样的生成器解决。PHP已经成为了过往,前后端分离是大势所趋。

其他要说的……

同温层的深冻这个博客,掐指一算也开了快8年了。它见证了我的成长,也记录了我一路以来的思想、体会。遗失这样一个博客,毫无疑问是令人心痛的。但换一种角度,在生命前进的旅途上,总有一些东西是注定留不住的。与其为失去的东西伤心,不如早日启程。有言道:“如果你为失去了星星而哭泣,那么你也要失去阳光了”。

就让我们启程吧,新的旅途!

C++仿函数应用小例

写一道涉及图BFS的题目。图BFS需要一个visit数组管理已经访问过的顶点。因此需要另外开一个数组去管理这个过程。AC后我想这样管理实在是有点烦人,于是想办法把queue和visit管理重新打包成一个类去管理。

一开始我是这样打包的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typename _Tp,typename _Sequence = deque<_Tp> >
class OnceBFS_Queue: public std::queue<_Tp,_Sequence>
{
public:
OnceBFS_Queue(int len, const _Sequence& __c = _Sequence()): uque(__c){
vis = new bool[len];
memset(vis, 0, len*sizeof(bool));
}
~OnceBFS_Queue() { free(vis); }
bool push(const _Tp& _x){
unsigned int idx = _x;
if(vis[idx]) { return false; }
else { vis[idx]=true; uque::push(_x); return true;}
}
bool isVis(const unsigned int& _x) { return vis[_x]; }
protected:
typedef std::queue<_Tp,_Sequence> uque;
bool* vis;
};

因为绝大多数情况下,顶点的编号是一个非负整数。但是这道题偏偏有点不一样,BFS的过程中会绑定上另一个参数,并且希望将这个参数提取出来使用。而上面这种形式,缺乏应变的空间。应付诸如int unsigned long这样的原生变量还行,一旦遇上稍微复杂一点的自定义结构,例如本题中我用到的pair<int, unsigned long long>,则根本没办法通过编译。

究其原因是无法通过这些数据类型得到需要标记的下标。如果是整数,则对指针的[ ]操作可以被编译器直接翻译。但是显然[ ]中不能放pair<int, long long>类型的变量。这时候就需要一个单独的函数去完成这件事情。

一开始我的想法是传递一个函数指针。能够想到两种方案。一种是从构造函数中传递,也就是在运行时完成指定过程。还有一种方法,是通过模板参数指定。但这样一来缺乏灵活性,没办法适配各种各样的参数条件(万一需要通过绑定或者别的手段,向函数中带入参数呢?);二来也不够优雅,STL缺乏合适的包装器和修饰器去表示一些简单的传递关系。

那么,借鉴一下算法竞赛中常用的——priority_queue。这是一个模板类,而且经常需要这样设定模板参数:

1
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>

在dijkstra算法中,需要这样指定priority_queue的参数,才能够正确地从队列顶部得到d值最大的顶点。让我们把注意力放在greater<pair<int,int>>上。之前我只知道这个STL类型可以像函数一样判断大小关系,并不知道其本质。但是只要转到它的定义去看看,就可以得到如下代码(因为STL s**t一样的代码风格,我稍微做了调整):

1
2
3
4
5
6
7
8
template<typename _Tp>
struct greater : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool operator()(const _Tp& __x, const _Tp& __y) const {
return __x > __y;
}
};

可以看到,所谓greater,实际上就是一个重载了( )运算符的struct。这就意味着,这个模板类被实例化之后,可以等价于这样的函数:

1
bool greater (const Type& __x, const Type& __y)

调用时也只需要类似:

1
2
3
int a, b;
/* Do something here */
bool ans = greater<int>(a, b)

可以发现,尽管greater<>是一个struct,但是经过运算符的重载,在使用中和函数无异。更重要的是,struct不比单纯的函数指针,你可以在不同的struct实例中夹带额外的内容。

这就是本文要说的——仿函数

实际上,仿函数并不是C++特有的概念。通过一些手段,可以在Java甚至C语言实现相同的原理。通过一些简单的封装,将复杂的操作通过一个类型带入各种作用域当中,同时降低了代码的耦合性,不得不说是一种很精妙的设计了。

通过仿函数设计出来的带有自动visit的数组是长这样的:

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
template<typename _Tp,typename _Ind,typename _Sequence = deque<_Tp> >
class OnceBFS_Queue: public std::queue<_Tp,_Sequence>
{
public:
OnceBFS_Queue(int len, const _Sequence& __c = _Sequence()): uque(__c)
{ vis = new bool[len]; memset(vis, 0, len*sizeof(bool)); }
~OnceBFS_Queue() { free(vis); }
bool push(const _Tp& _x){
unsigned int idx = Index(_x);
if(vis[idx]) { return false; }
else { vis[idx]=true; uque::push(_x); return true;}
}
bool isVis(const _Tp& _x) { return vis[Index(_x)]; }
bool isVis(const unsigned int& _x) { return vis[_x]; }
protected:
typedef std::queue<_Tp,_Sequence> uque;
bool* vis;
_Ind Index;
};


struct cp {
unsigned int operator() (const pair<int,ull> &x) {
return x.first;
//看起来很蠢,是因为这里确实不需要额外的操作,但是操作的扩展性大大增强了
//为了避免额外的性能开销,我还是把仿函数放在了模板参数,以尽量保持代码静态
}
};

原题目是多case的,即便如此,在不断的new和free中,也没有产生很明显的额外性能开销。实际上,在我们学校的垃圾OJ上,使用这样一个class,和使用传统C风格的写法,在性能上开销完全相同,不得不感叹一下C++的“0 cost”模板技术了。

经过观察,我发现还可以通过抽象基类的继承这种方式,实现对更大范围的容器的适配,例如前面提到的priority_queue,甚至stackdeque

不过因为各种容器的接口并不是非常一致,导致想要扩增适配,还是要手动进行继承和设定。因此写了一半还是放弃了。但是这一切对于自己对泛型的理解,还是有一些帮助的。因为实际上这是我第一次写带有3个截然不同的模板参数的模板类,也算是一点进步了。