CSS各种兼容的渐变方式

我的博客页面顶部的Banner是个图片,然后我在上面覆盖了一层从透明到白色的渐变。

但是这个渐变只能在Webkit浏览器上看到。。。。。。

今天在做honoka.moe页面的时候,作为使用Feelyblog的站点,当然也出现了同样的问题。

于是我写了一段CSS解决了它:

1
2
3
4
5
6
7
8
9
10
.gradient-element {
background:-webkit-linear-gradient(top, transparent, #fff);
background:-moz-linear-gradient(top, transparent, #fff);
background:-o-linear-gradient(top, transparent, #fff);
background:-ms-linear-gradient(top, transparent, #fff);
background:linear-gradient(top, transparent, #fff);
background:-webkit-gradient(linear, left top, left bottom, from(transparent), to(#fff));
filter:progid:DXImageTransform.Microsoft.Gradient(startColorStr=#00FFFFFF,endColorStr=#FFFFFFFF,gradientType=0);
-ms-filter:progid:DXImageTransform.Microsoft.Gradient(startColorStr=#00FFFFFF,endColorStr=#FFFFFFFF,gradientType=0);
}

嗯,大概就像这样。

从上到下分别是webkit新标准(新opera,safari),mozilla(火狐),Opera,IE, 其他浏览器兼容,老webkit(Chrome和国内一众浏览器。),IE9,IE8以下。

最后两个已经不是CSS了。。。。。

IE6,我实在是无能为力。

嗯,这样就够了。

PS. 我懒的把这个样式往这边同步。。。。这里就这样吧。。。

最近在用Onethink做CMS系统果然很顺手

最近接了以前一个同学的要我帮忙做网站的请求。大概就是学校项目的成果发布网站吧。他想要个类似CMS系统的东西。

本来我想直接介绍给他开源系统让他自己去研究,不过他并不是计算机相关专业,对网站开发这件事情可以说是一窍不通。

再加上他承诺给我的报酬还不错,于是我就决定找个框架帮他做二次开发了。


说到建CMS,我首先想到的其实是帝国、织梦这种国内老牌CMS系统。

不过这些系统虽然老牌,功能强大。但是正是因为庞大所以才不好用。

刚好我在CCBC7的开发中,和之前feelyblog的开发中用到了一个php框架叫做ThinkPHP。这个框架的团队做了一个CMF框架,叫做OneThink。趁着这个机会,我就想到,用OneThink作为框架来开发这个网站。

B站直传——并不是1080P的噩梦

在新浪上传彻底死了之后,我曾经在贴吧回答一个人的问题时提到,新浪死了代表着1080P党在B站已死。

那个时候的乐视云,对于1080P是一律二压的。只要视频宽度超过1280,就一定会被二压。虽然有些人使用上传1080×1920的视频来规避这一限制,但是毕竟需要你自己在本地把视频旋转回来,在B站播放器没有外部插件的支持下,应该是非常难看的。

最近有人告诉我说这个限制没有了。可是自己手里也一直没有片子可以让我上传试试的。

今天听到了一个很绝望的消息,新浪开始逐步改变外链播放器验证法。使得B站新浪源的投稿纷纷失效。嗯,没错,我在B站投的新浪源的1080P Lovelive也一起挂掉了。于是我想着将它传到乐视云上去。

经过多次实验,我发现直传1080P很多限制甚至比新浪要松,这样方便我们上传画质更好的视频。

我的视频压制参数如下:

1
--crf 24

音频则是压成128KAAC。

不二压视频条件:视频流分辨率小于1080P,码率小于2000Kbps(新浪只有1024Kbps),音频为128KAAC(不是128KAAC都被压成64位AAC了。)

而且并不需要封装flv。这样的条件其实更适合1080p的压制和发布。

下面是今天上传的一个截图。

1080P Lovelive 第一话截图

1080P Lovelive 第一话截图

乐视云上传貌似是不限速的。我这里光纤100Mbps大概能以11MB/s的速度上传。是的,第一集我就传了几秒就成功了。我自己都惊呆了。

用Etag实现php-GD创建的图片缓存

转帖,这篇文章是我去年写的。转到新博客里来。。。。。

网站把图片存在数据库里,有的时候比较方便,有的时候就有点麻烦了。。。比如这些图片不经常更改的时候。。

数据库里的图片一般用php的GD库取出来。然后声明图片格式:

1
header('Content-Type: image/png'); 

进行适当的操作之后,再用

1
imagepng($im); 

输出到浏览器里面。

这本来是一种很好的方式。。可是我马上发现了,当我刷新页面的时候,图片重新载入了。

然后图片非常大,于是我的网站变得很慢 。

php生成的图片不能像正常放在服务器里的图片一样缓存么?

我在尝试了之后终于找到了解决方案:

HTTP/1.1里面有个东西叫做Etag,定义为“被请求变量的实体值”。就是指标识客户端正在请求的数据的一个标记(Token一样的东西)。
使用

1
header('Etag:'.$ETag); 

来设置Etag,其中变量$ETag为自定义的Etag的值。

在HTTP请求里面,客户端会带上If-None-Match,内容就是服务器发来的Etag。然后我们只要用php比对一下是不是相同,如果相同就返回304 Not Modified,如果不同就继续返回数据。

HTTP并没有规定Etag应该是什么样的,于是我们可以自己定义Etag的值,只要保证每次更改Etag都不同就可以了。。常见的是使用md5这样的哈希散列值。

php代码非常简单:

1
2
3
4
5
6
7
$ETag="你自己的Etag"; 
if($_SERVER['HTTP_IF_NONE_MATCH']==$ETag){
header('HTTP/1.1 304 Not Modified'); //返回304,告诉浏览器调用缓存
exit();
}else{
header('Etag:'.$ETag);
};

这一段放在最开头就好了。。。

当然其实我不是把它放在最开头的,我用GD的另一个目的是反外链,于是我把它放在了我的反外链代码后面。如果是外链,直接返回替换的图片,然后才进入这一段代码判断是不是可以调用缓存。

写到这里,这一篇就应该结束了,不过我还有两点需要补充:

第一点是——Etag和URL有关,不同的URL上即使是相同的Etag也没有任何意义。只有URL相同,Etag也相同才会调用缓存。这就要求我们不能用post传参。。。

但是Etag并没有说一定只能用在特定的文件上面,Etag什么都可以用,包括图片,音乐,CSS,JS,HTML本身,都可以有一个Etag,然后用Etag来控制缓存。

第二点是——

在我给我的图片输出页加上这样的代码,然后测试的时候,我发现服务器返回的并不是304,而是200。HTTP header信息里面分明写着:

1
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0 

坑爹嘛这是。检查了Apache设置,php设置和.htaccess设置都没发现这东西是从哪里带上的。。。最后我发现。。其实是因为这个php一开头第一个语句:

1
session_start(); 

php会话信息默认带no-cache头。。

于是如果你的php非要带session_start();,就像我一样,一定要从SESSION里面拿gid去查询数据库。就要在一开头(session_start前面)加上:session_cache_limiter('public');了。

1
2
3
<?php 
session_cache_limiter('public');//设置session缓存
session_start();//读取session

现在是真正的结束了。。

PS。。。。PHP的GD库真是个好东西。。。

关于某些东西的记录

最近得知B站某些悲剧的消息,于是赶紧记录一下锁定投稿的cid,免得以后自己都没得看了。

1434475
1434476
1434477
1434478
1434479
1434480
1434481
1434482
1434483
1434484
1434485
1434486
1434487 NCOP
1434488 NCED


2015-10-27 update

由于新浪视频出错。这些投稿彻底成为死稿。再见。

Feelyblog也许正式坑了

嗯。也许你打开我的博客并没有看到太大变化。

或者你注意到了我的博客有了很多新功能。

不过,很遗憾的是,这些新功能并不是来自我的Feelyblog,而是Wordpress。

为了保持和以前一致的感觉,我把Feelyblog的主题也迁移过来了。

这可能标志着——Feelyblog正式坑了。

最近我的Web开发方面会更加投入进CCBC7。所以这就是说——Namido Puzzle也坑了。

很不幸的开一篇日志来讲这件事情。

如果暑假的时候我能在想起来。

我会继续更新Feelyblog的。

从这个角度上,也不算完全坑掉了。

至少还给Wordpress留下了一套主题嘛。。。233。。

UVa 10765 Doves and bombs 鸽子和炸弹的故事——求割点

一打开这道题,我不禁吓了一跳。。。超长的题目还带一张图,这种满篇文字的题目不禁让人感觉压抑。而且很有ACM出题的风范,下面我摘录一小段:

1
It is the year 95 ACM (After the Crash of Microsoft). After many years of peace, a war has broken out. Your nation, the island of Evergreen Macros And Confusing Shortcuts (EMACS), is defending itself against the railway empire ruled by Visually Impaired Machinists (VIM).

这就是ACM的题目里面十分常见的一个景象——扩充缩写。这件事情是这么干的:已知一个缩写,比如ACM,我们找出一些词汇,让它们表达出一个含义,并且它的缩写是ACM,就像上文做的那样——After the Crash of Microsoft.

有的时候,这件事情是很有意思的。出题的人也许是给我们这些做题的人在思考题目的过程中带来一些有趣的东西。不过对于我这种英语苦手来说。。。。。。我才不喜欢这样的题目呢。


好像说的有点跑题。不过这个题目本身还是很简单的。

题目定义了“鸽子度”这个东西,它就是一张图中去掉一个点之后剩下的连通分支个数。

题目求的是“鸽子度”最大而序号最低的前m个点,并把这些点的序号和“鸽子度”输出。

我从东京大学ACM模板上面抄了一份神代码并且把它转换成了C++11的代码,这种存储一张图的格式十分值得借鉴。

AC代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream> 
#include <algorithm>
#include <string>
#include <vector>
using namespace std;

class V : public vector<int> {
public:
int index;
int num, count;
V(int idx = 0) :index(idx), num(-1), count(0) {

}
bool operator < (const V &b) const {
if (count != b.count){
return count > b.count;
}
else{
return index < b.index;
}
}
};
int dfs(vector<V> &vs, V &v, int c) {
v.num = c;
int low = c;
for (int u : v) {
if (vs[u].num < 0) {
int t = dfs(vs, vs[u], c + 1);
low = min(low, t);
if (v.num <= t) v.count++;
} else low = min(low, vs[u].num);
}
return low;
}
void solve(int n, int m){
vector<V> vs;
for (int i = 0; i < n; i++){
vs.push_back(V(i));
}
int x, y;
while (cin >> x >> y){
if (x < 0 && y < 0) break;
vs[x].push_back(y);
vs[y].push_back(x);
}
for (vector<V>::iterator it = vs.begin(); it != vs.end(); it++){
if ((*it).num < 0){
dfs(vs, *it, 0);
if ((*it).count > 0){
(*it).count--;
}
}
}
sort(vs.begin(), vs.end());
for (int i = 0; i < m; i++){
cout << vs[i].index << ' ' << vs[i].count + 1 << 'n';
}
}
int main(int agrc, char *agrv[]){
ios::sync_with_stdio(false);
int n, m;
while (cin >> n >> m, n m){
solve(n, m);
cout << 'n';
}
}

神奇的线段树模板

两个树状数组=一棵线段树?

就是这么神奇。

本来我早就想发这个模板。。

但是忘掉了。

今天做题才想起来。。。

这是那题的AC代码。。

我干了件很逗逼的事情。

就是typedef int ll

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
#include <algorithm> 
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef int ll;

struct BIT{
ll *vs;
int N;
BIT(int n){
N = n + 1;
vs = new ll[N]();
}
~BIT(){
delete[] vs;
}
void add(int k, ll a){
for (int i = k + 1; i<N; i += i&-i){
vs[i] += a;
}
}
ll sum(int s, int t){
if (s>0) return sum(0, t) - sum(0, s);
ll res = 0;
for (int i = t; i>0; i -= i&-i)
res += vs[i];
return res;
}
};
struct Seg{
BIT *dif, *pre;
Seg(int n){
dif = new BIT(n);
pre = new BIT(n);
ll *is = new ll[n];
for (int i = 0; i<n; i++){
cin >> is[i];
}
dif->add(0, is[0]);
pre->add(0, 0);
for (int i = 1; i<n; i++){
ll x = is[i] - is[i - 1];
pre->add(i, x*i);
dif->add(i, x);
}
delete[] is;
}
~Seg(){
delete dif;
delete pre;
}
void update(int s, int t, ll v){
dif->add(s, v);
dif->add(t, -v);
pre->add(s, v*s);
pre->add(t, -v*t);
}
ll query(int s, int t){
if (s>0) return query(0, t) - query(0, s);
ll ps = pre->sum(0, t);
ll ds = dif->sum(0, t);
return ds*t - ps;
}
};
void solve(int n, int m, int q){
Seg seg(n);
int l = 0;
for (int i = 0; i < q; i++){
int query;
cin >> query;
l = query - 1;
cout << seg.query(l, l + m) << 'n';
seg.update(l, l + m, -1);
}
}
int main(int agrc, char *agrv[]) {
int n, m, q;
while (cin >> n >> m >> q){
solve(n, m, q);
}
}

poj1144 Network——图论,求割点

传说中的模板题。

Tarjan算法求割点。

Tarjan算法是求割点算法里边一种十分易于理解的算法。我们知道一个无向图在dfs的过程中会产生一棵生成树,剩下的弦必然不会跨越子树,所以我们通过一遍dfs就能记录下一个点能够返回的它最早的祖先的编号。

对于某个点,如果它以及它的子孙结点不能返回它祖先的结点,那么这个点很明显就是割点。


网上的很多题解都没有讲清楚这题什么意思,而这题的输入也十分的坑爹,再加上我是用VC写的。于是一次CE、一次RE、第三次AC(←虽然代码本身没问题)。

这份代码在图论题目里是十分有代表性的。

题目的意思就是简单的让你求割点的数目。

输入的第一个数字代表点的个数。

然后的几行里面,每行的第一个数字代表第n个点,它们联通到之后的数字的点上。

如果第一个数字为0,代表这组数据结束了。

如果点的个数为0,表示输入结束了。

1
2
3
5
5 1 2 3 4
0

对于上面这组数据,表示有5个点,其中第5个点连接到第1、2、3、4个点。


这就是坑了我很久的地方。。。。

下面是我的AC代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
#include <iostream> 
#include <vector>
#include <sstream>
#include <string>
#include <algorithm>
#include <cstring>
using namespace std;
struct info{
int len;
int dclock;
int *cut;
int *low;
int *dfn;
info(int n){
len = n;
dclock = 1;
cut = new int[n];
low = new int[n];
dfn = new int[n];
memset(cut, 0, sizeof(int)*len);
memset(dfn, -1, sizeof(int)*len);
}
~info(){
delete[] cut;
delete[] low;
delete[] dfn;
}
};
void dfs(info &tree, vector< vector<int> > &map, int u, int fa){
int son = 0;
tree.dfn[u] = tree.low[u] = tree.dclock++;
for (int i = 0; i < map[u].size(); i++){
int v = map[u][i];
if (tree.dfn[v]<0){
dfs(tree, map, v, u);
son++;
tree.low[u] = min(tree.low[u], tree.low[v]);
if ((fa < 0 && son > 1) (fa >= 0 && tree.low[v] >= tree.dfn[u]))
tree.cut[u] = 1;
}
else if (tree.low[u] > tree.low[v] && v != fa){
tree.low[u] = min(tree.low[u], tree.dfn[v]);
}
}
}
int solve(int n){
vector<vector<int>> map;
map.resize(n);
int s, u;
while (1){
string temp;
getline(cin, temp);
istringstream buf(temp);
buf >> s;
if (s == 0) break;
while (buf >> u){
map[s - 1].push_back(u - 1);
map[u - 1].push_back(s - 1);
}
}
info tree(n);
for (int i = 0; i < n; i++){
if (map[i].size()>0){
dfs(tree, map, i, -1);
break;
}
}
int cnt = 0;
for (int i = 0; i < n; i++){
if (tree.cut[i] == 1)
cnt++;
}
return cnt;
}

int main(int agrc, char *agrv[]){
int n;
while (cin >> n, n){
cout << solve(n) << endl;
}
}

C# Scanner类

C#没有Java那样好用的Scanner类。于是我在需要读入一堆数字的时候,我是这么写的:

1
2
3
4
//以下代码从标准输入流中读取空格分隔的两个数 a b 
string[] tempInput = Console.ReadLine().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int a = int.Parse(tempInput[0]);
int b = int.Parse(tempInput[1]);

于是做题的时候我觉得太麻烦了,就仿照Java的Scanner功能实现了一个Scanner类。然后根据需要增加了几个奇怪的函数。

以下是我的Scanner类:
(注释采用了VS的智能感知标记,这样可以在使用这些方法时从补全框中看到方法描述等信息)

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
class Scanner 
{
Queue<string> buffer;

///<summary>构造函数?它不需要构造函数,构造函数也没有参数。</summary>
public Scanner(){}

///<summary>清除输入流缓冲。如果本行被读出一部分,此方法会清除这行的其他部分。</summary>
public void Ignore()
{
buffer.Clear();
}
protected void eat(string s)
{
string[] tempInput = new Regex("(\s+)").Split(s);
buffer = new Queue<string>(tempInput);
buffer.Enqueue("");
}

///<summary>
/// 从输入流中读取下一行,如果本行已经被读出一部分,此方法将直接读出其余的所有内容。
/// 此方法执行后会清空流缓冲,所以不需要再执行<c>Ignore()</c>方法。
///</summary>
public string NextLine()
{
if (buffer.Count == 0) return Console.ReadLine();
else
{
StringBuilder temp = new StringBuilder();
foreach (string k in buffer)
{
temp.Append(k);
}
buffer.Clear();
return temp.ToString();
}
}

///<summary>
/// 该方法返回一个布尔值,表示输入流是否结束。
/// 在输入流中没有其他内容时会返回false
///</summary>
public bool HasNext()
{
if (buffer==nullbuffer.Count == 0)
{
string s = Console.ReadLine();
if (s == null) return false;
eat(s);
}
return true;
}

///<summary>
/// 该方法返回下一个字符串。由任意可能的空白符号分隔。
///</summary>
public String Next()
{
HasNext();
string t = buffer.Dequeue();
buffer.Dequeue();
return t;
}

///<summary>
/// 该方法返回输入流中接下来的一个32位整数。如果接下来的字符不能表示一个整数,将抛出FormatException异常。
///</summary>
///<exception cref="System.FormatException">字符串不能解析为数字</exception>
public int NextInt()
{
return int.Parse(Next());
}

///<summary>
/// 该方法返回输入流中接下来的一个64位整数。如果接下来的字符不能表示一个整数,将抛出FormatException异常。
///</summary>
///<exception cref="System.FormatException">字符串不能解析为数字</exception>
public long NextLong()
{
return long.Parse(Next());
}

///<summary>
/// 该方法返回输入流中接下来的一个浮点数。如果接下来的字符不能表示一个数,将抛出FormatException异常。
///</summary>
///<exception cref="System.FormatException">字符串不能解析为数字</exception>
public double NextDouble()
{
return double.Parse(Next());
}

///<summary>
/// 该方法将下一行按空白字符分隔,并以字符串数组形式返回。
/// 如果输入流中一行已经被读出一部分,该方法将解析并返回剩余的部分。
///</summary>
public string[] NextArray()
{
return new Regex("\s+").Split(NextLine());
}

///<summary>
/// 该方法将下一行按空白字符分隔的整数解析成32位整型数组并返回。
/// 如果输入流中一行已经被读出一部分,该方法将解析并返回剩余的部分。如果存在字符不能表示一个数,将抛出FormatException异常。
///</summary>
///<exception cref="System.FormatException">字符串不能解析为数字</exception>
public int[] NextIntArray()
{
string[] s=NextArray();
int[] n = new int[s.Length];
for (int i = 0; i < s.Length; i++)
{
n[i] = int.Parse(s[i]);
}
return n;
}

///<summary>
/// 该方法将下一行按空白字符分隔的整数解析成64位整型数组并返回。
/// 如果输入流中一行已经被读出一部分,该方法将解析并返回剩余的部分。如果存在字符不能表示一个数,将抛出FormatException异常。
///</summary>
///<exception cref="System.FormatException">字符串不能解析为数字</exception>
public long[] NextLongArray()
{
string[] s = NextArray();
long[] n = new long[s.Length];
for (int i = 0; i < s.Length; i++)
{
n[i] = long.Parse(s[i]);
}
return n;
}

///<summary>
/// 该方法将下一行按空白字符分隔的数字解析成浮点数组并返回。
/// 如果输入流中一行已经被读出一部分,该方法将解析并返回剩余的部分。如果存在字符不能表示一个数,将抛出FormatException异常。
///</summary>
///<exception cref="System.FormatException">字符串不能解析为数字</exception>
public double[] NextDoubleArray()
{
string[] s = NextArray();
double[] n = new double[s.Length];
for (int i = 0; i < s.Length; i++)
{
n[i] = double.Parse(s[i]);
}
return n;
}
}

以下提供了一个snippet文件,在VS中,使用(工具->)代码段管理器导入这个文件之后,可以在代码中键入Scanner按两次Tab来插入这段代码。

https://github.com/zyzsdy/acm/blob/master/CSharp/Scanner.snippet

(请注意以上文件和上面的代码有一些微小的差异,使用上面的代码需要手动插入using System.Text.RegularExpressions;一句。)

标准的用法是类似这样:

1
2
3
4
5
static Scanner cin = new Scanner(); 
static void Main(string[] args)
{
cin.NextInt();
}