Archive for category ACM/ICPC
比赛的意义
今天是2011 World Finals ACM/ICPC的比赛日,晚上10点开始看比赛的直播,看着每个学校的队伍的排名交错的攀升,看到HDU-Knuth的两题AC,两题WA,突然脑海闪回过自己的大二暑假,背上出了点汗,手臂上起了鸡皮疙瘩。
杭电进World Finals已经不是第一次,但是今年退役后看比赛跟去年在役看比赛是两种心情。在役时似乎更多的是不甘心和一些小嫉妒,因为我显然WF无望。之前自然也知道想进World Finals很难很难,但还是难免偶尔会狂妄的暗暗把这当作自己的目标。现在的我,对自己宽容了,内心平静了,但有时候也怀疑自己是不是失去了激情和进取心。
记得以前北大的CICI姐姐写过一篇日志,大致是说她在World Finals看到一个非洲的大学的队伍,只AC两题,却在赛后激动的跟领队拥抱欢呼。虽然她一个人也可以做出两题,但是她却没有那些人的快乐。
想到这里,我释然了,ACM/ICPC竞赛可以给我的,我都已经得到了。
图论图论!
没错,就是图论!要好好练!
图论旅程第一题 : POJ 2337 Catenyms
5551478 clumsydragon 2337 Accepted 200K 16MS C++ 1775B
以单词的开头字母和结尾字母为点,单词为边,找一条欧拉路。先计算每个点的度数,判断能否构成一条欧拉路,然后再用深搜寻找答案。
Ural 1129 Door painting
这题应该MS是利用图论的性质来求解。题目要求无向图的每条边两端各涂绿或黄色(两端不同),且一个点内两种颜色数量相差不能超过1。如果这张图能构成一个欧拉回路,假设每条边的头为黄尾为绿,按照欧拉回路来遍历,那么每个点的黄绿两色数量都恰好相等;如果这张图无法构成欧拉回路,那么可以在图中加边来使他满足,因为每个点最多被加一条边,那么拿去这条多余的边,点内黄绿两色的数量变为相差1。我的做法就是先加边,然后dfs把每条路上色。
最小树形图:
对图中的每个点取权值最小的入边,若这个边集组成的图中若没有圈,这得到该图的最小树形图;否则对每个圈进行缩点。缩点中,涉及到对重点在圈内的弧的权值的处理。圈内的边权全部加到最小树形图的代价上,指向圈的入弧比如Arc[i][j] 则减去 Arc[pre[j]][j], 这里pre[j]是圈中的j点的前驱。然后对缩点后的图继续上述的步骤,一直到边集中再也没有圈为止。这些所选的边的边权之和,加上之前已经得到的边权就可以得到最小树形图的总代价了。
突然有个想法,zl算法在缩点前和缩点后的状态之间权值变化的计算,是不是有DP的思想呢?
最小树形图具体的题目有 :
TJU 2248 Channel Design
PKU 3164 Command Network
UVa 11183 Teen Girl Squad
上面三题都是固定节点的最小树形图,没有固定节点的最小树形图也可以转化成固定节点的方法来做。
HDU 3072 Intelligence System
这也是一题比较有意思的最小树形图。先用Kosaraju算法找出所有强连通分量并缩点,然后再做最小树形图。
贴下HDU 3072的代码:
- #include <algorithm>
- #include <iostream>
- #include <cstdlib>
- #include <cstring>
- #include <bitset>
- #include <string>
- #include <vector>
- #include <cctype>
- #include <cstdio>
- #include <ctime>
- #include <queue>
- #include <stack>
- #include <cmath>
- #include <list>
- #include <map>
- #include <set>
- using namespace std;
- #define inf 0x7fffffff
- #define sz 110
- vector <int> VS[50010];
- #define NMAX 51000
- vector<int> path[NMAX];
- vector<int> npath[NMAX];
- int n,m, scc;
- int order[NMAX], order_pos, id[NMAX];
- bool vis[NMAX];
- int pre[sz];
- int del[sz];
- bool visited[sz];
- void dfs(int pos)
- {
- int i,j,l;
- vis[pos] = true;
- l = path[pos].size();
- for (i=0;i<l;i++) {
- j = path[pos][i];
- if (!vis[j]) {
- dfs(j);
- }
- }
- order[ order_pos ++ ] = pos;//make order
- }
- void ndfs(int pos)
- {
- int i,j,l;
- vis[pos] = true;
- id[pos] = scc;
- VS[scc].push_back(pos);
- l = npath[pos].size();
- for (i=0;i<l;i++) {
- j = npath[pos][i];
- if (!vis[j]) {
- ndfs(j);
- }
- }
- }
- void Kosaraju()
- {
- int i,j;
- //dfs in original graph
- memset(vis, 0, sizeof(vis));
- order_pos = 0;
- for (i=0; i<n ;i++) {
- if (!vis[i]) {
- dfs(i);
- }
- }
- //dfs in inverse graph
- memset(vis, 0, sizeof(vis));
- memset(id, 0, sizeof(id));
- scc = 0;
- for (i=order_pos-1; i>=0 ;i--) {
- if (!vis[ order[i] ]) {
- ndfs(order[i]);
- scc ++;
- }
- }
- scc --;
- }
- int hash[50010];
- struct node{
- int ni, nj, val;
- };
- node ainfo[100010];
- int val[110][110];
- struct edge{
- int no;
- int vt[400];
- edge() { no = 0; }
- inline void clear() { no = 0; }
- inline int operator [] (const int &x) {
- return vt[x];
- }
- inline void push(const int &x) {
- vt[no ++] = x;
- }
- };
- edge arc[sz], rarc[sz];
- inline void modify(int &x, const int &y) {
- x = x < y ? x : y;
- }
- inline int minTree(int root, int n) {
- memset(del, 0, sizeof(del));
- memset(pre, -1, sizeof(pre));
- int i, j, k, np, op, mi, ret = 0, last = 0;
- bool fg;
- while(1) {
- for(i = 0; i < n; i++) {
- if(i == root || del[i]) continue;
- fg = 0;
- for(j = 0; j < rarc[i].no; j++) {
- if(del[rarc[i][j]]) continue;
- op = rarc[i][j];
- if(!fg || mi > val[op][i]) {
- mi = val[op][i];
- np = op;
- if(!fg) fg = 1;
- }
- }
- if(fg) pre[i] = np;
- }
- for(i = last; i < n; i++) {
- if(i == root || del[i]) continue;
- last = i;
- memset(visited, 0, sizeof(visited));
- for(j = pre[i]; j != root && !visited[j]; j = pre[j]) {
- visited[j] = 1;
- }
- if(j == root) continue;
- i = j;
- do {
- ret += val[pre[j]][j];
- del[j] = 1;
- j = pre[j];
- } while(j != i);
- j = i;
- do {
- for(k = 0; k < rarc[j].no; k++) {
- if(del[rarc[j][k]]) continue;
- op = rarc[j][k];
- if(val[op][i] == inf) {
- rarc[i].push(op);
- arc[op].push(i);
- val[op][i] = val[op][j] - val[pre[j]][j];
- }
- else
- modify(val[op][i], val[op][j] - val[pre[j]][j]);
- }
- for(k = 0; k < arc[j].no; k ++) {
- if(del[arc[j][k]]) continue;
- op = arc[j][k];
- if(val[i][op] == inf) {
- arc[i].push(op);
- rarc[op].push(i);
- val[i][op] = val[j][op];
- }
- else
- modify(val[i][op], val[j][op]);
- }
- j = pre[j];
- } while(j != i);
- del[i] = 0;
- break;
- }
- if(i >= n) {
- for(i = 0; i < n; i++) {
- if(i == root || del[i]) continue;
- ret += val[pre[i]][i];
- }
- return ret;
- }
- }
- return ret;
- }
- int main()
- {
- while (scanf("%d %d" , &n , &m) == 2)
- {
- int i, j;
- for (i=0 ; i < n ; i ++){
- VS[i].clear();
- path[i].clear();
- npath[i].clear();
- }
- scc = 0;
- int ta , tb;
- for (i=0 ; i < m ; i++){
- scanf("%d %d %d" , &ta , &tb, &ainfo[i].val);
- ainfo[i].ni = ta;
- ainfo[i].nj = tb;
- path[ta].push_back(tb);
- npath[tb].push_back(ta);
- }
- Kosaraju();
- scc ++;
- for(i = 0; i < scc; i++) {
- for(j = 0; j < VS[i].size(); j++) {
- hash[VS[i][j]] = i;
- //printf("%d ", VS[i][j]);
- }
- //printf("\n");
- }
- for(i = 0; i < scc; i++) {
- for(j = 0; j < scc; j++) {
- val[i][j] = inf;
- }
- arc[i].clear();
- rarc[i].clear();
- }
- for(i = 0; i < m; i++) {
- ta = hash[ainfo[i].ni];
- tb = hash[ainfo[i].nj];
- if(val[ta][tb] == inf) {
- arc[ta].push(tb);
- rarc[tb].push(ta);
- modify(val[ta][tb], ainfo[i].val);
- }
- else
- modify(val[ta][tb], ainfo[i].val);
- }
- for(i = 0; i < scc; i++) {
- val[i][i] = inf;
- }
- int root = hash[0];
- int ret = minTree(root, scc);
- printf("%d\n", ret);
- }
- return 0;
- }
AC自动机
AC自动机是KMP算法和Trie数据结构的结合,重点主要在KMP算法的理解。我觉得AC自动机是我学过的最精妙和犀利的算法。
AC自动机的题目有蛮多的,我只做了两题最简单的 :
Keywords Search
最单纯的AC自动机
Word Puzzles
也比较简单,不过我的做法是把单词正反两个方向都建立了Trie,这样后面查找的量能减少一半,在POJ上比我只建立一个方向的代码的效率快了1/3。不过要注意的是,本来keywords都是唯一的,但是建两个方向以后就不唯一了。顺便贴下这题的代码。
- #include <iostream>
- #include <cstring>
- #include <queue>
- #include <cstdio>
- #include <cstdlib>
- #include <cmath>
- using namespace std;
- #define sz 197260
- int dir[8][2] = {-1, 0, -1, 1, 0, 1, 1, 1, 1, 0, 1, -1, 0, -1, -1, -1};
- int rx[1011], ry[1011], rd[1011], len[1011];
- char text[1001][1010];
- bool vsd[1011];
- int afcg[1011];
- int next[sz][26], pos;
- int fa[sz], fp[sz], isword[sz], val[sz], id[sz];
- int r, c, zf, rm;
- inline bool valid(const int &x, const int &y) {
- return (x >= 0 && x < r && y >= 0 && y < c);
- }
- inline void build(const int &cur, char *st, const int &k) {
- int op = *st - 'A';
- int ps;
- if(!next[cur][op]) {
- ps = pos;
- next[cur][op] = pos ++;
- val[ps] = op;
- fa[ps] = cur;
- }
- else
- ps = next[cur][op];
- if(*(st + zf) == '\0') {
- if(isword[ps]) {
- if(zf > 0) afcg[k] = -id[ps];
- }
- else {
- isword[ps] = 1;
- id[ps] = k * zf;
- }
- }
- else
- build(ps, st + zf, k);
- }
- inline void bfs() {
- int i, j, op, now, nxt, root = 0;
- fp[root] = -1;
- queue <int> qq;
- while(!qq.empty()) qq.pop();
- for(i = 0; i < 26; i++) {
- if(next[root][i])
- qq.push(next[root][i]);
- }
- while(!qq.empty()) {
- now = qq.front(), qq.pop();
- nxt = fa[now];
- op = val[now];
- do{
- nxt = fp[nxt];
- }while(nxt != -1 && !next[nxt][op]);
- if(nxt == -1)
- fp[now] = root;
- else
- fp[now] = next[nxt][op];
- for(i = 0; i < 26; i++) {
- if(next[now][i])
- qq.push(next[now][i]);
- }
- }
- }
- inline void search(const int &x, const int &y, const int &k) {
- int tx = x, ty = y;
- int op, tmp, dd, cur = 0;
- while(valid(tx, ty)) {
- op = text[tx][ty] - 'A';
- while(cur != -1 && next[cur][op] == 0) {
- cur = fp[cur];
- }
- if(cur == -1) cur = 0;
- else {
- cur = next[cur][op];
- tmp = cur;
- while(tmp != -1) {
- if(isword[tmp]) {
- dd = abs(id[tmp]);
- if(vsd[dd]) break;
- if(id[tmp] < 0) {
- rx[dd] = tx;
- ry[dd] = ty;
- rd[dd] = (k + 4) & 7;
- }
- else {
- rx[dd] = tx - dir[k][0] * (len[dd] - 1);
- ry[dd] = ty - dir[k][1] * (len[dd] - 1);
- rd[dd] = k;
- }
- vsd[dd] = 1;
- rm --;
- }
- tmp = fp[tmp];
- }
- }
- tx += dir[k][0];
- ty += dir[k][1];
- }
- }
- inline void init(const int &cur) {
- for(int i = 0; i < 26; i++) {
- if(next[cur][i]) {
- init(next[cur][i]);
- next[cur][i] = 0;
- }
- }
- isword[cur] = 0;
- }
- int main()
- {
- freopen("ACM.in", "r", stdin);
- freopen("ACM.out", "w", stdout);
- char ln[1500];
- int n, root = 0;
- int i, j;
- while(scanf("%d %d %d", &r, &c, &n) != EOF) {
- getchar();
- for(i = 0; i < r; i++) {
- gets(text[i]);
- }
- init(root);
- pos = 1;
- for(i = 1; i <= n; i++) {
- gets(ln + 1);
- afcg[i] = i;
- ln[0] = '\0';
- len[i] = strlen(ln + 1);
- zf = 1;
- build(root, ln + 1, i);
- zf = -1;
- build(root, ln + len[i], i);
- }
- rm = n;
- bfs();
- memset(vsd, 0, sizeof(vsd));
- int tr = r - 1, tc = c - 1;
- for(i = 1; i < 5; i++) {
- switch(i)
- {
- case 1:
- for(j = 0; j < c; j++) {
- search(tr, j, i);
- }
- case 2:
- for(j = 0; j < r; j++) {
- search(j, root, i);
- }
- break;
- case 3:
- for(j = 0; j < r; j++) {
- search(j, root, i);
- }
- case 4:
- for(j = 0; j < c; j++) {
- search(root, j, i);
- }
- break;
- }
- }
- for(i = 1; i <= n; i++) {
- if(afcg[i] != i) {
- j = afcg[i];
- rd[i] = (rd[j] + 4) % 8;
- rx[i] = rx[j] + (len[j] - 1) * dir[rd[j]][0];
- ry[i] = ry[j] + (len[j] - 1) * dir[rd[j]][1];
- }
- printf("%d %d %c\n", rx[i], ry[i], rd[i] + 'A');
- }
- }
- return 0;
- }
ZOJ 3209 Treasure Map
精确覆盖问题,看着DLX算法的伪码打的。
以格子为行,矩形为列,构造一个描述覆盖情况的矩形,用DLX算法求解。
其他可以用dancing links求解的题目还有
nuaa 1267 N皇后问题
hust 1017 Exact Cover
23849 clumsydragon 1017 Accepted 1872 kb 256 ms C++ 2253 B 2009-07-10 13:37:56
spoj 1771 Yet Another N-Queen Problem
2559174 2009-07-12 07:19:00 CD Yet Another N-Queen Problem accepted 0.36 2.6M C++
tju 3219 Radar
fzu 1686 神龙的难题
神龙的难题允许overlap的覆盖,写了3遍还是TLE,先放一放
贴下3209的代码
- #include <iostream>
- #include <cstring>
- using namespace std;
- #define sz 451451
- int l[sz], r[sz], u[sz], d[sz], y[sz];
- int s[961];
- int n, m, ret;
- inline void remove(const int &c) {
- int i, j;
- r[l[c]] = r[c];
- l[r[c]] = l[c];
- for(i = d[c]; i != c; i = d[i]) {
- for(j = r[i]; j != i; j = r[j]) {
- d[u[j]] = d[j];
- u[d[j]] = u[j];
- s[y[j]] --;
- }
- }
- }
- inline void resume(const int &c) {
- int i, j;
- for(i = u[c]; i != c; i = u[i]) {
- for(j = l[i]; j != i; j = l[j]) {
- d[u[j]] = j;
- u[d[j]] = j;
- s[y[j]] ++;
- }
- }
- r[l[c]] = c;
- l[r[c]] = c;
- }
- inline int dfs(int k) {
- if(ret <= k)
- return 0;
- int i, j, c;
- if(r[0] == 0) {
- if(k < ret)
- ret = k;
- return 1;
- }
- int mi = 0x7FFFFFFF;
- for(i = r[0]; i; i = r[i]) {
- if(s[i] < mi) {
- mi = s[i];
- c = i;
- }
- }
- remove(c);
- for(i = d[c]; i != c; i = d[i]) {
- for(j = r[i]; j != i; j = r[j]) {
- remove(y[j]);
- }
- dfs(k + 1);
- for(j = l[i]; j != i; j = l[j]) {
- resume(y[j]);
- }
- }
- resume(c);
- return 0;
- }
- int main()
- {
- int t, tn, tm, t1, t2, pos;
- int i, j, k;
- int x1, y1, x2, y2;
- scanf("%d", &t);
- while(t --) {
- scanf("%d %d %d", &tn, &tm, &n);
- m = tn * tm;
- for(i = 0; i <= m; i++) {
- if(i + 1 > m)
- t1 = 0;
- else
- t1 = i + 1;
- r[i] = t1, l[t1] = i;
- u[i] = i;
- s[i] = 0;
- }
- int hd;
- t2 = m + 1;
- for(i = 1; i <= n; i++) {
- scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
- hd = t2;
- x2 --, y2 --;
- for(j = y1; j <= y2; j++) {
- pos = j * tn + x1 + 1;
- for(k = x1; k <= x2; k++) {
- t1 = t2;
- if(j == y2 && k == x2)
- t2 = hd;
- else
- t2 ++;
- u[t1] = u[pos], d[u[t1]] = t1;
- u[pos] = t1, s[pos] ++;
- r[t1] = t2, l[t2] = t1;
- y[t1] = pos;
- pos++;
- }
- }
- t2 = t1 + 1;
- }
- bool fg = 1;
- for(j = 1; j <= m; j++) {
- if(!s[j]) {
- fg = 0;
- break;
- }
- d[u[j]] = j;
- }
- if(!fg)
- printf("-1\n");
- else {
- ret = 0x7FFFFFFF;
- dfs(0);
- if(ret == 0x7FFFFFFF)
- printf("-1\n");
- else
- printf("%d\n", ret);
- }
- }
- return 0;
- }
- /*
- 2
- 5 10 9
- 0 0 5 1
- 0 1 5 2
- 0 2 5 3
- 0 3 5 4
- 0 4 5 5
- 0 5 5 10
- 2 3 5 10
- 0 5 2 10
- 0 3 2 5
- 30 30 8
- 0 0 11 30
- 11 0 30 11
- 11 11 20 20
- 11 20 20 30
- 20 11 30 30
- 0 0 11 11
- 0 11 11 30
- 0 0 30 20
- */
省赛总结
| rank | name | solved | A | B | C | D | E | F | G | H | I | J | K | 罚时 |
| 15 | HDU-Firebirds | 7 | 13 (1) | 144 (3) | 138 (1) | 7 | 0 | 22 (1) | 0 | 0 | 24 (1) | 121 (2) | 66 (2) | 608 |
省赛对我来说是入队以来最重要的比赛,因为直到那天我都还没想好省赛后是否退役,说不定这就是我最后一场比赛了(明年省赛不算,如果退役了,相信那时候也只是抱着旅游的态度了)。省赛前夜11点就整理完东西,洗漱上床了,但一直醒到2点多。
第二天6点半起来了,感觉有点睡眠不足,不过还算蛮精神的。上午的东西都直接略过,下午在机房门口等比赛开始的时候,发现去年杭州赛区的主场作战真的是蛮大的优势。
比赛开始我猜C是ms题,然后看了题目,是跟通常情况有点不同的MST,我们队不熟prim,又不会证明kruscal肯定可以得到正解,于是我放下C看I,并发现是简单题上机敲。敲完sample出不来,于是打印。Teddy上机敲A,在无声无息中过掉了。我发现自己I想复杂了,上去改了下,还是有问题。甘露上机敲F,期间我发现I的一个bug,甘露过掉F后,我上去改了I,提交后Yes。
过完I,看了下statistics,B和K都很多人过,我推了下B,是一个
的函数,忘掉怎么求最小值,直接扔给斌哥了。然后看K,简单构造题,上机打完后,Teddy对我的解法不是很肯定,造了几组数据测了下,然后提交CE……崩溃,赛前测编译器,好像就是平常poj的g++编译器,没想到还是出现这种错误。然后加了头文件,返回Yes。wyb上机打B。
接着看了下J,很熟悉的题目,记得是一个DP。不过Teddy对于这题的解法记忆不是很清晰了,我想了个简单的解法,换下wyb实现了下,过不了造的数据。这时teddy记起来解法了,换下wyb敲J。我帮斌哥一起看B,另外想了个解法,不过到最后还是跟斌哥已经wa的做法一样,感觉有两种可能,一种是情况没考虑全,一种是精度问题,然后一直在讨论。teddy过了J后,对C还是拿不定,我觉得还是应该试下kruscal。甘露很快敲好了C,提交以后居然Yes了……昏倒。接着wyb上去改了一下B,也过掉了。
这时候过了两个钟头多,我们的rank好像是在11,但是罚时没有优势。我们都很清楚要稳拿金牌一定要再过一题,而且剩下时间还蛮充裕的。wyb最先开了D题,我和甘露讨论了G。但是我图论实在烂,在读过几乎全部题目后(除了D)决定用搜索开H。wyb用暴力打好D,提交后tle,然后甘露上机敲G,敲到一半他觉得不肯定,换我上机敲H =,=||
赛后想起来这段时间真的是在梦游,我敲H的时候心里很虚,但是还是觉得不应该放弃有想法的题目,敲完大部分代码。敲到没想清楚的地方后,换上wyb继续敲D。我记得这时候已经剩下一个钟头不到的时间了,于是和teddy都渐渐放弃了自己的题目,和wyb一起做D。这题他用了上次宁波刚从DD牛那里学到的逆元来处理做除法取模,本来我们都以为这题会很有希望的,没想到就一直这么修修改改到比赛结束。
时间还是无情的指向了5:25。我们已经明白金牌无望,伤心下一直在抱怨题目区分度不好。但是冷静下来想想,这真的只是题目的原因吗?我们队还是在比赛策略上有很大的失误:
一是C题太犹豫。Kruscal只需要5分钟的机时,但是我们一直到2个多钟头才下决心去试,是对比赛形势的错误估计。这种比赛,有6、7个队过掉了就应该果断的尝试。
二是7题以后的各自为战。我们队的优势是互补,平常比赛难题也都是合作解出的。
三是最后合力攻D时,阵脚已经乱了。两个人不去好好读题,却不停修改代码以及问wyb各种问题,影响了他深入思考。到比赛结束teddy还没有理解题意,我还没有读懂wyb的解法,最后一次提交代码的版本都搞乱了。这种时候,应该有一个人出来稳定整个队的情绪。
四是练习赛没有把握好测环境的机会,导致CE的出现。这是经验和总结的欠缺。
银牌实在是令人遗憾,但也是真实水平的反映。每一次比赛都是绝好的积累经验和反省的机会,希望再通过一个暑假的集训,Firebirds能拿到让我们三个人都满意的成绩。









