|  | 
		
			| 常用链接留言簿随笔分类随笔档案文章分类搜索最新评论
	阅读排行榜评论排行榜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日 |  |