9.8 a.m.小结
作者:互联网
T1:问题 A: 医院设置
题目描述
设有一棵二叉树(如图),其中圈中的数字表示结点中居民的人口,圈边上数字表示结点编号。现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻结点之间的距离为1。就本图而言,若医院建在1处,则距离和=4+12+2*20+2* 40=136;若医院建在3处,则距离和=4* 2+13+ 20+40=81...
输入
第一行一个整数n,表示树的结点数(n<=100)。接下来的n行每行描述了一个结点的状况,包含三个整数,整数之间用空格(一个或多个)分隔,其中:第一个数为居民人口数;第二个数为左链接,为0表示无链接;第三个数为右链接,为0表示无链接。
输出
只有一个整数,表示最小距离和。
样例输入
5
13 2 3
4 0 0
12 4 5
20 0 0
40 0 0
样例输出
81
题解
这道题乍一看要用什么高级的东西,结果直接枚举根节点就OK了。首先链式前向心建双向边,然后枚举1到n每个作为根节点遍历图,同时记录深度,每一步计算出ans的值,然后取ans的最小值就可以了。
参考代码
#include<cstdio>
using namespace std;
struct tree
{
int nex,to;
}tr[1000];
int min1(int p,int q) { return p<q?p:q; }
int n,head[1000],cnt=0,d[1000],ans=999999999;
void build_tree(int u,int v)
{
tr[++cnt].nex=head[u];
tr[cnt].to=v;
head[u]=cnt;
}
int search(int x,int f,int dep)
{
int sum=d[x]*dep;
for(int i=head[x];i;i=tr[i].nex)
{
int to=tr[i].to;
if(to==f) continue;
sum+=search(to,x,dep+1);
}
return sum;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&d[i]);
int l,r;
scanf("%d%d",&l,&r);
if(l)
{
build_tree(i,l);
build_tree(l,i);
}
if(r)
{
build_tree(i,r);
build_tree(r,i);
}
}
for(int i=1;i<=n;i++)
ans=min1(ans,search(i,0,0));
printf("%d",ans);
return 0;
}
T2:问题 B: 黑暗城堡
题目描述
知道黑暗城堡有 N 个房间,M 条可以制造的双向通道,以及每条通道的长度。
城堡是树形的并且满足下面的条件:
设 Di为如果所有的通道都被修建,第 i 号房间与第 1 号房间的最短路径长度;
而 Si 为实际修建的树形城堡中第 i 号房间与第 1 号房间的路径长度;
要求对于所有整数 i(1≤i≤N),有 Si=Di 成立。
你想知道有多少种不同的城堡修建方案。当然,你只需要输出答案对 231−1 取模之后的结果就行了。
输入
第一行为两个由空格隔开的整数 N,M;
第二行到第 M+1 行为 3 个由空格隔开的整数 x,y,l:表示 x 号房间与 y 号房间之间的通道长度为 l。
输出
一个整数:不同的城堡修建方案数对 231−1 取模之后的结果。
样例输入
4 6
1 2 1
1 3 2
1 4 3
2 3 1
2 4 2
3 4 1
样例输出
6
提示
【样例说明】
一共有 4 个房间,6 条道路,其中 1 号和 2 号,1 号和 3 号,1 号和 4 号,2 号和 3 号,2 号和 4 号,3 号和 4 号房间之间的通道长度分别为 1,2,3,1,2,1。
而不同的城堡修建方案数对 231−1 取模之后的结果为 6。
【数据规模】
对于全部数据,1≤N≤1000,1≤M≤N(N−1)/2,1≤l≤200。
题解
这道题比较迷惑,因为它就考了SPFA。现在来定义一个num数组,num[i]表示所有指向i这个点的点中,能够使i通过这个点走向起始点的距离恰好是最短距离d[i]的点的个数,其实也可以看做是边的个数。现在来想一想,如果要找一条路径满足题设条件,那么就需要满足对于每一个点,都能满足这个点能够通过上一个点(边)到达原点恰好是最短路d,因此只需要在num中随便选取一条即可。对于每一个点都有num[i]个选法,所以我们只需要把num的值全部乘起来就能得到题目所求。如何求num?参考定义,只需要在走SPFA的过程中,如果更新最短路,num=1,如果等于最短路的长度,num++,就可以了。注意num是数组。因为要取模,所以要开long long。
参考代码
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
struct tree
{
int nxt,to,dis;
}tr[1000100];
queue<int>q;
int head[1000100],cnt=0;
void build_tree(int u,int v,int d)
{
tr[++cnt].nxt=head[u];
tr[cnt].to=v;
tr[cnt].dis=d;
head[u]=cnt;
}
int n,m,d[1000100],num[1000100],vis[1000100];
int main()
{
scanf("%d%d",&n,&m);
memset(d,127/3,sizeof(d));
for(int i=1;i<=m;i++)
{
int u,v,d;
scanf("%d%d%d",&u,&v,&d);
build_tree(u,v,d);
build_tree(v,u,d);
}
d[1]=0;num[1]=1;vis[1]=1;
q.push(1);
while(!q.empty())
{
int pt=q.front();q.pop();
vis[pt]=0;
for(int i=head[pt];i;i=tr[i].nxt)
{
int to=tr[i].to,dis=tr[i].dis;
if(d[to]>d[pt]+dis)
{
d[to]=d[pt]+dis;
num[to]=1;
if(!vis[to])
{
vis[to]=1;
q.push(to);
}
}
else if(d[to]==d[pt]+dis) num[to]++;
}
}
long long ans=1ll;
for(int i=1;i<=n;i++)
ans=(ans*(long long)num[i])%2147483647ll;
printf("%lld",ans);
return 0;
}
T3:问题 C: 繁忙的都市
题目描述
城市 C 是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市 C 的道路是这样分布的:城市中有 n 个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:
1. 改造的那些道路能够把所有的交叉路口直接或间接的连通起来。
2. 在满足要求 1 的情况下,改造的道路尽量少。
3. 在满足要求 1、2 的情况下,改造的那些道路中分值最大值尽量小。
作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。
输入
第一行有两个整数 n,m 表示城市有 n 个交叉路口,m 条道路。接下来 m 行是对每条道路的描述,u, v, c 表示交叉路口 u 和 v 之间有道路相连,分值为 c。(1≤n≤300,1≤c≤10000)
输出
两个整数 s, max,表示你选出了几条道路,分值最大的那条道路的分值是多少。
样例输入
4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8
样例输出
3 6
题解
这道题实质上考的是最小生成树。
我们来模拟最小生成树的过程,每次选取合法的当前所有剩余边中的最小边,直到遍历了所有的点。我们来假设有k个点,最小生成树为p,假设第i个点和第j个点恰好有一条边在最小生成树上。我们假设满足题设的树不是最小生成树,根据上述最小生成树的建法,我们来考察,必然是先不选最小边,而选择选次小边,因为次小边一定大于最小边,这样造成的结果就是要么次小边会被选,要么会使结果大于最小生成树(要么入选要么不入选),对于1个点就不会使答案更优,那么对于n个点应该也是一样的。这样我们就直接求最小生成树即可。考试时举样例发现举不出反例(多举几组,比如20组),那么就可以尝试用这种方法骗分,很多时候都能骗到满分(想想就可以了)。
参考代码
#include<cstdio>
#include<queue>
using namespace std;
struct node
{
int dis,from,to;
};
priority_queue<node>q;
bool operator < (node n,node m)
{
return n.dis>m.dis;
}
int n,m,ans=-999999999;
int fa[1000];
int find(int v)
{
if(fa[v]==v) return v;
else return fa[v]=find(fa[v]);
}
int max1(int x,int y) { return x>y?x:y; }
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int u,v,d;
scanf("%d%d%d",&u,&v,&d);
node pt;pt.dis=d;
pt.from=u;pt.to=v;
q.push(pt);
}
while(!q.empty())
{
node pt=q.top();q.pop();
int from=find(pt.from),to=find(pt.to);
if(from==to) continue;
fa[from]=to;
ans=max1(ans,pt.dis);
}
printf("%d %d",n-1,ans);
return 0;
}
T4:问题 D: 走廊泼水节
题目描述
【简化版题意】给定一棵N个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。求增加的边的权值总和最小是多少。
我们一共有N个OIER打算参加这个泼水节,同时很凑巧的是正好有N个水龙头(至于为什么,我不解释)。N个水龙头之间正好有N-1条小道,并且每个水龙头都可以经过小道到达其他水龙头(这是一棵树,你应该懂的..)。但是OIER门为了迎接中中的挑战,决定修建一些个道路(至于怎么修,秘密~),使得每个水龙头到每个水龙头之间都有一条直接的道路连接(也就是构成一个完全图呗~)。但是OIER门很懒得,并且记性也不好,他们只会去走那N-1条小道,并且希望所有水龙头之间修建的道路,都要大于两个水龙头之前连接的所有小道(小道当然要是最短的了)。所以神COW们,帮那些OIER们计算一下吧,修建的那些道路总长度最短是多少,毕竟修建道路是要破费的~~
输入
本题为多组数据~
第一行t,表示有t组测试数据
对于每组数据
第一行N,表示水龙头的个数(当然也是OIER的个数);
2到N行,每行三个整数X,Y,Z;表示水龙头X和水龙头Y有一条长度为Z的小道
输出
对于每组数据,输出一个整数,表示修建的所有道路总长度的最短值。
样例输入
2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5
样例输出
4
17
提示
【数据范围与约定】
每个测试点最多10组测试数据
50% n<=1500;
100% n<=6000
100% z<=100
【样例解释】
第一组数据,在2和3之间修建一条长度为4的道路,是这棵树变成一个完全图,且原来的树依然是这个图的唯一最小生成树.
题解
根据贪心的思路,加的边权越小,总和越小。但是我们还要保证这是在满足不会影响最小生成树建树的条件下达到的。那么我们先排个序,按升序来排,模拟最小生成树的过程,然后O(n)遍历一遍所有最小生成树的边,令两个端点为x,y,如果x、y在同一并查集中,说明这条边已经没有用处了,所以直接continue,否则,就是把x和y的并查集合并。自然,这两个并查集中所有完全图剩余的边就不会被计入答案,为了使这些边不会提前进入到最小生成树的答案中,我们就需要让所有的边边权为当前最小生成树的边加一,然后根据乘法定理,x并查集的元素与y并查集中的元素任意配对,就能得到size[x]*size[y]条边,再减去x、y的这一条边,就能O(1)累加ans,然后合并x、y的并查集。注意输出多组数据,要更新数组。
参考代码
#include<cstdio>
#include<algorithm>
using namespace std;
int t;
struct tree
{
int from,to,dis;
}tr[6001];
int n,fa[6001],size[6001];
bool comp1(tree p,tree q) { return p.dis<q.dis; }
int find(int v)
{
if(fa[v]==v) return v;
else return fa[v]=find(fa[v]);
}
int main()
{
scanf("%d",&t);
while(t--)
{
long long ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
fa[i]=i;
size[i]=1;
}
for(int i=1;i<n;i++)
scanf("%d%d%d",&tr[i].from,&tr[i].to,&tr[i].dis);
sort(tr+1,tr+n,comp1);
for(int i=1;i<n;i++)
{
int x=find(tr[i].from);
int y=find(tr[i].to);
if(x==y) continue;
ans+=1ll*(tr[i].dis+1ll)*1ll*(size[x]*size[y]-1ll);
fa[x]=y;
size[y]+=size[x];
}
printf("%lld\n",ans);
}
return 0;
}
T5:问题 E: 最优贸易
题目描述
C国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1条。
C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到C国旅游。当他得知“同一种商品在不同城市的价格可能会不同”这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚一点旅费。设C国 n 个城市的标号从 1~n,阿龙决定从1号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以被重复经过多次,但不要求经过所有 n 个城市。
阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。因为阿龙主要是来C国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。
输入
第一行包含 2 个正整数n 和m,中间用一个空格隔开,分别表示城市的数目和道路的
数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这n 个城
市的商品价格。
接下来 m 行,每行有3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果z=1,表示这条道路是城市x 到城市y 之间的单向道路;如果z=2,表示这条道路为城市x 和城市y 之间的双向道路。
输出
一个整数,表示答案。
样例输入
5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2
样例输出
5
提示
输入数据保证 1 号城市可以到达n 号城市。
对于 10%的数据,1≤n≤6。
对于 30%的数据,1≤n≤100。
对于 50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 100%的数据,1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市
水晶球价格≤100。
题解
这道题也是一道需要正反转化的题。我们要得到最大的差价,就是拿每个点到起点的最小价值减去每个点到终点的最大价值。所以我们需要建双图,同时要建反图,一个处理到起点的最小值,另一个处理到终点的最大值。因此跑2遍SPFA就能够得到答案。
参考代码
#include<cstdio>
#include<queue>
using namespace std;
struct tree
{
int nxt,to;
};
tree tr1[500010],tr2[500010];
int head1[500010],cnt1=0,head2[500010],cnt2=0,vis1[500010],vis2[500010];
queue<int>q;
int n,m,val[100010],dp1[100010],dp2[100010];
void build_tree(int u,int v)
{
tr1[++cnt1].nxt=head1[u];
tr1[cnt1].to=v;
head1[u]=cnt1;
tr2[++cnt2].nxt=head2[v];
tr2[cnt2].to=u;
head2[v]=cnt2;
}
int min1(int x,int y) { return x<y?x:y; }
int max1(int x,int y) { return x>y?x:y; }
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
for(int i=1;i<=m;i++)
{
int pd,u,v;
scanf("%d%d",&u,&v);
scanf("%d",&pd);
build_tree(u,v);
if(pd==2)
build_tree(v,u);
}
q.push(1);vis1[1]=1;
for(int i=1;i<=n;i++)
{
dp1[i]=707406378;
dp2[i]=-707406378;
}
while(!q.empty())
{
int pt=q.front();q.pop();
vis1[pt]=0;
for(int i=head1[pt];i;i=tr1[i].nxt)
{
int to=tr1[i].to;
if(dp1[to]>min1(dp1[pt],val[to]))
{
dp1[to]=min1(dp1[pt],val[to]);
if(!vis1[to])
{
q.push(to);
vis1[to]=1;
}
}
}
}
q.push(n);vis2[n]=1;
while(!q.empty())
{
int pt=q.front();q.pop();
vis2[pt]=0;
for(int i=head2[pt];i;i=tr2[i].nxt)
{
int to=tr2[i].to;
if(dp2[to]<max1(dp2[pt],val[to]))
{
dp2[to]=max1(dp2[pt],val[to]);
if(!vis2[to])
{
q.push(to);
vis2[to]=1;
}
}
}
}
int ans=-999999999;
for(int i=1;i<=n;i++)
if(dp1[i]!=707406378&&dp2[i]!=-707406378)
ans=max1(ans,dp2[i]-dp1[i]);
printf("%d",ans);
return 0;
}
T6:问题 F: 奖金
题目描述
由于无敌的凡凡在2005年世界英俊帅气男总决选中胜出,Yali Company总经理Mr. Z心情好,决定给每位员工发奖金。公司决定以每个人本年在公司的贡献为标准来计算他们得到奖金的多少。
于是Mr.Z下令召开m方会谈。每位参加会谈的代表提出了自己的意见:“我认为员工a的奖金应该比b高!”Mr.Z决定要找出一种奖金方案,满足各位代表的意见,且同时使得总奖金数最少。每位员工奖金最少为100元。
输入
第1行两个整数n、m,表示员工总数和代表数;
以下m行,每行2个整数a、b,表示某个代表认为第a号员工奖金应该比第b号员工高。
输出
若无法找到合理方案,则输出“Poor Xed”;否则输出一个数表示最少总奖金。
样例输入
2 1
1 2
样例输出
201
提示
【数据范围】
80%的数据满足:n<= 1000,m<= 2000;
100%的数据满足:n< = 10000,m< =20000。
题解
鉴于这道题数据范围很小,选择直接建图暴搜。我们还是选择用SPFA扫描,同时我们需要按拓扑序入队。每次f的初值是100,每次遇到了i比j要高,就直接更新i的答案,与f[j]+1比大小,找一个最大值。最后ans就是所有f的和。特别注意,问题无解就是存在环,拓扑序无法满足。
参考代码
#include<cstdio>
#include<queue>
using namespace std;
struct tree
{
int nxt,to;
}tr[20010];
queue<int>q;
int head[20010],cnt=0;
int n,m,seg[10010],dp[10010],a[10010],ans=0;
int max1(int p,int q) { return p>q?p:q; }
void build_tree(int u,int v)
{
tr[++cnt].nxt=head[u];
tr[cnt].to=v;
head[u]=cnt;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) dp[i]=100;
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
build_tree(v,u);
seg[u]++;
}
for(int i=1;i<=n;i++)
{
if(seg[i]==0)
q.push(i);
}
while(!q.empty())
{
int tod=q.front();q.pop();
a[++ans]=tod;
for(int i=head[tod];i;i=tr[i].nxt)
{
int to=tr[i].to;
seg[to]--;
dp[to]=max1(dp[to],dp[tod]+1);
if(seg[to]==0) q.push(to);
}
}
if(ans!=n)
{
printf("Poor Xed");
return 0;
}
int ex=0;
for(int i=1;i<=n;i++)
ex+=dp[i];
printf("%d",ex);
return 0;
}
T7:问题 G: 对称二叉树
题目描述
如果二叉树的左右子树的结构是对称的,即两棵子树皆为空,或者皆不空,则称该二叉树是对称的。编程判断给定的二叉树是否对称.
例:如下图中的二叉树 T1 是对称的,T2 是不对称的。
输入
二叉树用顺序结构给出,若读到#则为空,二叉树 T1=ABCDE,T2=ABCD#E
输出
如果二叉树是对称的,输出“Yes”,反之输出“No”。
样例输入
ABCDE
样例输出
Yes
题解
这是一道搜索题,我们只需要在建树的过程中判断是否存在一个节点的儿子只有1个的情况,如果有,pd=1,return,否则,如果最后pd=0,那么就说明这是一棵对称二叉树。
参考代码
#include<cstdio>
#include<cstring>
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
char s[1000001];int pd=0,len;
void make_tree(int x)
{
if(pd==1||lc>len) return;
if(s[lc-1]!='#'&&s[rc-1]!='#'&&rc<=len)
{
make_tree(lc);
make_tree(rc);
return;
}
else if(s[lc-1]=='#'&&s[rc-1]=='#')
{
make_tree(lc);
make_tree(rc);
return;
}
else
{
pd=1;
return;
}
}
int main()
{
scanf("%s",s);
len=strlen(s);
make_tree(1);
if(pd==0) printf("Yes");
else printf("No");
return 0;
}
问题 H: 小球
题目描述
许多的小球一个一个的从一棵满二叉树上掉下来组成 FBT(Full Binary Tree,满二叉树),每一时间,一个正在下降的球第一个访问的是非叶子节点。然后继续下降时,或者走右子树,或者走左子树,直到访问到叶子节点。决定球运动方向的是每个节点的布尔值。最初,所有的节点都是 false,当访问到一个节点时,如果这个节点是 false,则这个球把它变成 true,然后从左子树走,继续它的旅程。如果节点是 true,则球也会改变它为 false,而接下来从右子树走。满二叉树的标记方法如下图:
因为所有的节点最初为 false,所以第一个球将会访问节点 1,节点 2 和节点 4,转变节点的布尔值后在在节点 8 停止。第二个球将会访问节点 1、3、6,在节点 12 停止。明显地,第三个球在它停止之前,会访问节点 1、2、5,在节点 10 停止。
现在你的任务是,给定 FBT 的深度 D,和 I,表示第 I 个小球下落,你可以假定 I 不超过给定的 FBT 的叶子数,写一个程序求小球停止时的叶子序号。
输入
仅一行包含两个用空格隔开的整数 D 和 I。其中 2<=D<=20,1<=I<=524288。
输出
对应输出第 I 个小球下落停止时的叶子序号。
样例输入
4 2
样例输出
12
题解
这道题,我不懂,只知道10的13次方好像都能过???搜索呗,观看下述代码。
或者说:if太快了。
参考代码
#include<cstdio>
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
int d,i,num[2100000];bool vis[2100000];
int search(int x,int dep)
{
if(dep==d) return x;
if(vis[x]==false)
{
vis[x]=true;
return search(lc,dep+1);
}
else
{
vis[x]=false;
return search(rc,dep+1);
}
}
int main()
{
scanf("%d%d",&d,&i);
for(int j=1;j<=i;j++)
{
num[j]=search(1,1);
}
printf("%d",num[i]);
return 0;
}
标签:输出,int,样例,9.8,道路,include,小结,节点 来源: https://blog.csdn.net/Penguin_Wang/article/details/120229256