第一题 这题据说有最小生成树和并查集两种方法。
看了几种别的同学的解法,没有看到用最小生成树的,都是并查集
其他同学的方法是找连通块,连通块的个数减一就是最少要修的路(当然建立连通块用的是并查集)
我的方法是:至少要修n-1条路
对连通的每条路进行判断,如果连接的两个点本来就联通的,这条路为非必须路,不计数。否则就是一条必须路。必须路一共要n-1条,减去已经有的,就是还需要修的
A. 修路 2014新生暑假个人排位赛06
题目描述
小弱的学校很喜欢修路,现在给你一张他学校的地图,地图上有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点)
445. 高兴
题目描述
小弱有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;
}
第三题
449. 排序
题目描述
给你n个数,请你将他们从小到大输出出来。
输入格式
多组数据。
输入第一行为n,接下来一行给出n个数,每个数在0到10000。
输入文件大小为8.2MB。
输出格式
输出一行,排序之后的n个数。
输入样例
3
4 2 1
输出样例
1 2 4
这题的解法除了计数排序外,是针对很多组短数据,如果是像我说的,数据量大,用排序的话不见得最优,当然可以加个判断,不同的数据是否够大
两种判断(不过在这题由于不是学长的考点,可能学长给的数据里没有那种很长很长数据范围遍布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 所以卡着时间过了
还是手写的比较好吧
444. 爱好和平
题目描述
在星际时代,每个帝国都靠着贸易路线连接着各个联盟星球,这些贸易路线都是双向可达的。一个帝国的综合实力由他贸易连接着的联盟星球数决定。
学姐作为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;
}