ReZero's Utopia.

Algo tips

Word count: 10.7kReading time: 48 min
2020/08/10 Share
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
// 注意
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
// 注意
else if (nums[mid] > target)
right = mid - 1; // 注意 }
return -1;
}
  1. 为什么 while 循环的条件中是 <=,⽽不是 <
  • 因为初始化 right 的赋值是 nums.length - 1 ,即最后一个元素的索引,而不是nums.length
  • 防止漏掉 [a, a] 区间

RSA-basic

费马小定理(Fermat Theory)是数论中的一个重要定理,其内容为: 假如p是质数,且gcd(a,p)=1,那么
a(p-1)≡1(mod
p)。即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。


蒙哥马利幂模运算
RSA算法的核心之一

1
2
3
4
5
6
7
8
9
10
11
long long pows(int a, int b, int mod){
long long temp = 1;
while(b){
if(b%2){
temp = temp*a%mod;
}
a = a*a%mod;
b/=2;
}
return temp;
}

拓扑排序

拓扑排序

方法:

  1. 找到所有入度为0的点插进队列
  2. 选择一个并输出它
    • 然后删掉与之关联的所有边(即任何与之相连的入度-1)
    • 删除过程遇到入度为0点插入队列
  3. 重复第二步,直到队列中无0点

优化:如果要求最小字典序

用反向拓扑加优先队列

#include <bits/stdc++.h>

using namespace std;

vector<int> G[5020];
map<string, int> maps;
map<int, string> mapr;

struct less_cmp{
    bool operator()(const int &a, const int &b){
        return mapr[a] > mapr[b];
    }
};

int main(){
    int T;
    cin >> T;
    for(int kase = 1; kase <= T; ++kase){
        int n, m, in[5020], cnt = 0, ans[5020];
        cin >> n;
        for(int i = 0; i < n; ++i){
            string cla;
            cin >> cla;
            maps.insert(pair<string, int>(cla, i));
            mapr.insert(pair<int, string>(i, cla));
        }
        cin >> m;
        for(int i = 0; i < m; ++i){
            string as, bs;
            cin >> as >> bs;
            int u = maps[as], v = maps[bs];
            G[u].push_back(v);
            in[v]++;
        }
        priority_queue<int, vector<int>, less_cmp > que;
        for(int i = 0; i < n; ++i){
            if(in[i] == 0)
                que.push(i);
        }
        vector<int>::iterator iter;
        while(!que.empty()){
            int temp = que.top();
            que.pop();
            ans[cnt++] = temp;
            for(iter = G[temp].begin(); iter != G[temp].end(); ++iter){
                    if(--in[*iter] == 0){
                        que.push(*iter);
                    }
                }
        }
        cout << "Case " << kase << ":" << endl;
        for(int i = 0; i < cnt; ++i){
            cout << mapr[ans[i]] << endl;
        }
        maps.clear();
        mapr.clear();
        for(int i = 0; i < 5020; ++i){
            G[i].clear();
        }
    }
}

开心的wrong answer-_-

第一行为样例组数T。每组样例第一行为课程数量n(1 <= n <= 5000),以下n行每行表示一门课程名称。接下来为关系数量m(1 <= m <= 10000),每一行有两个课程名称a、b,表示a课程要开设在b课程前面。(输入保证无环)

原题如上,旭哥思路的代码

#include <bits/stdc++.h>

using namespace std;
vector<int> G[5020];
string mapr[5020];
map<string, int> maps;

int main(){
    int T;  scanf("%d", &T);
    for(int kase = 1; kase <= T; ++kase){
        int n, m, in[5020] = {0}, cnt = 0, ans[5020];

        scanf("%d", &n);
        for (int i = 0; i < n; ++i) G[i].clear();
        maps.clear();
        for(int i = 0; i < n; ++i){
            char as[44];
            scanf("%s", as);
            mapr[i] = as;
        }
        sort(mapr, mapr + n);
        for(int i = 0; i < n; ++i){
            maps[mapr[i]] = i;
        }

        scanf("%d", &m);
        for(int i = 0; i < m; ++i){
            char as[44], bs[44];
            scanf("%s %s", as, bs);
            int u = maps[as], v = maps[bs];
            G[u].push_back(v);
            ++in[v];
        }

        printf("Case %d:\n", kase);
        for(int i = 0; i < n; ++i){
            int temp = 0;
            for(int j = 0; j < n; ++j){
                if(!in[j]){
                    printf("%s\n", mapr[j].c_str());
                    temp = j; --in[j];
                    break;
                }
            }
            for(int k = 0; k < G[temp].size(); ++k){
                --in[G[temp][k]];
            }
        }
    }
}
  • 权值置-1,排除节点
  • G(vector)来控制制约关系
  • sort排序保证字典序

并查集 (转)

本文由 简悦 SimpRead 转码, 原文地址 http://blog.csdn.net/dellaserss/article/details/7724401

这个文章是几年前水 acm 的时候转的, 当时也不知道作者是谁, 要是有人知道的话说一下吧 并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了。以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定。不分享出来真是对不起 party 了。(party:我靠,关我嘛事啊?我跟你很熟么?) 来看一个实例,杭电 1232 畅通工程 首先在地图上给你若干个城镇,这些城镇都可以看作点,然后告诉你哪些对城镇之间是有道路直接相连的。最后要解决的是整幅图的连通性问题。比如随意给你两个点,让你判断它们是否连通,或者问你整幅图一共有几个连通分支,也就是被分成了几个互相独立的块。像畅通工程这题,问还需要修几条路,实质就是求有几个连通分支。如果是 1 个连通分支,说明整幅图上的点都连起来了,不用再修路了;如果是 2 个连通分支,则只要再修 1 条路,从两个分支中各选一个点,把它们连起来,那么所有的点都是连起来的了;如果是 3 个连通分支,则只要再修两条路…… 以下面这组数据输入数据来说明 4 2 1 3 4 3 第一行告诉你,一共有 4 个点,2 条路。下面两行告诉你,1、3 之间有条路,4、3 之间有条路。那么整幅图就被分成了 1-3-4 和 2 两部分。只要再加一条路,把 2 和其他任意一个点连起来,畅通工程就实现了,那么这个这组数据的输出结果就是 1。好了,现在编程实现这个功能吧,城镇有几百个,路有不知道多少条,而且可能有回路。 这可如何是好? 我以前也不会呀,自从用了并查集之后,嗨,效果还真好!我们全家都用它! 并查集由一个整数型的数组和两个函数构成。数组 pre[] 记录了每个点的前导点是什么,函数 find 是查找,join 是合并。

int pre[1000];

int find(int x)               // 查找根节点
{   
    int r = x;
    while(pre[r] != r)         //返回根节点 r
    r = pre[r];
    int i = x ,j;
    while(i != r)                // 路径压缩 
    {     
        j = pre[i]; // 在改变上级之前用临时变量  j 记录下他的值     
        pre[i] = r ; // 把上级改为根节点
        i = j; 
    }   
    return r;
}

void join(int x,int y)     // 判断 x y 是否连通,
 // 如果已经连通,就不用管了 // 如果不连通,就把它们所在的连通分支合并起,
{
    int fx = find(x),fy = find(y);
    if(fx != fy)
        pre[fx]=fy;
}

为了解释并查集的原理,我将举一个更有爱的例子。 话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉 “朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢? 我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名 “齐达内朋友之队”“罗纳尔多朋友之队”…… 两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。 但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长,要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?” 这样一来,队长面子上挂不住了,而且效率太低,还有可能陷入无限循环中。于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否连通,至于他们是如何连通的,以及每个圈子内部的结构是怎样的,甚至队长是谁,并不重要。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。 下面我们来看并查集的实现。 int pre[1000]; 这个数组,记录了每个大侠的上级是谁。大侠们从 1 或者 0 开始编号(依据题意而定),pre[15]=3 就表示 15 号大侠的上级是 3 号大侠。如果一个人的上级就是他自己,那说明他就是掌门人了,查找到此为止。也有孤家寡人自成一派的,比如欧阳锋,那么他的上级就是他自己。每个人都只认自己的上级。比如胡青牛同学只知道自己的上级是杨左使。张无忌是谁?不认识!要想知道自己的掌门是谁,只能一级级查上去。 find 这个函数就是找掌门用的,意义再清楚不过了(路径压缩算法先不论,后面再说)。

int find(int x)      // 查找我(x)的掌门
{
    int r = x;                   // 委托 r 去找掌门
    while(pre[r] != r)  // 如果 r 的上级不是 r 自己(也就是说找到的大侠他不是掌门 = =)
        r = pre[r];               // r 就接着找他的上级,直到找到掌门为止。
    return r;              // 掌门驾到~~~

}

再来看``看 join 函数,就是在两个点之间连一条线,这样一来,原先它们所在的两个板块的所有点就都可以互通了。这在图上很好办,画条线就行了。但我们现在是用并查集来描述武林中的状况的,一共只有一个 pre[] 数组,该如何实现呢? 还是举江湖的例子,假设现在武林中的形势如图所示。虚竹小和尚与周芷若 MM 是我非常喜欢的两个人物,他们的终极 boss 分别是玄慈方丈和灭绝师太,那明显就是两个阵营了。我不希望他们互相打架,就对他俩说:“你们两位拉拉勾,做好朋友吧。” 他们看在我的面子上,同意了。这一同意可非同小可,整个少林和峨眉派的人就不能打架了。这么重大的变化,可如何实现呀,要改动多少地方?其实非常简单,我对玄慈方丈说:“大师,麻烦你把你的上级改为灭绝师太吧。这样一来,两派原先的所有人员的终极 boss 都是师太,那还打个球啊!反正我们关心的只是连通性,门派内部的结构不要紧的。” 玄慈一听肯定火大了:“我靠,凭什么是我变成她手下呀,怎么不反过来?我抗议!” 抗议无效,上天安排的,最大。反正谁加入谁效果是一样的,我就随手指定了一个。这段函数的意思很明白了吧?

void join(int x, int  y)      // 我想让虚竹和周芷若做朋友
{
    int fx=find(x),fy=find(y);      //虚竹的老大是玄慈,芷若 MM 的老大是灭绝
    if(fx!=fy)                        // 玄慈和灭绝显然不是同一个人_**
    pre[fx]=fy;               // 方丈只好委委屈屈地当了师太的手下啦_**
}

再来看看路径压缩算法。建立门派的过程是用 join 函数两个人两个人地连接起来的,谁当谁的手下完全随机。最后的树状结构会变成什么胎唇样,我也完全无法预计,一字长蛇阵也有可能。这样查找的效率就会比较低下。最理想的情况就是所有人的直接上级都是掌门,一共就两级结构,只要找一次就找到掌门了。哪怕不能完全做到,也最好尽量接近。这样就产生了路径压缩算法。 设想这样一个场景:两个互不相识的大侠碰面了,想知道能不能揍。 于是赶紧打电话问自己的上级:“你是不是掌门?” 上级说:“我不是呀,我的上级是谁谁谁,你问问他看看。” 一路问下去,原来两人的最终 boss 都是东厂曹公公。 “哎呀呀,原来是记己人,西礼西礼,在下三营六组白面葫芦娃!” “幸会幸会,在下九营十八组仙子狗尾巴花!” 两人高高兴兴地手拉手喝酒去了。 “等等等等,两位同学请留步,还有事情没完成呢!” 我叫住他俩。 “哦,对了,还要做路径压缩。” 两人醒悟。 白面葫芦娃打电话给他的上级六组长:“组长啊,我查过了,其习偶们的掌门是曹公公。不如偶们一起及接拜在曹公公手下吧,省得级别太低,以后查找掌门麻环。” “唔,有道理。” 白面葫芦娃接着打电话给刚才拜访过的三营长…… 仙子狗尾巴花也做了同样的事情。 这样,查询中所有涉及到的人物都聚集在曹公公的直接领导下。每次查询都做了优化处理,所以整个门派树的层数都会维持在比较低的水平上。路径压缩的代码,看得懂很好,看不懂也没关系,直接抄上用就行了。总之它所实现的功能就是这么个意思。 hdu1232

#include<iostream>  
using namespace std;


int  pre[1050];

bool t[1050];  //t 用于标记独立块的根结点  

int Find(int x)  
{

    int r = x;

    while(r != pre[r])  
        r = pre[r];

    int i = x,j;

    while(pre[i] != r)  
    {
        j = pre[i];
        pre[i] = r;
        i = j;
    }  
    return r;
}  

void mix(int x,int y)  
{

    int fx = Find(x), fy = Find(y);

    if(fx != fy)  
    {  
        pre[fy] = fx;
    }  
}   

int main()  
{
    int N, M, a, b, i, j, ans;

    while(scanf("%d %d", &N, &M) && N)  
    {
        for(i = 1; i <= N; ++i)           // 初始化   
            pre[i] = i;
        for(i = 1; i <= M; ++i)           // 吸收并整理数据   
        {
            scanf("%d %d", &a, &b);
            mix(a, b);
        }
        memset(t, 0, sizeof(t));
        for(i = 1; i <= N; ++i)           // 标记根结点  
        {
            t[Find(i)] = 1;      
        }  
        for(ans = 0, i = 1; i <= N; ++i)  
            if(t[i])  
                ans++;
        printf("%d\n", ans-1);
    }  
    return 0;
}//dellaserss  

以下为原文附的代码: 回到开头提出的问题,我的代码如下:

int pre[1000];

int find(int x)
{
    int r = x;

   while(pre[r] != r)
   r = pre[r];

   int i = x; int j;

   while(i != r)
   {
        j = pre[i];
        pre[i] = r;
        i = j;
   }
   return r;
}

int main()
{

   int n, m, p1, p2, i, total, f1, f2;

   while(scanf("%d", &n) && n)         // 读入 n,如果 n 为 0,结束
   {   
        // 刚开始的时候,有 n 个城镇,一条路都没有 // 那么要修 n-1 条路才能把它们连起来
        total = n-1;  
        // 每个点互相独立,自成一个集合,从 1 编号到 n // 所以每个点的上级都是自己
        for(i = 1;i <= n;i++) 
        { 
            pre[i ]=i;
        }
        // 共有 m 条路
        scanf("%d",&m); while(m--)
        { 
            // 下面这段代码,其实就是 join 函数,只是稍作改动以适应题目要求
            // 每读入一条路,看它的端点 p1,p2 是否已经在一个连通分支里了
            scanf("%d %d", &p1, &p2);
            f1 = find(p1);
            f2 = find(p2);

// 如果是不连通的,那么把这两个分支连起来
// 分支的总数就减少了 1,还需建的路也就减了 1
            if(f1 != f2){
                pre[f2] = f1;
                total--;
            }
            // 如果两点已经连通了,那么这条路只是在图上增加了一个环 // 对连通性没有任何影响,无视掉
        }
        // 最后输出还要修的路条数
        printf("%d\n",total);
    }
    return 0;
}

Eight queens

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

#include <bits/stdc++.h>

using namespace std;

int answer[92]{
15863724,16837425,17468253,17582463,24683175,25713864,25741863,26174835,26831475,
27368514,27581463,28613574,31758246,35281746,35286471,35714286,35841726,36258174,
36271485,36275184,36418572,36428571,36814752,36815724,36824175,37285146,37286415,
38471625,41582736,41586372,42586137,42736815,42736851,42751863,42857136,42861357,
46152837,46827135,46831752,47185263,47382516,47526138,47531682,48136275,48157263,
48531726,51468273,51842736,51863724,52468317,52473861,52617483,52814736,53168247,
53172864,53847162,57138642,57142863,57248136,57263148,57263184,57413862,58413627,
58417263,61528374,62713584,62714853,63175824,63184275,63185247,63571428,63581427,
63724815,63728514,63741825,64158273,64285713,64713528,64718253,68241753,71386425,
72418536,72631485,73168524,73825164,74258136,74286135,75316824,82417536,82531746,83162574,84136275 };




int main(){
int cases;
cin >> cases;


for(int kase = 0; kase < cases; ++kase){
int n; cin >> n;
cout << answer[n-1] << endl;
}
}

//
//int vis[3][20] = {0};
//int R[8] = {0};
//int tot = 0;
//int n = 8;
//
//
//void seac(int cur){
// if(cur == 8) {
// tot++;
// cout<< "";
// for(int i = 0; i < 8; ++i){
// cout << R[i] + 1;
// }
// cout << ",";
// }
// else for(int i = 0; i < 8; ++i){
// if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+8]){
//
// R[cur] = i;
//
// vis[0][i] = vis[1][cur+i] = vis[2][cur-i+8] = 1;
// seac(cur + 1);
// vis[0][i] = vis[1][cur+i] = vis[2][cur-i+8] = 0;
//
// }
// }
//}
//memset(vis, 0, sizeof(vis));
// memset(R, 0, sizeof(R));
// seac(0);


Floyd

此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了这个算法。Robert W.Floyd这个牛人是朵奇葩,他原本在芝加哥大学读的文学,但是因为当时美国经济不太景气,找工作比较困难,无奈之下到西屋电气公司当了一名计算机操作员,在IBM650机房值夜班,并由此开始了他的计算机生涯。

作者:ahalei来源:51CTO博客|2014-03-26 09:04

收藏

分享

081028t67l8vd73686e68m.png

暑假,小哼准备去一些城市旅游。有些城市之间有公路,有些城市之间则没有,如下图。为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程。

081028xjgvimgz7882qdu7.png

上图中有4个城市8条公路,公路上的数字表示这条公路的长短。请注意这些公路是单向的。我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径。这个问题这也被称为“多源最短路径”问题。

现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储。比如1号城市到2号城市的路程为2,则设e[1][2]的值为2。2号城市无法到达4号城市,则设置e[2][4]的值为∞。另外此处约定一个城市自己是到自己的也是0,例如e[1][1]为0,具体如下。

081028o2n5ebn8hdeh9e5l.png

现在回到问题:如何求任意两点之间最短路径呢?通过之前的学习我们知道通过深度或广度优先搜索可以求出两点之间的最短路径。所以进行n2遍深度或广度优先搜索,即对每两个点都进行一次深度或广度优先搜索,便可以求得任意两点之间的最短路径。可是还有没有别的方法呢?

我们来想一想,根据我们以往的经验,如果要让任意两点(例如从顶点a点到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即a->k->b,才可能缩短原来从顶点a点到顶点b的路程。那么这个中转的顶点k是1~n中的哪个点呢?甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即a->k1->k2b->或者a->k1->k2…->k->i…->b。比如上图中从4号城市到3号城市(4->3)的路程e[4][3]原本是12。如果只通过1号城市中转(4->1->3),路程将缩短为11(e[4][1]+e[1][3]=5+6=11)。其实1号城市到3号城市也可以通过2号城市中转,使得1号到3号城市的路程缩短为5(e[1][2]+e[2][3]=2+3=5)。所以如果同时经过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10。通过这个的例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。好,下面我们将这个问题一般化。

当任意两点之间不允许经过第三个点时,这些城市之间最短路程就是初始路程,如下。

081029zdxxq919ttqt8tu8.png

如现在只允许经过1号顶点,求任意两点之间的最短路程,应该如何求呢?只需判断e[i][1]+e[1][j]是否比e[i][j]要小即可。e[i][j]表示的是从i号顶点到j号顶点之间的路程。e[i][1]+e[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。其中i是1n循环,j也是1n循环,代码实现如下。

  1. for(i=1;i<=n;i++)
  2. {
  3. for(j=1;j<=n;j++)
  4. {
  5. if ( e[i][j] > e[i][1]+e[1][j] )
  6. e[i][j] = e[i][1]+e[1][j];
  7. }
  8. }

在只允许经过1号顶点的情况下,任意两点之间的最短路程更新为:

081029itl7z7m4l9qqg56d.png

通过上图我们发现:在只通过1号顶点中转的情况下,3号顶点到2号顶点(e[3][2])、4号顶点到2号顶点(e[4][2])以及4号顶点到3号顶点(e[4][3])的路程都变短了。

接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短路程。如何做呢?我们需要在只允许经过1号顶点时任意两点的最短路程的结果下,再判断如果经过2号顶点是否可以使得i号顶点到j号顶点之间的路程变得更短。即判断e[i][2]+e[2][j]是否比e[i][j]要小,代码实现为如下。

  1. //经过1号顶点
  2. for(i=1;i<=n;i++)
  3. for(j=1;j<=n;j++)
  4. if (e[i][j] > e[i][1]+e[1][j]) e[i][j]=e[i][1]+e[1][j];
  5. //经过2号顶点
  6. for(i=1;i<=n;i++)
  7. for(j=1;j<=n;j++)
  8. if (e[i][j] > e[i][2]+e[2][j]) e[i][j]=e[i][2]+e[2][j];

在只允许经过1和2号顶点的情况下,任意两点之间的最短路程更新为:

081029e7gjlaaul4zk7z4n.png

通过上图得知,在相比只允许通过1号顶点进行中转的情况下,这里允许通过1和2号顶点进行中转,使得e[1][3]和e[4][3]的路程变得更短了。

同理,继续在只允许经过1、2和3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:

081029pd747o8o87o07o7l.png

最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:

081030h7tmht7cs2h7qftu.png

整个算法过程虽然说起来很麻烦,但是代码实现却非常简单,核心代码只有五行:

  1. for(k=1;k<=n;k++)
  2. for(i=1;i<=n;i++)
  3. for(j=1;j<=n;j++)
  4. if(e[i][j]>e[i][k]+e[k][j])
  5. e[i][j]=e[i][k]+e[k][j];

这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。其实这是一种“动态规划”的思想,关于这个思想我们将在《啊哈!算法2——伟大思维闪耀时》在做详细的讨论。下面给出这个算法的完整代码:

  1. #include <stdio.h>

  2. int main()

  3. {

  4. int e[10][10],k,i,j,n,m,t1,t2,t3;

  5. int inf=99999999; //用inf(infinity的缩写)存储一个我们认为的正无穷值

  6. //读入n和m,n表示顶点个数,m表示边的条数

  7. scanf(“%d %d”,&n,&m);

  8. //初始化

  9. for(i=1;i<=n;i++)

  10. for(j=1;j<=n;j++)

  11. if(i==j) e[i][j]=0;

  12. else e[i][j]=inf;

  13. //读入边

  14. for(i=1;i<=m;i++)

  15. {

  16. scanf(“%d %d %d”,&t1,&t2,&t3);

  17. e[t1][t2]=t3;

  18. }

  19. //Floyd-Warshall算法核心语句

  20. for(k=1;k<=n;k++)

  21. for(i=1;i<=n;i++)

  22. for(j=1;j<=n;j++)

  23. if(e[i][j]>e[i][k]+e[k][j] )

  24. e[i][j]=e[i][k]+e[k][j];

  25. //输出最终的结果

  26. for(i=1;i<=n;i++)

  27. {

  28. for(j=1;j<=n;j++)

  29. {

  30. printf(“%10d”,e[i][j]);

  31. }

  32. printf(“\n”);

  33. }

  34. return0;

  35. }

有一点需要注意的是:如何表示正无穷。我们通常将正无穷定义为99999999,因为这样即使两个正无穷相加,其和仍然不超过int类型的范围(C语言int类型可以存储的最大正整数是2147483647)。在实际应用中最好估计一下最短路径的上限,只需要设置比它大一点既可以。例如有100条边,每条边不超过100的话,只需将正无穷设置为10001即可。如果你认为正无穷和其它值相加得到一个大于正无穷的数是不被允许的话,我们只需在比较的时候加两个判断条件就可以了,请注意下面代码中带有下划线的语句。

  1. //Floyd-Warshall算法核心语句
  2. for(k=1;k<=n;k++)
  3. for(i=1;i<=n;i++)
  4. for(j=1;j<=n;j++)
  5. if(e[i][k]<inf && e[k][j]<inf && e[i][j]>e[i][k]+e[k][j])
  6. e[i][j]=e[i][k]+e[k][j];

上面代码的输入数据样式为:

  1. 48
  2. 122
  3. 136
  4. 144
  5. 233
  6. 317
  7. 341
  8. 415
  9. 4312

第一行两个数为n和m,n表示顶点个数,m表示边的条数。

接下来m行,每一行有三个数t1、t2 和t3,表示顶点t1到顶点t2的路程是t3。

得到最终结果如下:

081030is22w3mmnz3r33m3.png

通过这种方法我们可以求出任意两个点之间最短路径。它的时间复杂度是O(N3)。令人很震撼的是它竟然只有五行代码,实现起来非常容易。正是因为它实现起来非常容易,如果时间复杂度要求不高,使用Floyd-Warshall来求指定两点之间的最短路或者指定一个点到其余各个顶点的最短路径也是可行的。当然也有更快的算法,请看下一节:Dijkstra算法。

另外需要注意的是:Floyd-Warshall算法不能解决带有“负权回路”(或者叫“负权环”)的图,因为带有“负权回路”的图没有最短路。例如下面这个图就不存在1号顶点到3号顶点的最短路径。因为1->2->3->1->2->3->…->1->2->3这样路径中,每绕一次1->-2>3这样的环,最短路就会减少1,永远找不到最短路。其实如果一个图中带有“负权回路”那么这个图则没有最短路。

081030elthvel6et6k886y.png

此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了这个算法。Robert W.Floyd这个牛人是朵奇葩,他原本在芝加哥大学读的文学,但是因为当时美国经济不太景气,找工作比较困难,无奈之下到西屋电气公司当了一名计算机操作员,在IBM650机房值夜班,并由此开始了他的计算机生涯。此外他还和J.W.J. Williams(威廉姆斯)于1964年共同发明了著名的堆排序算法HEAPSORT。堆排序算法我们将在第七章学习。Robert W.Floyd在1987年获得了图灵奖。

博客地址:http://ahalei.blog.51cto.com/4767671/1383613

Huffman

给定一个字符串,进行哈夫曼编码

#include <bits/stdc++.h>

using namespace std;

typedef struct node{
    int num;
    string data;
    node(int _num, string _data){
        num = _num; data = _data;
    }
}node;

bool operator<(const node &a, const node &b){
     return a.num > b.num;
}


int main(){
    int kase = 1;
    string line;
    while(getline(cin, line)){
        int chr[30] = {0};
        string answer[30];
        priority_queue<node> que;
        int len = line.length();
        for(int i = 0; i < len; ++i){
            chr[line[i] - 'A']++;
        }
        for(int i = 0; i < 26; ++i){
            if(chr[i]){
                string p = "";
                que.push(node(chr[i], p + char('A' + i)));
            }
        }

        int n = que.size() - 1;
        if(n > 0){
            for(int i = 0; i < n; ++i){
                node a = que.top(); que.pop();
                node b = que.top(); que.pop();
                for(int j = 0; j < a.data.size(); ++j){
                    answer[a.data[j]-'A'] += '0';
                }
                for(int j = 0; j < b.data.size(); ++j){
                    answer[b.data[j]-'A'] += '1';
                }
                que.push(node(a.num + b.num, a.data + b.data));
            }
            cout << "Case #" << kase++ << ":" << endl;
            for(int i = 0; i < 26; ++i){
                if(chr[i]){
                    reverse(answer[i].begin(), answer[i].end());
                }
            }
            ///对answer字符串数组排序:相同chr的字符串按字典序排
            for(int i = 0; i < 26; ++i){
                for(int j = i; j < 26; ++j){
                    if(chr[i] == chr[j]){
                        if(answer[j] < answer[i]){
                            swap(answer[i], answer[j]);
                        }
                    }

                }
            }
            ///以上对answer字符串数组排序:相同chr的字符串按按字典序排
            for(int i = 0; i < 26; ++i){
                if(chr[i]){
                    printf("%c: %s\n", i + 'A',answer[i].c_str());
                }
            }
            int sum = 0;
            for(int i = 0; i < 26; ++i){
                sum += answer[i].size() * chr[i];
            }
            cout << sum;
            if(sum < 50){
                cout << " ";
                for(int i = 0; i < len; ++i){
                    cout << answer[line[i] - 'A'];
                }
            }
        } else if (n == 0){
            node fina = que.top();
            cout << fina.data << ":" << 0 << endl;
            cout << len;
            if(len < 50){
                cout << " ";
                for(int i = 0; i < len; ++i){
                    cout << 0;
                }
            }
        }
        cout << endl;

    }

}

开心的wrong answer 正在查,
已查出错误,字典序保证方式出错 要保证为字典序不能后期排
因为要考虑同时保证最子权与父权的构建关系
不能生成后才排序编码

而应是最简最直接思路
- 排好序后进行编码

#include <bits/stdc++.h>

using namespace std;

typedef struct node{
    int num;
    string data;
    node(int _num, string _data){
        num = _num; data = _data;
    }
    bool operator<(const node &b)const{
         ///重点唯一改动
         if (num == b.num) return data[0] > b.data[0];
         return num > b.num;
    }
}node;



int main(){
    int kase = 1;
    string line;
    while(getline(cin, line)){
        int chr[30] = {0};
        string answer[30];
        priority_queue<node> que;
        int len = line.length();
        for(int i = 0; i < len; ++i){
            chr[line[i] - 'A']++;
        }
        for(int i = 0; i < 26; ++i){
            if(chr[i]){
                string p = "";
                que.push(node(chr[i], p + char('A' + i)));
            }
        }
        int n = que.size() - 1;
        if(n > 0){
            for(int i = 0; i < n; ++i){
                node a = que.top(); que.pop();
                node b = que.top(); que.pop();
                for(int j = 0; j < a.data.size(); ++j){
                    answer[a.data[j]-'A'] += '0';
                }
                for(int j = 0; j < b.data.size(); ++j){
                    answer[b.data[j]-'A'] += '1';
                }
                que.push(node(a.num + b.num, a.data + b.data));
            }
            cout << "Case #" << kase++ << ":" << endl;
            for(int i = 0; i < 26; ++i){
                if(chr[i]){
                    reverse(answer[i].begin(), answer[i].end());
                }
            }
            ///对answer字符串数组排序:相同chr的字符串按字典序排
            for(int i = 0; i < 25; ++i){
                for(int j = 0; j < 25 - i; ++j){
                    if(chr[j] && chr[j+1])
                    if(chr[j] == chr[j+1]){
                        if(answer[j] > answer[j+1]){
                            swap(answer[j+1], answer[j]);
                        }
                    }

                }
            }
            ///以上对answer字符串数组排序:相同chr的字符串按按字典序排
            for(int i = 0; i < 26; ++i){
                if(chr[i]){
                    printf("%c: %s\n", i + 'A',answer[i].c_str());
                }
            }
            int sum = 0;
            for(int i = 0; i < 26; ++i){
                sum += answer[i].size() * chr[i];
            }
            cout << sum;
            if(sum < 50){
                cout << " ";
                for(int i = 0; i < len; ++i){
                    cout << answer[line[i] - 'A'];
                }
            }
        } else if (n == 0){
            node fina = que.top();
            cout << "Case #" << kase++ << ":" << endl;
            cout << fina.data << ": " << 0 << endl;
            cout << len;
            if(len < 50){
                cout << " ";
                for(int i = 0; i < len; ++i){
                    cout << 0;
                }
            }
        }
        cout << endl;

    }

}

这里写图片描述

Power of Two

Given an integer, write a function to determine if it is a power of two.

1
2
3
bool isPowerOfTwo(int n) {
return n > 0 && !(n&(n-1));
}

Explain: 100 & 011 = 0

Try think about power of 4: return num > 0 && (num & (num - 1)) == 0 && (num - 1) % 3 == 0;

(4^n - 1) % 3 == 0
proof:
(1) 4^n - 1 = (2^n + 1) * (2^n - 1)
(2) among any 3 consecutive numbers, there must be one that is a multiple of 3
among (2^n-1), (2^n), (2^n+1), one of them must be a multiple of 3, and (2^n) cannot be the one, therefore either (2^n-1) or (2^n+1) must be a multiple of 3, and 4^n-1 must be a multiple of 3 as well.

Sqrt(x)

https://leetcode.com/problems/sqrtx/discuss/25057/3-4-short-lines-Integer-Newton-Every-Language/24092

1
2
3
4
long r = x;
while (r*r > x)
r = (r + x/r) / 2;
return r;

answer

Redo or Undo

撤销模拟程序 编写程序模拟word中的“重做Redo”“撤销Undo”两个按钮。即键盘输入一段文字(不能含#,e.g., I as Tom whether he will go to Beijingh)之后输入“#U”(“U”代表Undo)则撤销最后一个输入的字符(“h”),在输出位置重新输出一遍新撤销后的串。这时可以再继续输入。当然,也可以输入“#R”恢复刚才删除的输入h。每有一次#R都要输出一下新的结果。当然,可以同时连续输入多个#U或多个#R。比如,输入#U#U#R#U#U#U#R#R。

  • Input
    • 第一行为一个数字T,代表一共有T组测试样例,每组测试样例包含以下内容:
      每行包含一串字母(包含空格,不超过100个字符)。
      开始第一个字母不能为“#”,即第一个字母必须要敲入文字。
      “#R”表示重做。
      “#U”表示撤销。
      “##”表示本次测试样例结束。
  • Ouput
    • 每输入一次重做,都要输出操作之后的文字
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
#include <iostream>
#include <stack>
#include <cstdio>

using namespace std;

int main(){
int cases;
cin >> cases; getchar();
for(int kase = 1; kase <= cases; ++kase){

stack<char> cc;
string oper = "", opert;

while(getline(cin, opert) && opert != "##"){
if(opert[0] == '#'){
if(opert[1] == 'U'){
int num = oper.size() - 1;
if(num >= 0){
cc.push(oper[num]);
//cout << oper[num] << "*-*" << cc.size() << endl;
oper.resize(num);
}
}
else if(opert[1] == 'R'){
if(!cc.empty()){
char c = cc.top();
oper += c;
cc.pop();
}
cout << oper << endl;
}
}
else {
oper += opert;
while(!cc.empty()) cc.pop();
}
}

}


}

求后缀表达式

规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。

//中缀表达式转后缀表达式的方法:
//1.遇到操作数:直接输出(添加到后缀表达式中)
//2.栈为空时,遇到运算符,直接入栈
//3.遇到左括号:将其入栈
//4.遇到右括号:执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出。
//5.遇到其他运算符:加减乘除:弹出所有优先级大于或者等于该运算符的栈顶元素,然后将该运算符入栈
//6.最终将栈中的元素依次出栈,输出。
//fork from http://www.cnblogs.com/mygmh/archive/2012/10/06/2713362.html
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
#include <iostream>
#include <map>
#include <stack>
#include <cstdio>
using namespace std;

map<char, int> sym;
void init(){
sym.insert(make_pair('+',1));
sym.insert(make_pair('-',1));
sym.insert(make_pair('*',2));
sym.insert(make_pair('/',2));
}

bool isInt(char c){
return (c >= '0' && c <= '9');
}

double calc(double a, double b, char c){
if(c == '+') return a+b;
else if(c == '-') return b-a;
else if(c == '*') return b*a;
return b/a;

}

int main(){
string ss;
init();
int kase = 1;
while(cin >> ss){
cout << "Case #" << kase++ << ":" << endl;
stack<char> last;
stack<double> answer;
int qsize = ss.size();

for(int i = 0; i < qsize; ++i){
if(isInt(ss[i])){ /// num is the true num
double num = 0;
while(isInt(ss[i])){
num = num * 10 + ss[i] - '0';
++i;
}
int point = 0;
if(ss[i] == '.') {
++i;

while(isInt(ss[i])){
num = num * 10 + ss[i] - '0';
++i; ++point;
}
--i;
for(int j = 0; j < point; ++j){
num /= 10.0;
}

} else --i;
printf("%.*lf ", point, num);
answer.push(num);

}
else if (last.empty() || ss[i] == '('){
last.push(ss[i]);
}
else if(ss[i] == ')') {
while(last.top() != '('){
///calculate the num
double a = answer.top(); answer.pop();
double b = answer.top(); answer.pop();
answer.push(calc(a,b, last.top()));


///epichar
cout << last.top();
last.pop();
}
last.pop();
}
else{
while(!last.empty() && sym[ss[i]] <= sym[last.top()]){
double a = answer.top(); answer.pop();
double b = answer.top(); answer.pop();
answer.push(calc(a,b, last.top()));

cout << last.top();
last.pop();
}
last.push(ss[i]);
}
}
while(!last.empty()) {
double a = answer.top(); answer.pop();
double b = answer.top(); answer.pop();
answer.push(calc(a,b, last.top()));

cout << last.top();
last.pop();
}
cout << endl;
cout << "The answer is ";
double ans = answer.top();
printf("%g",ans);
cout << "." <<endl << endl;
}
}

/**************************************************************
Language: C++
Result: Accepted
Time:1668 ms
Memory:1296 kb
****************************************************************/

KMP

How to calc the next array.

  1. Calc the max length of suffix and prefix as follows:

    string a b a a b c a b a
    length 0 0 1 1 2 0 1 2 3
    next -1 0 0 1 1 2 0 1 2

    As you can see, the next array is the suffix equals prefix length array move on one step and init the first values as -1.

1
2
3
4
5
6
7
8
9
10
11
12
13
void GetNext(char* p,int next[])  
{
next[0] = -1;
int k = -1, = 0;
while (j < (p.length - 1)) {
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k]) {
++j, ++k, next[j] = k;
} else {
k = next[k];
}
}
}

Kruskal

在n个城市之间建设网络,只需保证连通即可,求最经济的架设方法。

input

  • 有多组输入数据。每组第一行输入三个整数n、m、c(1<=n,m,c<=100000),分别代表城市数量,可建道路数量和单位长度道路修建费用。接下来m行每行三个整数u、v(1<=u,v<=n)、d(1<=d<=100000)。代表可建道路的起点城市、终点城市和长度。

output

  • 每组数据输出一行,输出数据组数和使所有城市连通的最小费用,无法全部连通输出-1。

Kruskal算法的步骤:

1.对所有边进行从小到大的排序。 2.每次选一条边(最小的边),如果如果形成环,就不加入(u,v)中,否则加入。那么加入的(u,v)一定是最佳的。

并查集解决,略微优化:路径压缩

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
149
150
151
#include <bits/stdc++.h>

using namespace std;

///Kruskal算法的步骤:
///1.对所有边进行从小到大的排序。
///2.每次选一条边(最小的边),如果如果形成环,就不加入(u,v)中,否则加入。
///那么加入的(u,v)一定是最佳的。

///构成环的条件就是u,v已经连通,则不能添加
///关于这一点可用并查集查掌门解决

typedef struct Node{
int beg;
int las;
int len;
}Node;

Node node[10200];
int prev[10200] = {0};

bool cmp(Node a, Node b){
return a.len < b.len;
}

int finds(int x){
int r = x;
while(prev[r] != r){
r = prev[r];
}
return r;
}

bool join(int x, int y){
int fx = finds(x), fy = finds(y);
if(fx != fy){
prev[fx] = fy;///fx != fy,即无共同顶点,不构成三点环
return 1;
}
return 0;
}

int main(){
int cases; cin >> cases;
for(int kase = 1; kase <= cases; ++kase){
int n, m, c, sum = 0, tree = 0;
cin >> n >> m >> c;
for(int i = 1; i <= n; ++i){
prev[i] = i;
}
for(int i = 0; i < m; ++i){
int u, v, d;
cin >> node[i].beg >> node[i].las >> node[i].len;
}
sort(node, node + m, cmp);
for(int i = 0; i < m; ++i){
if(join(node[i].beg, node[i].las)){
sum += node[i].len;
}
}
for(int i = 1; i <= n; ++i){
if(prev[i] == i){
tree++;
}
}
cout << "Case #" << kase << ":" << " ";
if(tree > 1){
cout << "-1";
}
else {
cout << sum*c;
}
cout << endl;
}

}

> 傻逼的爆了一堆超时,二次弱逼式优化



#include <bits/stdc++.h>

using namespace std;

struct Node{
int beg;
int las;
int len;
}node[100100];

int prevs[100100];

int cmp(const Node &a, const Node &b){
return a.len < b.len;
}

int finds(int x){
int r = x;
while(prevs[r] != r){
r = prevs[r];
}
int t = x;
while(t != r){
int tf = prevs[t];
prevs[t] = r;
t = tf;
}
return r;
}

bool join(int x, int y){
int fx = finds(x), fy = finds(y);
if(fx != fy){
prevs[fx] = fy;
return 1;
}
return 0;
}

int main(){
int cases; scanf("%d", &cases);
for(int kase = 1; kase <= cases; ++kase){
int n, m, c, sides = 0;
long long sum = 0;
scanf("%d%d%d", &n, &m, &c);
for(int i = 1; i <= n; ++i){
prevs[i] = i;
}
for(int i = 0; i < m; ++i){
scanf("%d%d%d", &node[i].beg, &node[i].las, &node[i].len);
}
sort(node, node + m, cmp);
for(int i = 0; i < m; ++i){
if(join(node[i].beg, node[i].las)){
sum += node[i].len;
sides++;
if(sides == n-1) break;
}
}
printf("Case #%d: ", kase);

if(sides == n-1) {
printf("%lld",sum*c);
}else {
printf("-1");
}
printf("\n");
}
return 0;
}
CATALOG
  1. 1. Binary Search
  2. 2. RSA-basic
  3. 3. 拓扑排序
  4. 4. 拓扑排序
    1. 4.0.1. 方法:
    2. 4.0.2. 优化:如果要求最小字典序
    3. 4.0.3. 原题如上,旭哥思路的代码
  • 5. 并查集 (转)
  • 6. Eight queens
  • 7. Floyd
  • 8. 此算法由Robert W. Floyd(罗伯特·弗洛伊德)于1962年发表在“Communications of the ACM”上。同年Stephen Warshall(史蒂芬·沃舍尔)也独立发表了这个算法。Robert W.Floyd这个牛人是朵奇葩,他原本在芝加哥大学读的文学,但是因为当时美国经济不太景气,找工作比较困难,无奈之下到西屋电气公司当了一名计算机操作员,在IBM650机房值夜班,并由此开始了他的计算机生涯。
  • 9. Huffman
  • 10. Power of Two
  • 11. Sqrt(x)
  • 12. Redo or Undo
  • 13. 求后缀表达式
  • 14. KMP
  • 15. How to calc the next array.
  • 16. Kruskal
  • 17. 在n个城市之间建设网络,只需保证连通即可,求最经济的架设方法。
    1. 17.1. input
    2. 17.2. output
      1. 17.2.1. Kruskal算法的步骤:
      2. 17.2.2. 并查集解决,略微优化:路径压缩