ECNU XCPC 2021 Selection Round #3

A. Word Processor

题目大意:

给定一个包含N个单词的字符串,要求将这个字符串分成若干行,使得每一行单词的字符数(不包括空格)不超过K,最后输出结果。
数据范围:1<=N<=100,1<=单词长度<=15,1<=K<=80。

题目分析:

暴力字符串操作即可。贪心的将单词放到同一行并记录单词的字符数,如果总字符数将超过K,那就把单词归入下一行,并将上一行输出即可。最后一定要注意把最后一行统计出来,因为每一次发现超过K才会打印,而最后一行不会出现超过K的情况,所以要在最后补充(详见代码)。

正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long ll;
ll n,k;
string str[110];
int main()
{
	cin>>n>>k;
	for(ll i=1;i<=n;i++)
		cin>>str[i];
	ll now=str[1].size(),last=0;
	for(ll i=2;i<=n;i++)
	{
		ll len=str[i].size();
		if(now+len<=k)//判断加上这个单词之后,长度是否小于k
			now+=len;
		else
		{
			now=len;
			for(ll j=last+1;j<i;j++)
				cout<<str[j]<<" ";
			cout<<endl;
			last=i-1;
		}
	}
	for(ll i=last+1;i<=n;i++)//因为循环内没有处理最后一行的情况,所以需要输出
		cout<<str[i]<<" ";
	cout<<endl;
	
	return 0;
}

B. Race

题目大意

一个人在长度为K的赛道上运动,他的起点为0,终点为K,起始速度为0。在每一秒开始的时候,他可以选择加速1,减速1或者保持原速,并用此速度运动一秒。最终他将在整数秒的时间刚好到达终点。下面给出N个询问,每一个询问有一个限定速度X,要求在通过终点的时候速度不超过X,并输出最短的完成时间。
数据范围:1<=N<=1000,1<=K<=109,1<=X<=105

题目分析:

1.首先无论他在什么时间以v的速度运动对答案的贡献都是一样的,我们可以把这些运动当成v/t图上的一段平行于t轴的长度为1的线。所以我们可以将这些线进行任意组合,最终我们可以使得,无论中间的运动状态如何变化,我们都可以将运动转化为速度首先递增(非严格),然后再单调递减(非严格)的过程。

  • 假设速度序列为:1,2,3,4,5,4,3,2,3,4。
  • 它将和这个序列完全等价:1,2,2,3,3,3,4,4,4,5。或者:1,2,3,4,5,4,4,3,3,2

2.而题目要求最短时间,所以显然我们应该让运动中的最大速度尽可能大。于是我们可以采用贪心的方法:

  • 用尽可能短的时间达到最大速度。
  • 最大速度状态下运动尽可能长的时间。
  • 最后将速度降至X。
    所以速度序列首先是严格单调递增的,最后是严格单调递减的。如果设最大速度为v,那么根据等差数列求和,严格单调递增的时间走的距离长度为: ( 1 + v ) ∗ v 2 \frac{(1+v)*v}{2} 2(1+v)v;严格单调递增的时间走的距离长度为: ( v − 1 + X ) ∗ ( j − x ) 2 \frac{(v-1+X)*(j-x)}{2} 2(v1+X)(jx)

3.但回到题目意思,我们要求要在整数秒刚好到终点,所以这样的做法并不能完全保证刚好到达。假设经过两个单调过程之后,剩下的路程为res。则能够在最大速度状态下运动的时间为: ⌊ r e s j ⌋ \lfloor \frac{res}{j} \rfloor jres。因为R满足0<R<v,那么余数R很显然可以让它划分到前面严格递增的序列中,以速度R运动1秒即可。
4.最后需要注意当限定速度X很大,使得就算一直加速,当到达K时速度依然小于X的特殊情况。这时候只需将X变成满足 ( 1 + X ) ∗ X 2 \frac{(1+X)*X}{2} 2(1+X)X>k的最大X即可。而最大速度v,观察数据范围,显然可以通过暴力枚举的方法得到,最后取最小时间即可。

正解程序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long ll;
const ll maxn=10010;
ll k,n;
int main()
{
	scanf("%lld%lld",&k,&n);
	for(ll i=1;i<=n;i++)
	{
		ll x,ans=1e18;
		scanf("%lld",&x);
		while((1+x)*x/2>k)//出现特殊情况
			x--;
		for(ll j=x;j<=200000;j++)
		{
			ll sum=(1+j)*j/2+(j-1+x)*(j-x)/2;
			if(sum>k)
				break;
			ll res=k-sum,t1=j+j-x;
			ll t2=res/j;
			if(res%j!=0)
				t2++;
			ans=min(ans,t1+t2);
		}
		printf("%lld\n",ans);
	}
	
	return 0;
}

C. Berry Picking

题目大意:

有N棵果树,第i棵树上水果的数量为Bi,我们可以用K个篮子去装水果,每一个篮子只能装一棵果树上的水果。最后要求K个篮子中最小的 K 2 \frac{K}{2} 2K个篮子的水果总数最多,保证K为偶数。输出这个最大值。
数据范围:1<=N<=1000,1<=Bi<=1000,1<=K<=1000。

题目分析:

1.因为有效数据只有最小的 K 2 \frac{K}{2} 2K个篮子的水果,所以就要求这K个篮子中的水果数尽量平均,这样可以让最大的 K 2 \frac{K}{2} 2K个篮子的水果和最小的 K 2 \frac{K}{2} 2K个篮子的水果的数量尽量接近,最后使得答案最大。
2.假设这K个篮子的最大水果数为MAX,那么我们可以枚举这个MAX的值,贪心的去装出为MAX的篮子,并计算最终的答案,取最大值即可。

  • 如果这样装出来的篮子超过了K个,那么答案显然就是 M A X ∗ K 2 \frac{MAX*K}{2} 2MAXK
  • 如果这样装出来的篮子个数没有K个,那么就将剩下的篮子尽可能装得更多,并取最大的K个篮子即可。根据数据范围,这一段可用暴力排序的方式来解决。

3.所以最后时间复杂度为O(BiN)。

正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <queue> 

using namespace std;
typedef long long ll;
const ll maxn=1010;
ll n,k,a[maxn],mod;

bool cmp(ll x,ll y)
{
	return x%mod>y%mod;
}
int main()
{
	scanf("%lld%lld",&n,&k);
	ll Max=-1,ans=-1;
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		Max=max(Max,a[i]);
	}
	for(ll i=1;i<=Max;i++)
	{
		ll num=0,pos=0,sum=0;
		for(ll j=1;j<=n;j++)
			num+=a[j]/i;
		mod=i;
		sort(a+1,a+1+n,cmp);
		if(num>=k)//如果最大值的数量超过K
		{
			ans=max(ans,i*k/2);
			continue;
		}
		if(num>=k/2)//如果最小的部分有为最大值的篮子
			num-=k/2;
		else//如果没有,剩下的篮子中,最大的要先补充到前k/2个中
			pos=k/2-num,num=0;
		sum=num*i;
		ll cnt=0;
		for(ll j=pos+1;j<=n;j++)
		{
			sum+=a[j]%i;
			if(++cnt==k/2-num)
				break;
		}
		if(cnt!=k/2-num)
			break;
		ans=max(ans,sum); 
	}
	printf("%lld\n",ans);
	
	return 0;
}

D. Time is Mooney

题目大意:

有N个城市,M条单向路径,每经过一个城市都会获得一笔资金mi,每经过一条路径将会耗时1天,旅行的花费为C*T2。起始城市和终点城市都为1号城市,并且1号城市的m1=0。要求选择合理的旅行方式,使得最后获得的钱最多。
数据范围:2<=N<=1000,1<=M<=2000,0<=mi<=1000,1<=C<=1000。

题目分析:

1.很显然这是一个DP题目,我们设dp[i] [j]代表时间i在城市j时的最大收益。然后算出C * T2-C * (T-1)2=2 * T-1那么显然,状态转移方程为:dp[i] [j]=max(dp[i - 1] [k]+2 * T - 1),k指和城市i相连的城市。
2.因为我们是i由i-1得到,而不是从i-1转移到i,所以为了遍历方便,构建单向边时我们采取反向连边的方式,这样处理起来更加方便和符合我们的写代码的逻辑。
3.根据估算,T2>m * T时,T>1000,所以我们可以取Tmax=2000,这样时间复杂度为O(TmaxN)。

正解代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <queue> 

using namespace std;
typedef long long ll;
const ll maxn=1010;
struct node
{
	ll v;
	ll next;
}e[maxn*2];
ll n,m,C;
ll p[maxn],t=0;
ll money[maxn],dp[maxn*10][maxn];

void insert(ll u,ll v)
{
	e[t].v=v;
	e[t].next=p[u];
	p[u]=t++;
}
int main()
{
	memset(dp,0x80,sizeof(dp));
	memset(p,-1,sizeof(p));
	scanf("%lld%lld%lld",&n,&m,&C);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&money[i]);
	for(ll i=1;i<=m;i++)
	{
		ll u,v;
		scanf("%lld%lld",&u,&v);
		insert(v,u);//反向建边
	}
	dp[0][1]=0;
	ll ans=0;
	for(ll t=1;t<=10000;t++)
	{
		for(ll u=1;u<=n;u++)
		{
			for(ll i=p[u];i!=-1;i=e[i].next)
			{
				ll v=e[i].v;
				dp[t][u]=max(dp[t][u],dp[t-1][v]+money[u]-C*(2*t-1));
			}
		}
		ans=max(ans,dp[t][1]);
	}
	printf("%lld\n",ans);
	
	return 0;
}

E. Drinking

题目大意:

现有 n 杯水,第 i 杯水中有 wi 单位体积的水。可以选择一个区间 [l,r],并拿一个初始为空的杯子(杯子的容积无限大),他可以重复无限次以下操作:

  • 选定任意一杯水 i ,i∈[l,r];
  • 使 i 和初始杯子里的水的体积变为它们的平均值。

我们希望进行若干操作后最大化杯子里的水的体积,设 g(l,r)为这个最大值。你需要求: ∑ l = 1 n ∑ r = l n g ( l , r ) n 2 \sum_{l=1}^n{\sum_{r=l}^n{\frac{g(l,r)}{n^2}}} l=1nr=lnn2g(l,r)
数据范围:1<=wi<=105,1<=n<=106

题目分析:

1.首先我们考虑在[l,r]的区间内,怎么得到g(l,r)。很明显我们贪心的将杯子和所有的杯子从小到大进行取平均值即可得到最大值,里面还需要进行排序。
2.因为数据范围的与原因,直接暴力的复杂度显然是不能接受的,为O(n3log n)。
3.将答案表达出来,设第 i 次选择平均的数为Xi,则答案为: ∑ i = 1 r − l + 1 X   i   2 i \sum_{i=1}^{r-l+1}{\frac{X~i~}{2^i}} i=1rl+12iX i 。而我们发现Xi的值不过105,在 i 很大的情况下,到后面贡献会越来越低,当为T=50的时候基本就可以忽略不记了。然而此时的时间复杂度仍然有O(n2T)。
4.再进行分析我们发现,一个数的贡献只和比它大的数有关系,根据3的经验,我们在i的左边和右边分别找T个离 i 尽量近且比wi大的数,假设左边找到的数的位置从右往左是l1,l2,…,lT,右边从左往右是r1,r2,…,rT,l0=r0=i. 则 i 的贡献sum i 可以估算为:

  • sumi=wi × ∑ u = 1 T ∑ v = 1 T ( l u − 1 − l u ) × ( r v − l v − 1 ) × 1 2 u + v − 1 \sum_{u=1}^T{\sum_{v=1}^T{(l_{u-1}-l_{u})×(r_{v}-l_{v-1})×\frac{1}{2^{u+v-1}}}} u=1Tv=1T(lu1lu)×(rvlv1)×2u+v11
  • 即sumi=2wi ( ∑ u = 1 T l u − 1 − l u 2 u \sum_{u=1}^T{\frac{l_{u-1}-l_{u}}{2^u}} u=1T2ulu1lu)( ∑ v = 1 T r v − r v − 1 2 v \sum_{v=1}^T{\frac{r_{v}-r_{v-1}}{2^v}} v=1T2vrvrv1)

5.最后我们使用链表的方式去找到这T个数就好了,从小到大计算一个数的贡献,这样前后的数成单调递增,则不会互相影响。
6.综上所述,时间复杂度为O(nT+n log n)。

正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>
using namespace std;
typedef long long ll;
const ll maxn=1000010;
const ll T=50;
ll l[T+10],r[T+10],pre[maxn],bac[maxn];
ll n,times[maxn];
pair<ll,ll> val[maxn];
double ans=0;
int main()
{
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&val[i].first);
		val[i].second=i;
	}
	times[0]=1;
	for(ll i=1;i<=T;i++)
		times[i]=times[i-1]*2;
	sort(val+1,val+n+1);
	for(ll i=1;i<=n+1;i++)
		pre[i]=i-1;
	for(ll i=0;i<=n;i++)
		bac[i]=i+1;
	for(ll i=1;i<=n;i++)
	{
		ll p,q,cntl=0,cntr=0;
		l[0]=r[0]=p=q=val[i].second;
		for(ll j=1;j<=T;j++)
		{
			if(p)
			{
				p=pre[p];
				l[++cntl]=p;
			}
			if(q!=n+1)
			{
				q=bac[q];
				r[++cntr]=q;
			}
		}
		double t1=0,t2=0;
		for(ll j=1;j<=cntl;j++)
			t1+=(l[j-1]-l[j])/(double)times[j];
		for(ll j=1;j<=cntr;j++)
			t2+=(r[j]-r[j-1])/(double)times[j];
		ans+=t1*t2*2*val[i].first;
		pre[bac[val[i].second]]=pre[val[i].second];
		bac[pre[val[i].second]]=bac[val[i].second];
    }
	printf("%.10lf",ans/(n*n));
	
	return 0;
}

F. Mathematics

题目大意:

给一个递推公式:fn=1-n ⋅ \cdot fn-1。并给出初项f0=1- 1 е \frac{1}{е} е1。要求求出数列的第 n 项。
数据范围:0<=n<=104

题目分析:

1.本题看似十分简单,然而如果按照递推公式和初项去做题的话,那么最后误差会越来越大,因为电脑的储存方式是浮点数,没办法保证精度。尤其是根据递推公式,在不断乘上 n ,一点误差到了后面就会被不断放大。
2.根据数学知识,我们发现一个非常神奇的事情,这个数列是收敛的,无穷项的值趋近0。
3.既然正向做由于乘以 n 使得误差越来越大,那么反过来做的时候,是不是随着 n 的减小,通过除以 n 的方式,使得误差越来越小了?所以我们直接设f[2×105]=0,倒着推回去即可。

正解程序:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>

using namespace std;
typedef long long ll;
const ll maxn=20010;
double f[maxn];
ll n;
int main()
{
	f[maxn-10]=0;
	for(ll i=maxn-11;i>=0;i--)
		f[i]=(1-f[i+1])/(i+1);
	scanf("%lld",&n);
	printf("%.4lf\n",f[n]);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值