|
常用链接
留言簿
随笔分类
随笔档案
文章分类
搜索
最新评论
阅读排行榜
评论排行榜
Powered by: 博客园
模板提供:沪江博客
|
|
|
|
|
发新文章 |
|
|
置顶随笔
有朋友真好。 其实有时候只是想说说话,不是内心真的有疑问想寻求办法,因为答案其实就在自己心中,自己心里明白得很......但人就是这样,就算心里明白,也还是觉得憋,想找个人倾述。所以这时候,有朋友真好。朋友可以无偿地陪你,听你说话,跟你说话。 我今天其实也没发生什么,只是想了一些东西,心情就变得不太愉快,但并不是不愉快,只是很想有个人骂一下自己!骂自己的不成熟,骂自己没毅力,骂自己禁受不住挫折。然后我就找秦程聊天,聊着聊着,就释怀了,就开朗了。 有朋友真好。我没有好的文笔给解释这句话。 我对亲近的人都不轻易说谢谢,但今天我对秦程说了声谢谢,因为今天打电话的时候我想到blue 跟我说的一句话:有朋友真好。
2013年5月31日
差分约束(difference constraints),对,两个关键字要理解好,“difference”简单理解就是两个节点的“差”,对应的就是图中的边权,而“约束”对应的是图的边。这个图的边权不一定都是正数,之前我一直很奇怪为什么做最短路的时候初始化dis[]为了0也可以,那是因为我没意识到边权可以为负数,而思维定势地想初始化dis[]为0,那0不就是最小路径了吗,但这里差分约束的最短路径常常是负数的,所以最短路径可以不是0!! 看网上讲解的时候要小心,很多人把最长路和最短路是不分的,乱死了。 还有很重要的一点很多人没区分开, 求最小可行解 ==> 把不等式划为dv >= dx + Z的形式,即建立<u,v,Z>边 ==> 可行解要最小,其实就是取约束条件中`最大`的约束 ==> 求最长路 解释:为什么求最小可行解要划成dv >= dx + Z形式?因为这个形式暗指了“让dv尽量小”,因为此刻dv的取值区间为[du+Z, ∞]。 为什么可行解最小,即意味着取最大约束条件?这样想,如果有dv >= du + Z1, dv >= du + Z2,(Z1<Z2),那dv的最小取值就是du+Z2,因为du+Z1不满足第二个约束条件。 最后一步就好理解了,因为建图的边权就是约束值,既然上一步指要取最大约束,那当然是求最长路啦。 网上很多讲解没有区分开所谓的最大/最小,一会儿指可行解的最,一会儿指约束条件的最,弄得我乱了好久。 顺便贴一下: 求最大可行解 ==> 把不等式划为dv <= dx + Z的形式,即建立<u,v,Z>边 ==> 其实就是取约束条件中`最小`约束 ==> 求最短路 关于源点:很多时候额外价格源点可以帮我们把一个非连通图变成连通图,而对于源点的不等式,一定要和你之前建边时的不等式形式一样,如果之前是dv >= du + Z,那源点也要dv >= d0 + xxx。这个xxx就是dis[]的初始值,关于如何选取xxx,下面两句话摘自百度百科: “ 1.如果将源点到各点的距离初始化为0,最终求出的最短路满足 它们之间相互最接近了 2.如果将源点到各点的距离初始化为INF(无穷大),其中之1为0,最终求出的最短路满足 它们与该点之间相互差值最大。 ” 差分约束题目我一般是用SPFA+栈,为什么不用dijkstra+heap?因为dijkstra不能处理负环,而我们的题目可能有负环,所以干脆都用SPFA了,多数条件下,用stack的SPFA比用queue的快,why?因为常常地,用最先更新了的点去更新其它点,效果比用以前已经更新了的点(在queue的tail)好。 差分约束专题:http://972169909-qq-com.iteye.com/blog/1185527 poj 1201 差分约束 题意:求符合题意的最小集合Z的元素个数,约束条件:i j C,表区间[i,j]至少有C个Z集合的元素。 隐含条件是,S区间是个连续的数字区间,0 <= s[i+1] - s[i] <= 1,其中s[i]表0~i中有多少数字是Z集合元素。下面是隐含条件的建边。 for(int i = 0; i < 50001; i++) { //@ vert[i].push_back(i+1); edge[i].push_back(0); vert[i+1].push_back(i); edge[i+1].push_back(-1); } poj 1364 差分约束 题意:约束条件:i, n, op, K --> op分greater和less,需要满足Si + S[i+1] + S[i+2] + ... + S[i+n] > K (或小于) 因为我是用dis[i]表示S0+S1+...+Si的和,所以<u,v,w>应该表示的意思是sum[v]-sum[u-1] = w,所以这里0也是一个点,所以源点不能取0! //@ ?? 我在SPFA后面输出了dis[]数组来看,这些值并不符合题目的要求,那为什么整个程序是对的?如果要输出一个解的话怎么写? 答:因为这里的dis[i]表示的是s1+s2+...+si的和,用第一个样例来说, sample input: 4 2 1 2 gt 0 2 2 lt 2 输出的dis[0--n] : -1 0 0 0 0 s1+s2+s3 = sum[3] - sum[1-1] = dis[3] - dis[0] = 1 , 满足gt 0 s2+s3+s4 = sum[4] - sum[2-1] = dis[4] - dis[1] = 0 , 满足lt 2 所以,{si}的一组解应该为0,1,0,0,0. poj 1983 差分约束 题意:给出两种约束条件,一种是P A B X,意为精确地约束A比B远X个单位;另一种V A B,意为模糊地约束A至少比B远1个单位。是否有可行解? 好点:两种约束条件,其中Precise约束可以转换为X <= A-B <= X 有V i i 这种数据,这种数据在SPFA里会WA,在ballman_ford里AC,不过预处理一下就可以了,还是用SPFA. hdu 3666 差分约束 题意:给出矩阵{Cij},问是否存在数列{A} 和 {B},使得对于矩阵内所有Xij满足: L <= Xij * Ai / Bj <= U 构图。用log把乘除变成加减,就可以差分约束来做了。我用的是SPFA+stack求最短路,最长路应该也是可以的。没有建源点,直接一开>始把所有点push进去... 14xx ms 过挺险的~
2013年5月28日
做第一道查分约束poj 3159...TLE不断。估计管理员是跟C++的STL过不去,就卡用vector的人了。。而我已经好久好久没人工写什么邻接表,前向星什么的了,就只能TLE过不了了。。换了什么dijkstra+优先队列,SPFA+stack优化,只要用vector来存边,估计都过不了。 下面是TLE代码,留纪念。 #include <string.h> #include <stdio.h> #include <vector> using namespace std; int n, m; int dis[30001]; bool visit[30001]; int S[30001], S_top = 0; vector<int> vert[30001], edge[30001]; void dijkstra(int s); int main() { int u,v,w; scanf("%d%d", &n, &m); for(int i = 0; i < m; i++) { scanf("%d%d%d", &u, &v, &w); vert[u].push_back(v); edge[u].push_back(w); } dijkstra(1); //最长路-----不是的,是最短路! printf("%d\n", dis[n]); } void dijkstra(int s) { memset(dis, 127, sizeof(dis)); dis[s] = 0; visit[s] = true; S[++S_top] = s; while(S_top > 0) { int u = S[S_top--]; visit[u] = false; for(int Size = vert[u].size(), i = 0; i < Size; i++) { int v = vert[u][i]; if(dis[v] > dis[u] + edge[u][i]) { dis[v] = dis[u] + edge[u][i]; if(visit[v] == false) { S[++S_top] = v; visit[v] = true; } } } } }
2013年5月27日
/* 最短路 好题 题意:给出边建图,然后分别删除各条边,问每一次删边后的所有端点的两两最短路之和,若有一对端点不连通,则返回INF 思路:暴力解法是每次删边后都来n次最短路。这里面的冗余就是删除的边并不影响一些点的最短路树,所以这些点可以不用在删边后都来次dijkstra>。标程解法就是在暴力解法上加上一些剪枝。先预处理出所有点的最短路树,记x的最短路树的和为sum[x]。现在来去掉冗余:在预处理时用used[x][u][v]记录点x的最短路树是否用到了边u--v,则删除边u--v的时候,判断点x的最短路树是否用到边u--v的,若用到,则对x做一次dijkstra,用新的sum[x]表示>当前最短路树;否则用预处理的sum[x]就可以,不用再dijkstra. dijkstra是利用`边权为1`这一特性来加快的版本,具体看:http://www.cppblog.com/keroro/archive/2013/05/27/200621.html 这道题有很多重边,这估计也是考点之一,不好好处理重边的话会超时。 多数题解是错的,因为hdu上的这道题的数据比较水才可以过,可以试试discuss里给的数据,下面这几个题解比较靠谱。 http://blog.csdn.net/nash142857/article/details/8253913 http://www.cnblogs.com/crisxyj/archive/2013/03/10/2952396.html http://hi.baidu.com/novosbirsk/item/bfcf0cd201edfc2d39f6f709 两份代码的思想是完全一样的,只是“baidu blog”那份用w[i][e]来判断i的最短路树是否包括边e,而cnblog的那份是用used[x][u][v]来判断边u-->v是否xxx. */ #include <algorithm> #include <stdio.h> #include < string.h> #include <vector> #include <deque> using namespace std; #define MAXN 101 #define INF 99999999 #define debug printf("!\n") struct Edge { int u,v; } edge[3001]; vector< int> vertex[MAXN]; int visit[MAXN], sum[MAXN], cnt[MAXN][MAXN]; //cnt[u][v]表u--v的边有多少条,用来处理重边 bool used[MAXN][MAXN][MAXN]; //used[x][u][v]x的最短路树是否用到了边u--v int n, m; void init(); void dijkstra( int s, int when, int flag); int main() { int when = 0; int u, v; while(scanf("%d%d", &n, &m) != EOF) { init(); for( int i = 0; i < m; i++) { scanf("%d%d", &u, &v); vertex[u].push_back(v); vertex[v].push_back(u); edge[i].u = u, edge[i].v = v; cnt[u][v]++, cnt[v][u]++; } int ans = 0; for( int i = 1; i <= n; i++) { dijkstra(i, ++when, 1); ans += sum[i]; } for( int i = 0; i < m; i++) { int tmp = ans; int u = edge[i].u, v = edge[i].v; //forbid_u = edge[i].u, forbid_v = edge[i].v; 因为有重边所以不能用这种方法 for( int j = 1; j <= n; j++) if(used[j][u][v] && cnt[u][v] == 1) { //不加cnt[u][v] == 1会超时。。卡的就是重边,靠! int tmp = sum[j]; sum[j] = 0; //vector<int> :: iterator it1 = find(vertex[u].begin(), vertex[u].end(), v); //vector<int> :: iterator it2 = find(vertex[v].begin(), vertex[v].end(), u); //*it1 = u, *it2 = v;
cnt[u][v]--, cnt[v][u]--; dijkstra(j, ++when, 2); cnt[u][v]++, cnt[v][u]++; //*it1 = v, *it2 = u; //本来是用erase的,超时了。 现在换这种方法也超时了,估计find耗时太久。
ans = ans - tmp + sum[j]; //用新的sum[j]来代替旧的tmp
sum[j] = tmp; int k ; for(k = 1; k <= n; k++) if(visit[k] != when) break; //如果删边了以后j不能到达k(即k没有进过队) if(k <= n) { ans = INF; break; } } ans == INF ? printf("INF\n") : printf("%d\n", ans); ans = tmp; //不要把这个tmp和for_j里的tmp混了..
} for( int i = 0; i < m; i++) cnt[edge[i].u][edge[i].v] = cnt[edge[i].v][edge[i].u] = 0; //初始化因为感觉memset(cnt)会不会花更多时间
} return 0; } void dijkstra( int s, int when, int flag) { int floors = 1; int cur = 0; deque< int> Q[2]; Q[cur].push_back(s); visit[s] = when; do { while(!Q[cur].empty()) { int u = Q[cur].front(); Q[cur].pop_front(); for( int Size = vertex[u].size(), i = 0; i < Size; i++) { int v = vertex[u][i]; if(visit[v] != when && cnt[u][v] > 0) { if(flag == 1) used[s][u][v] = used[s][v][u] = true; //第一次最短路才标记used因为懒得写两遍,所以要flag来标记是删边前还收删边后做的最短路,1则是预处理时的最短路,此时要标记used;2则是删边后的最短路,这个时候不能标记used.
visit[v] = when; sum[s] += floors; Q[1-cur].push_back(v); } } } floors++; cur = 1 - cur; } while(!Q[cur].empty()); } void init() { memset(sum, 0, sizeof(sum)); memset(used, false, sizeof(used)); for( int i = 1; i <= n; i++) vertex[i].clear(); }
/* 最短路, 终点集合到s的最远距离最短,求s. 即已知终点集{d}求一s使得Min{ max{ dis(s, di) } } 好题 思路: 多次单源最短路,选出最大值 在对每个x进行分层搜索的过程中, 用max_d[y]记录每个地区x到达地区y的最短距离中的最大值. 最后求得的Star Value就是max_d[]中的最小值. 由于题目的特殊性`边权都为1`,所以可以借助这一性质变换一下SPFA使其更快。 说个题外话,在临高时看到有个学弟拓扑排序用到“分层思想”,一直觉得很妙。就是拓扑后我们可以得到floor[i],如果floor[i] > floor[j],即说明j是i的前驱节点(层数越小越接近root); 而floor[i] == floor[j]的话则i,j的相对顺序无所谓,因为他们都在“同一层”。 这里因为边权都为1,所以SPFA可以用到上述的分层思想,层数越高,离source越远。代码里面floors就表示层数,Q是滚动队列,就是一层一层地relax后继节点。 注意!!千万不要以为max_d[]是最短路算法里面的dis[],这里的max_d[i]是到点i到终点集合{di}的最大值!而常规最短路算法里的dis[]已经被省略为“层数”了,不需要记录,所以没开数组。 最重要的是学到一个tip!!以前我做多次最短路的时候总要每次都初始化visit[] -> false,但其实不用的,我们只要用一个变量when表示“这是第几次做SPFA(或其他)“,然后每次入队前都看”是否当前visit[v] == when就可以直到改点是否已经入过队...... */ #include <stdio.h> #include <string.h> #include <vector> #include <deque> using namespace std; #define debug printf("!\n") #define INF 999999999 #define MAXN 10000 int n; int max_d[MAXN]; int visit[MAXN]; vector<int> v[MAXN];
void SPFA(int s, int when); void init(); int main() { int cases, query, id, m, y, x; scanf("%d", &cases); while(cases--) { scanf("%d%d", &n, &query); init(); for(int i = 0; i < n; i++) { scanf("%d%d", &id, &m); while(m--) { scanf("%d", &y); v[id].push_back(y); } } int when = 0; while(query--) { scanf("%d", &m); while(m--) { scanf("%d", &x); SPFA(x, ++when); } } int ans = INF, ans_id = -1; for(int i = 1; i < MAXN; i++) if(!v[i].empty() && max_d[i] < ans) ans = max_d[i], ans_id = i; printf("%d %d\n", ans, ans_id); } return 0; } void init() { for(int i = 0; i < MAXN; i++) v[i].clear(); memset(max_d, 0, sizeof(max_d)); memset(visit, 0, sizeof(visit)); } void SPFA(int s, int when) { deque<int> Q[2]; int cur = 0; Q[cur].push_back(s); max_d[s] = max(max_d[s], 1); visit[s] = when; int floors = 1; do { floors++; while(!Q[cur].empty()) { int at = Q[cur].front(); Q[cur].pop_front(); for(int Size = v[at].size(), i = 0; i < Size; i++) { int to = v[at][i]; if(visit[to] != when) { //是否已入队 //max_d[to] = max(max_d[to], max_d[at]+1); 这句是不对的,因为这个分层跟拓扑排序的分层是不一样的,拓扑排序是要在入度为0时才能加进队Q,所以可以这样写,但是这里只要第一次遇见点to就必须得入队,因为要的是最短路径 max_d[to] = max(max_d[to], floors); //不把这句放在if外面,因为这里的max_d[to]是距离s的最短路径,最短路径也就是最小层数,最小层数在to第一次入队的时候已经得到了 visit[to] = when; Q[1-cur].push_back(to); } } } cur = 1 - cur; } while(!Q[cur].empty()); }
2013年5月17日
/* 最小公共祖先 题意: 给出一颗无向有边权树, 询问若干个(u,v)对的距离.
所谓LCA 的Tarjan算法, 实际上就是在建树的过程中把query中的lca给计算出来, 所以称为`离线算法` . 是的, 本质就是这么简单, 好多解释都搞复杂了.
步骤略, 自己google. 理解这个算法一定要抓住`递推`的思想(也有递归在里面, 也要抓住). Tarjan是利用并查集实现的, 在递推过程中, 一棵树的root节点代表这棵树(联系并查集来想), 这样做的好处是, 如果问点i与j的lca, 我们只要找i,j都属于的最小的哪棵子树就行了, 因为该子树就是我们的答案. 那如何确定是那颗子树呢? 这一点后面再解释一下. 下面说Tarjan最巧妙的点了. 因为是在建树的过程中计算所有query, 也就表示我们此刻是否能计算某一query对(u,v)的条件是 : u和v是否都已经遍历过. 所以我们可以在遍历到点v(假设经历v的时间比u晚)的时候把query给计算出来. 比如lcm(u,v)就是find(u). 那此刻的find(v)和lcm(u,v)相不相等呢? 答案是不相等, 至少在我的代码实现上不相等. 因为father[x]的更新是在`递归回去`的时候更新的, 而此刻在遍历v点, 还没递归回去呢, father[v]当然也就没更新啦. 其实上一段就已经回答了`如何确定哪棵子树是我们想要的答案`这一问题了. 就是find(u)所代表的子树! 注意, 是find(u), 不是find(v)! 因为u是在v之前已经被遍历过了, 并且递归回去过sub_root过了, 也就是father[u]被更新为sub_root了, 所以find(u)可以代表当前的sub_tree, 即`最小包含(u,v)子树`
下面两个解释, 推荐一下. 罗嗦一句, 看代码更容易理解, 人脑模拟一遍更容易理解 http://www.nocow.cn/index.php/Tarjan%E7%AE%97%E6%B3%95 http://blog.chinaunix.net/uid-1721137-id-181005.html */ #include <vector> #include <stdio.h> using namespace std; #define MAXN 40001 #define debug printf("!\n") vector<int> v[MAXN], w[MAXN], query[MAXN], ans_num[MAXN]; int father[MAXN], dis[MAXN], ans[201]; bool visit[MAXN]; int n;
void init() { for(int i = 1; i <= n; i++) { v[i].clear(); w[i].clear(); ans_num[i].clear(); query[i].clear(); father[i] = i; dis[i] = 0; visit[i] = false; } }
int find(int x) { return x == father[x] ? x : father[x] = find(father[x]); } void Union(int x, int y) { father[find(y)] = find(x); } void Tarjan(int now, int value) { visit[now] = true; dis[now] = value; for(int Size = v[now].size(), i = 0; i < Size; i++) { int tmp = v[now][i]; if(visit[tmp] != 0) continue; Tarjan(tmp, value + w[now][i]); Union(now, tmp); //注意顺序, 先Tarjan子节点tmp, 再更新其father[tmp], 因为要保证在递推tmp所代表的子树时, father[tmp] = tmp, 而与当前子树无关. 递归回来的时候再把tmp代表的子树`并入`到当前树里 }
for(int Size = query[now].size(), i = 0; i < Size; i++) { int tmp = query[now][i]; if(!visit[tmp]) continue; //若visit[tmp] == true, 即表示tmp节点已经遍历过, 此时可计算相应的query ans[ans_num[now][i]] = dis[now] + dis[tmp] - 2 * dis[find(tmp)]; } }
int main() { int cases, Query, x, y, z; scanf("%d", &cases); while(cases--) { scanf("%d%d", &n, &Query); init(); for(int i = 1; i < n; i++) { scanf("%d%d%d", &x, &y, &z); v[x].push_back(y); w[x].push_back(z); v[y].push_back(x); w[y].push_back(z); }
for(int i = 0; i < Query; i++) { scanf("%d%d", &x, &y); query[x].push_back(y); query[y].push_back(x); ans_num[x].push_back(i); ans_num[y].push_back(i); } Tarjan(1, 0); for(int i = 0; i < Query; i++) printf("%d\n", ans[i]); } return 0; }
2013年4月4日
在coursera上上看了两周的Scala, 刚才奇怪为什么Scala对于一般函数严格要求指明参数类型的语言, 对于匿名函数却可以不用指明参数类型?
google无果. 不想查了. 习惯了Scheme/SML这样有类型推导系统的语言, 再来写Scala有点排斥心理, 而且Scala更过分的是要连函数的返回类型也要写...喂喂, 这都完全丧失了FP的美感了吧 >_<
突然想起来, 昨天才发现SML内置没有对大数的操作---好失望~
当初选这门课是好奇它能把面向对象与函数式能结合到多大程度, 现在看来没什么令人惊奇的, 不贬不赞. 还是原生函数式语言比较好玩.
如果你是被标题骗过来的, 能回答标题的问题就请给我讲讲吧~谢谢!
2012年6月10日
2012年3月17日
有朋友真好。 其实有时候只是想说说话,不是内心真的有疑问想寻求办法,因为答案其实就在自己心中,自己心里明白得很......但人就是这样,就算心里明白,也还是觉得憋,想找个人倾述。所以这时候,有朋友真好。朋友可以无偿地陪你,听你说话,跟你说话。 我今天其实也没发生什么,只是想了一些东西,心情就变得不太愉快,但并不是不愉快,只是很想有个人骂一下自己!骂自己的不成熟,骂自己没毅力,骂自己禁受不住挫折。然后我就找秦程聊天,聊着聊着,就释怀了,就开朗了。 有朋友真好。我没有好的文笔给解释这句话。 我对亲近的人都不轻易说谢谢,但今天我对秦程说了声谢谢,因为今天打电话的时候我想到blue 跟我说的一句话:有朋友真好。
2011年10月30日
|
|