第六次新生排位赛 手写链表 树形dp 计数排序 状态压缩dp

来源:互联网 发布:淘宝运营是做什么的 编辑:程序博客网 时间:2024/06/03 20:47


第一题  这题据说有最小生成树和并查集两种方法。

看了几种别的同学的解法,没有看到用最小生成树的,都是并查集

其他同学的方法是找连通块,连通块的个数减一就是最少要修的路(当然建立连通块用的是并查集)

我的方法是:至少要修n-1条

对连通的每条路进行判断,如果连接的两个点本来就联通的,这条路为非必须路,不计数。否则就是一条必须路。必须路一共要n-1条,减去已经有的,就是还需要修的

时间限制 3000 ms 内存限制 65536 KB

题目描述

小弱的学校很喜欢修路,现在给你一张他学校的地图,地图上有n个点和m条双向边,每条边代表一条路,这条路有可能是畅通,也有可能正在修路。大家都知道修路使得交通很不方便。所有小弱很想学校快快的把路修好,使得他能够很轻松的到达主楼915去刷题。但考虑到学校的施工能力有限,小弱想让你帮他算出学校需要集中力量马上修好的最少路数,使得他能够从学校任意点出发,在不经过正在施工的路下到达主楼(编号为1)。

输入格式

有多组数据。
每组数据以n( 1<=n<=10000), m(1<=m<=200000)开头。接下来一行有m行数。每行有三个数,对应于u, v, s,分别为这条路的两端点(编号从1到n)和路况,s = 0代表畅通, s = 1 代表正在修路。输入保证图是连通图。

 

输出格式

对每组数据输出对应的最少路数。

输入样例

3 21 2 01 3 13 21 2 01 3 03 21 2 11 3 1

输出样例

102
#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>  using namespace std; int father[10005]; int main(){    int n, m;    while (~scanf("%d %d", &n, &m))    {        for (int i=1; i<=n; i++)            father[i] = i;        int ans = 0;        for (int i=1; i<=m; i++)        {            int u, v, s;            scanf("%d %d %d", &u, &v, &s);            if (s==0)            {                ans++;                while (father[u]!=u)                    u = father[u];                while (father[v]!=v)                    v = father[v];                if (u==v)                    ans--;                else                    father[v] = u;            }        }        ans = n-1-ans;        printf("%d\n", ans);    }}


关于最小生成树的做法我也准备去研究一下(看了半天没找到,不知道是怎样的)


第二题

使用了状态压缩dp,当然递推式有很多种,我直接抄了小白书上的,用递推确实比用递归快了一点,而且内存用得少一些,不过个人感觉递归会比较好理解吧

dp[s][v] 表示的是以v为起点走完不在s内的所有点一条路(最大或最小)权值。其中v在集合s内。

dp[s][v]  = max(dp[s][v] , d[v][u] + dp[s | 1 << u][u] )

这个递推式是从大到小递推的,太难理解了(唉,抄别人的就是不好,要理解很长时间,要是我自己写肯定不会写这么深奥的)

所以对于本题dp[1<<i][i] 表示的是以i为起点走遍所有点的最大权值(显然此时s中只有i点)

时间限制 4000 ms 内存限制 65536 KB

题目描述

小弱有n个玩具,现在小弱想把他们排列成一行,把玩具j放在玩具i的右边相邻的位置上,他将得到一个高兴值Hij.

输入格式

输入有多组数据。
每组数据以一个整数n(n <= 18)开头。
接下来n行, 每行n个整数。 第i行第j列Hij( Hij的绝对值 <= 10000)代表把玩具j放在玩具i的右边相邻的位置时高兴值。输入保证Hii = 0,最左边只考虑与右边相邻的玩具,最右边只考虑与左边相邻的玩具。

 

输出格式

对于每组数据输出最大高兴值。

输入样例

20 12 030 -1 73 0 33 3 0

输出样例

2 10
这种是递归

<pre name="code" class="cpp"><pre name="code" class="cpp">#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>#define INF 1800000using namespace std; int n;int d[20][20];int dp[1 << 18][20];bool vis[1 << 18][20];//也可以dp设个初始值,不等于初始值就表示访问过了int rec(int s, int v){    if (vis[s][v] == true)        return dp[s][v];    if (s==((1<<n)-1))        return dp[s][v] = 0;        int res = -INF;    for (int u=0; u<n; u++)         if (!(s >> u & 1))            res = max(res, rec(s | 1 << u, u) + d[v][u]);//        vis[s][v] = true;    return dp[s][v] = res;}int main(){    while (~scanf("%d", &n))    {        memset(vis, 0, sizeof vis);                for (int i=0; i<n; i++)            for (int j=0; j<n; j++)                scanf("%d", &d[i][j]);                int ans = -INF;        for (int i=0; i<n; i++)            ans = max(ans, rec(1<<i, i));                printf("%d\n", ans);    }    return 0;}



这种是递推


#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>#define INF 1800000using namespace std;int n;int d[20][20];int dp[1 << 18][20];int main(){    while (~scanf("%d", &n))    {        for (int i=0; i<n; i++)            for (int j=0; j<n; j++)                scanf("%d", &d[i][j]);        for (int s=0; s < 1<<n; s++)            fill(dp[s], dp[s]+n, -INF);        for (int i=0; i<n; i++)            dp[(1<<n)-1][i] = 0;//加减号的优先级比左移运算符高        for (int s=(1<<n)-2; s>0; s--)            for (int v=0; v<n; v++)                if (s>>v & 1)                    for (int u=0; u<n; u++)                        if ((u!=v)&&(!(s>>u&1)))                            dp[s][v] = max(dp[s][v], dp[s|1<<u][u] + d[v][u]);        //犯傻了。这里括号写错,主要是抄错了,写成了max(dp[s][v], dp[s|1<<u][u]) + d[v][u];        //调试得眼睛都花了,才找出来                        int ans = -INF;        for (int i=0; i<n; i++)        {            ans = max(ans, dp[1<<i][i]);        }        printf("%d\n", ans);    }    return 0;}

第三题

时间限制 1000 ms 内存限制 65536 KB

题目描述

给你n个数,请你将他们从小到大输出出来。

输入格式

多组数据。

输入第一行为n,接下来一行给出n个数,每个数在0到10000。

输入文件大小为8.2MB。

输出格式

输出一行,排序之后的n个数。

输入样例

34 2 1

输出样例

1 2 4
真的是比较坑,我以为会是一组数据很多很多数字,还想n可能要用long long 来储存,事实上是很多组短数据,,,,,

这题的解法除了计数排序外,是针对很多组短数据,如果是像我说的,数据量大,用排序的话不见得最优,当然可以加个判断,不同的数据是否够大


两种判断(不过在这题由于不是学长的考点,可能学长给的数据里没有那种很长很长数据范围遍布10000的,所以我比过了,没有速度上的优势)

#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>using namespace std;int a[10005];int b[10005];int n;int main(){    memset(a, 0, sizeof a);    while (~scanf("%d", &n))    {        int temp;        int maxn = 0;        int num = 0;        int minn = 10000000;        for (int i=0; i<n; i++)        {            scanf("%d", &temp);            if (!a[temp])            {                b[num++] = temp;                minn = min(minn, temp);                maxn = max(maxn, temp);            }            a[temp]++;        }        if (num < 2000)        {            sort(b, b+num);            for (int j=0; j<num; j++)            {                while(a[b[j]])                {                    a[b[j]]--;                    n--;                    if (n==0)                    {                        printf("%d\n", b[j]);                    }                    else                        printf("%d ", b[j]);                }            }        }        else        {            for (int j=minn; j<=maxn; j++)            {                while(a[j])                {                    a[j]--;                    n--;                    if (n==0)                    {                        printf("%d\n", b[j]);                    }                    else                        printf("%d ", b[j]);                }            }        }    }    return 0;}

针对学长这次考点的


#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>  using namespace std; int a[10005];int b[10005];int n;int main(){    memset(a, 0, sizeof a);    while (~scanf("%d", &n))    {        int temp;        int maxn = 0;        int num = 0;        for (int i=0; i<n; i++)        {            scanf("%d", &temp);            if (!a[temp])                b[num++] = temp;            a[temp]++;        }        sort(b, b+num);        for (int j=0; j<num; j++)        {            while(a[b[j]])            {                a[b[j]]--;                n--;                if (n==0)                {                    printf("%d\n", b[j]);                }                else                    printf("%d ", b[j]);            }        }    }    return 0;}


第四题,我又忧伤的考虑复杂了,建棵树dfs遍历一遍就可以了,我还在一个劲地想有没有什么简便方法

由于我用的是vector 所以卡着时间过了

还是手写的比较好吧

时间限制 1000 ms 内存限制 65536 KB

题目描述

在星际时代,每个帝国都靠着贸易路线连接着各个联盟星球,这些贸易路线都是双向可达的。一个帝国的综合实力由他贸易连接着的联盟星球数决定。
学姐作为Mays帝国的领袖,长期与Luke帝国保持着敌对关系,爱好和平的学姐希望结束长达几个世纪的战争,于是找实验室定做了一颗代号小苹果的炸弹,可以定点摧毁一颗星球,这颗星球被毁后,与它相连的全部贸易就都被切断了,这样Luke帝国可能就被切断为一个小联盟,他们就再也不会对学姐的地位构成威胁啦~
经过调查,Luke帝国为了节约经费,他的联盟星之间都有且仅有一条直接或间接的贸易通路。
现在给出Luke帝国的贸易线路,学姐想知道摧毁哪一颗行星可以使得分裂后的若干Luke联盟威胁最大的分部最小。

输入格式

输入有多组数据,组数不大于10组。每一组开头一行为n,m,表示Luke帝国的联盟星球数量,和贸易关系数,接下来m行,每行两个整数u,v,表示星球u,v之间存在直接的贸易路线,1<=u,v<=n,1<=n,m<=100000

输出格式

输出一个数表示推荐学姐摧毁的星球,如果有多解,输出编号最小的一个。

输入样例

5 41 21 31 44 5

输出样例

1
#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>  using namespace std; vector <int> a[100003];int n, m;int minans, mini;int cal(int fomer, int i){    int s = 1;    int help = 0;         for (int j=0; j<a[i].size(); j++)        {            if (a[i][j]==fomer) continue;            else            {                int tll = cal(i, a[i][j]);                help = max(help, tll);                s = s+tll;            }        }        help = max(help, n-s);        if ((help<minans)||((help==minans)&&(mini>i)))        {            minans = help;            mini = i;        }     return s;}int main(){    while (~scanf("%d %d", &n, &m))    {        for (int i=1; i<=n; i++)            a[i].clear();        minans = 1e9;        mini = 0;        int u, v;        for (int i=0; i<m; i++)        {            scanf("%d %d", &u, &v);            a[u].push_back(v);            a[v].push_back(u);        }        if (n==2)            printf("%d\n", min(u, v));        else        {            cal(0, 1);            printf("%d\n", mini);        }     }    return 0;}

参考S的手写链表


#include <iostream>#include <cstdio>#include <string.h>#include <vector>#include <cmath>#include <algorithm>using namespace std;struct{    int to, next;}a[200005];//maxn<<1, 最大值的两倍int head[100005];int n, m, tot;int minans, mini;void add(int u, int v){    a[tot].to = v;    a[tot].next = head[u];    head[u] = tot++;    a[tot].to = u;    a[tot].next = head[v];    head[v] = tot++;}int cal(int fomer, int u){    int s = 1;    int help = 0;    for (int j=head[u]; j!=-1; j = a[j].next)//手写链表a的结束标志在于head的初始化    {        int v= a[j].to;        if (v==fomer) continue;        else        {            int tll = cal(u, v);            help = max(help, tll);            s = s+tll; // s用于统计所有子节点加上自己的节点个数        }    }    help = max(help, n-s); // help用于求子节点中节点数的最大,即除去它以后的最大分部    if ((help<minans)||((help==minans)&&(mini>u)))    {        minans = help;        mini = u;    }    return s;}int main(){    while (~scanf("%d %d", &n, &m))    {        memset(head, -1, sizeof head);        minans = 1e9;        //mini = 0;        int u, v;        for (int i=0; i<m; i++)        {            scanf("%d %d", &u, &v);            add(u, v);        }        tot = 0; // 记得这里初始化        cal(0, 1);        printf("%d\n", mini);    }    return 0;}


0 0
原创粉丝点击