9.22 p.m.小结

T1:问题 A: 亲戚

题目描述

    或许你并不知道,你的某个朋友是你的亲戚。他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。如果能得到完整的家谱,判断两个人是否是亲戚应该是可行的,但如果两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,那么检验亲戚关系实非人力所能及。在这种情况下,最好的帮手就是计算机。为了将问题简化,你将得到一些亲戚关系的信息,如Marry和Tom是亲戚,Tom和Ben是亲戚,等。从这些信息中,你可以推出Marry和Ben是亲戚。请写一个程序,对于我们的关于亲戚关系的提问,以最快的速度给出答案。

输入

    输入由两部分组成。
    第一部分以N,M开始。N为问题涉及的人的个数(1 <= N <= 2000)。这些人的编号为12,3, ... ,N。下面有M行(1<= M<= 1000000,每行有两个数ai,bi,表示已知ai和bi是亲戚。
    第二部分以Q开始。以下Q行有Q个询问(1<= Q<= 1000000)每行为ci、di,表示询问ci和di是否为亲戚。

输出

    对于每个询问C、d,输出一行:若c和d为亲威,则输出“Yes”,否则输出“No”。

样例输入

10 7

2 4

5 7

1 3

8 9

1 2

5 6

2 3

3

3 4

7 10

8 9

样例输出

Yes

No

Yes

题解

这道题最好的询问效率应该是O(1)。可以想象成染色。如果两个人是亲戚就染成相同的颜色。最后判断的时候直接看两个人是否颜色相同即可。当然这和并查集的思想很类似,只不过一个是“找祖先”,一个是“染颜色”。大体的思路和实现是相同的。每次都会错!!Yes、No的大小写需要十分注意!!

参考代码

#include<cstdio>
using namespace std;
int n,m,fa[201000],q;
int find(int v)
{ return fa[v]==v?v:fa[v]=find(fa[v]); }
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) fa[i]=i;
	while(m--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		a=find(a);b=find(b);
		if(a==b) continue;
		fa[a]=b;
	}
	scanf("%d",&q);
	while(q--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		a=find(a);b=find(b);
		if(a==b) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

T2:问题 C: 专用牛棚

目描述

N头牛,每头牛有个喝水时间,这段时间它将独占一个Stall 现在给出每头牛的喝水时间段,问至少要多少个Stall才能满足它们的要求

输入

第一行给出数字N,(1 <= N <= 50,000)

接下来给出这N头牛的喝水的时间A,B(1 <= A <= B <= 1,000,000)

输出

最少要安排几个Stall才能满足所有牛的需求

样例输入

3

1 2

2 3

3 4

样例输出

2

题解

这道题刚刚做的时候没看懂样例。结果把时间的右端点加1就可以了(3 4是表示从第3秒开始到第4秒结束,不妨就变成第3秒开始到第5秒开始,记录时间方便一点)。然后用拦截导弹的思想,先把左端点升序排序,然后做就OK了。很简单?不好意思,直接TLE(流泪了)。因为这种做法是很有可能超过1E8的。不妨换一种想法,用线段树的思想来看,如果把所有时间安排到一个轴上,那么所求的答案就是一个点经过时间段最多的段数。可以认为是不用那么多的stall就无法满足这一刻。然后每一个点上有几个时间段就意味着此时此刻要用几个stall。因此一个区间修改再加访问区间最大值就可以了。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;
int n,cnt=0,d[1010000],a[1010000],maxn=0;
int max1(int p,int q) { return p>q?p:q; }
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		int l,r;
		scanf("%d%d",&l,&r);
		a[l]++;
		a[r+1]--;
	}
	for(int i=1;i<=1000000;i++)
	{
		a[i]+=a[i-1];
		maxn=max1(maxn,a[i]);
	}
	printf("%d",maxn);
	return 0;
}

T3:问题 D: 蚂蚁搬沙

题目描述

山谷中住着一个巨大的蚂蚁王国,蚁穴外有一个整洁的广场,天气晴好时蚁群常在那里举行各种活动。这天夜里,天降沙尘,第2天,广场上堆满了大大小小的沙堆,蚁哨出去数了数共有n堆,蚁后要求她的臣民将广场上的沙堆清理掉。具体办法是:每次可以把广场上的任意k堆沙子合并成一堆,重复进行直至所有的沙堆最终合并成一堆。规定(1):2≤k≤m,m由蚁后指定,(2):每次合并k堆沙子的代价是这k堆沙子的重量和。 
你的任务是,对给定的n和m,计算出将n堆沙子最终合并成1堆的最小总代价。 
例如,广场上有7堆沙子,其重量分别为45,13,12,16,9,5,22。当m=3时,这些沙堆合并成一堆的最小总代价为199。当m=5时,这些沙堆合并成一堆的最小总代价为148。

输入

包含n+2个整数(n≤100000),其中第一行2个正整数,分别表示n堆沙子和每次合并时可以合并的最大堆数m,从第二行开始有n个数,表示n堆沙子的重量(1~500),数与数之间用空格隔开。

输出

只包含一个正整数,表示将n堆沙子合并成1堆所需的最小总代价。 

样例输入

7 3 

45 13 12 16 9 5 22 

样例输出

199 

题解

这道题与哈弗曼树有着紧密的联系。至于哈弗曼树,可以粗略的认为所有叶子节点就是我们输入的元素,某一节点的和等于其子节点的和,而答案就是所有除根节点外的所有节点之和。可以把题意缩减为建一课节点儿子不超过m的哈弗曼树,使得树根节点最小。举个栗子,如图1,这是题目中样例1的哈弗曼树画法。

                                                                图一

 

                                                                图二 

然而,我们还有一种计算方式,即令根节点高度为0,则答案为所有节点的数乘以当前高度的总和。由此我们想到,让尽量大的数在高度低的地方(高处不胜寒),然后找到一种方式建树……说实话,我也没找到什么好方法,因此暴力dfs搜索。现在再给出图二,是关于题目中第二组样例(148)的画法。因此观察可得出以下结论:

结论一:对于一层有n个节点,当前树的最大值一定在当前层数的叶子节点为0到n-1中取到(即扩展的节点数为1到n)。

结论二:当叶子节点的数量多于剩余可选数的数量时,不可再继续扩展。即应把剩余的数全部安放于这一层。

因此即可采用类似于dp的思想,用map作为记忆化的工具,由上往下的搜,从下到上记录答案。这样做能拿60分!

至于标程为什么能拿满分?主要是dfs要搜一下,而标程直接用的结论,相当于只算了一种树的状态。其实很容易想到,每一层都用才能最大化的减少答案,因此用类似贪心的思想(此处不给出证明,有兴趣者可自行探讨),把这个树通过补0的方式补成完全多叉树,然后维护一个优先队列就可以解决问题。

总结:能拿暴力分为什么不拿?考试时很难找到这样正确的贪心想法,反倒是朴素加优化简单,又具有正确性。如果找到了这种贪心,还可以用朴素算法对拍,可谓是一举多得!

参考代码(朴素非优化)

#include<cstdio>
#include<algorithm>
#define LL unsigned long long
using namespace std;
int n;LL a[201000],m;
LL min1(LL p,LL q) { return p<q?p:q; }
LL dfs(LL dep,LL k,int sr)
{
	if(k>=sr) return a[sr]*dep;
	LL minn=99999999999999999ll;
	for(int i=0;i<k;++i)
	minn=min1(minn,(a[sr]-a[sr-i])*dep+dfs(dep+1,(k-i)*m,sr-i));
	return minn;
}
int main()
{
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++) a[i]=a[i-1]+a[i];
	printf("%lld",dfs(1ll,m,n));
	return 0;
}

参考代码(朴素带map优化(记忆化))

#include<map> 
#include<cstdio>
#include<algorithm>
#define LL unsigned long long
using namespace std;
int n;LL a[201000],m;
map<long long,long long>st;
LL min1(LL p,LL q) { return p<q?p:q; }
LL dfs(LL dep,LL k,int sr)
{
	if(st.find((dep+k*100)*20000+sr)!=st.end()) //压缩+记忆化
	return st[(dep+k*100)*20000+sr];
	if(k>=sr) return a[sr]*dep;
	LL minn=99999999999999999ll;
	for(int i=0;i<k;++i)
	minn=min1(minn,(a[sr]-a
	[sr-i])*dep+dfs(dep+1,(k-i)*m,sr-i));
	st[(dep+k*100)*20000+sr]=minn;
	return minn;
}
int main()
{
	scanf("%d%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++) a[i]=a[i-1]+a[i];
	printf("%lld",dfs(1ll,m,n));
	return 0;
}

参考代码(标程)

#include<cstdio>
#include<queue>
using namespace std;
priority_queue<int,vector<int>,greater<int> >q;
int n,m;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int k;scanf("%d",&k);
		q.push(k);
	}
	int sis=n;
	while((sis-1)%(m-1)!=0) 
	{
		q.push(0);
		++sis;
	}
	int ans=0; 
	while(!q.empty())
	{
		int asn=0;
		for(int i=1;i<=m;i++)
		{
			asn+=q.top();q.pop();
		}
		ans+=asn;
		if(q.empty())
		{
			printf("%d",ans);
			return 0;
		}
		q.push(asn);
	}
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值