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


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

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

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

我的方法是:至少要修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 2
1 2 0
1 3 1
3 2
1 2 0
1 3 0
3 2
1 2 1
1 3 1

输出样例

1
0
2
#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,最左边只考虑与右边相邻的玩具,最右边只考虑与左边相邻的玩具。

 

输出格式

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

输入样例

2
0 1
2 0
3
0 -1 7
3 0 3
3 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 1800000
using 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 1800000
using 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个数。

输入样例

3
4 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 4
1 2
1 3
1 4
4 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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值