2019.01.29【NOIP提高组】模拟 B 组 比赛总结

过程

这次的题目名字好连贯哦!
兴致勃勃地点开T1,结果发现T1出奇的水。
先剔除燃料不足的飞船,再以人数为关键字从大到小排序一下,从前往后选出人数总和大于等于m的飞船,不就可以切了吗?
第一题难得的水呀!再看T2。
这题讲的是一个扫地非酋想要通过充值成为RMB欧皇的故事。看到题目那么多限制,装备只有2件(破扫把和铁锈铲),而好像并没有什么用的赋值符文只有1件,M,K还那么小!我顿时信心倍增,嘿嘿!这题切定了!
转到T3,又最短路又奇数长度什么的,乱七八糟。但看到暴力的部分分有50,我就下定决心打暴力了。
回到第二题,我很快就发现了几个规律,并想出了一个 O ( m × m a x ) O(m\times max) O(m×max)的DP(其中max表示把所有加法符文都贴给装备1的时候,装备1的最大值)但我很快就发现这样的DP似乎无法转移,于是多加了一维,变成了 O ( m ⋅ k ⋅ m a x ) O(m\cdot k\cdot max) O(mkmax)。我却自信地认为自己能切。
打完代码后,还剩十几分钟。届时OJ早已卡得濒临崩溃。于是,在查完错误后(还剩1.5分钟),我就点开论坛想写总结了。然后被XC发现了,我……
比赛还是要坚持到最后一刻的!
估分:100+100+50=250
得分:100+ 40 + 0 =140
另外,我们终于发现了卡OJ的幕后黑手——小学生!
有图有真相
这完全是同一个人提交的!据了解,他们是一直按OK键,然后就……除了最后一个代码,其他全部都变成了空代码了。


题解

T1

直接贪心。把符合要求的飞船排序,选最大的就可以了。

T2

先来看一下什么叫做自然对数。以下是引自度娘的解释:

自然对数以常数e为底数的对数。记作lnN(N>0)。在物理学,生物学等自然科学中有重要的意义。一般表示方法为lnx。数学中也常见以logx表示自然对数。

什么东西啊!!!!很遗憾,我也不知道这是神马东东
简单来说,x的自然对数是 log ⁡ e x \log_ex logex,其中e是自然常数。以下是引自度娘的解释(又来了):

自然常数,是数学中一个常数,是一个无限不循环小数,且为超越数,其值约为2.71828同时,e也是一个成熟的细胞的平均分裂周期。

嗯,看懂了一些东西,准确来说,自然常数应是 e = lim ⁡ x → ∞ ( 1 + 1 x ) x e=\lim_{x\to \infty}\left(1+\frac{1}{x}\right)^x e=xlim(1+x1)x头晕目眩……不过这些神一般的东西并不影响我们的做题。c++中,x的自然对数表示为log(x),在pascal中则表示为ln(x)
P.S:由于我以前做过有关自然对数的题目,因此我会怎么在c++中求自然对数
天哪!这题怎么那么毒瘤!给你乘上100个2000,让你不得不用高精度就好了,还**地让你求那么大的数字的自然对数!我顿时感到生无可恋。
不过,以我多年刷题的经验,出题者不可能毒瘤到如此地步。题目中不是有这么一句话么:

由于这个数可能很大,请输出它的自然对数

因此,自然对数应该不会是出题者用来卡我们的!
数字最大只可能到 1 0 20000 10^{20000} 1020000,也就是 2 60000 → 2 80000 2^{60000}\to2^{80000} 260000280000,这个数显然是可以用整型变量储存的。况且 e > 2 e>2 e>2,那么指数会更小。于是这题用自然对数即可取代高精度。
但是对于乘法又如何呢?我猜想: log ⁡ e a + log ⁡ e b = log ⁡ e a b \log_ea+\log_eb=\log_eab logea+logeb=logeab。这个东西我比赛时只是感性地理解,试了几个数认为是正确的。比赛后证明了出来:

不妨设 m = log ⁡ e a , n = log ⁡ e b m=\log_ea,n=\log_eb m=logea,n=logeb,即 e m = a , e n = b e^m=a,e^n=b em=a,en=b
那么 a b = e m × e n = e m + n ab=e^m\times e^n=e^{m+n} ab=em×en=em+n
也就是 log ⁡ e a + log ⁡ e b = log ⁡ e a b \log_ea+\log_eb=\log_eab logea+logeb=logeab

那么求 x × y x\times y x×y自然对数就变成了求 log ⁡ e x + log ⁡ e y \log_ex+\log_ey logex+logey的值了。

接着,很容易可以发现,先赋值、再加、最后乘一定是最优的。因此我们可以分开处理3种操作。
先看赋值操作。很显然这个操作要最先做,而且最优的方案一定是把2个数中较小的那个赋值嘛。因此我就把赋值操作转换成加法操作,即把它修改成加上赋值数减去较小数的加法操作(这个方法是有问题的,下面将会讲到)
再看乘法操作,这明显放在最后做是最优的。而且我们把乘法的符文分给哪一个装备都是一样的。设第一个装备的威力值为x,第二个的威力值为y,可以乘上a,那么 ( a x ) y = a ( x y ) (ax)y=a(xy) (ax)y=a(xy),因此加法操作、乘法操作后的,乘上某些数的最大值即为答案。那么要乘上哪些数呢?明显是越大越好。因此我们可以先给乘法符文从大到小排序,然后记录一个类似于前缀和的数组 g n = ∏ i = 1 n a i g_n=\prod_{i=1}^{n}a_i gn=i=1nai来维护累乘最大值。
接下来只剩下最难搞加法操作了。
考虑n=1的情况:这个是很简单的,因为加上的数是越大越好的,因此排序后算算前缀和,再统计答案就可以了(不要问我为什么打了个DP)。
再看n=2的情况:
首先,对于 a + b = c a+b=c a+b=c,要使 a b ab ab尽量大, ∣ a − b ∣ |a-b| ab要尽量小。所以我们要使2个装备的值尽可能大。这比较难搞,因此我认为要用DP。
我一开始设 f i , j f_{i,j} fi,j表示把第 i 个符文给装备 j 的时候的最大乘积,结果发现无法通过 a b ab ab得出 ( a + x ) b (a+x)b (a+x)b,转移不了。然后我就只好想别的状态了。
我发现这样设是可行的: f i , j f_{i,j} fi,j表示到了符文 i ,装备1的威力值为 j 时,装备2的威力值最大为多少。那么状态转移方程显然:
f i , j = { f i − 1 , j − a i , 用符文i升级装备1 f i − 1 , j + a i , 用符文i升级装备2 f i − 1 , j 不使用符文i f_{i,j}= \begin{cases} f_{i-1,j-a_i},&\text{用符文i升级装备1}\\ f_{i-1,j}+a_i,&\text{用符文i升级装备2}\\ f_{i-1,j}&\text{不使用符文i} \end{cases} fi,j=fi1,jai,fi1,j+ai,fi1,j用符文i升级装备1用符文i升级装备2不使用符文i
接着可以发现,第三条转移其实没有什么用。我们只需从大到小给数组排序,到符文的常数小于等于0时直接退出就可以了。
答案明显等于 max ⁡ 0 ≤ i ≤ m a x x i × f m , i × g m \max_{0\leq i\leq maxx}i\times f_{m,i}\times g_m 0imaxxmaxi×fm,i×gm其中 m a x x maxx maxx表示把所有的加法符文都用来升级装备1,装备1的最大值
接着我就惊奇地发现输出的答案竟然是5.075(对应160)!我摸拟了半天样例,结果也是这个呀!
这时我才发现有一个参数k,凉凉!莫非我的代码要删掉重打吗?
好像并不用!只需做一些小小的修改即可——

  1. f i , j f_{i,j} fi,j表示用了 i 个符文,装备1的威力值为 j 时,装备2的威力值最大为多少。
  2. 状态转移方程为 f i , j = { f i − 1 , j − a t , 用符文t升级装备1 f i − 1 , j + a t , 用符文t升级装备2 f i − 1 , j 不使用符文i f_{i,j}=\begin{cases}f_{i-1,j-a_t},&\text{用符文t升级装备1}\\f_{i-1,j}+a_t,&\text{用符文t升级装备2}\\f_{i-1,j}&\text{不使用符文i}\end{cases} fi,j=fi1,jat,fi1,j+at,fi1,j用符文t升级装备1用符文t升级装备2不使用符文i其中 t 是一个枚举符文的变量
  3. 答案就变成了 max ⁡ 0 ≤ i ≤ k , 0 ≤ j ≤ m a x x j × f i , j × g k − i \max_{0\leq i\leq k,0\leq j\leq maxx}j\times f_{i,j}\times g_{k-i} 0ik,0jmaxxmaxj×fi,j×gki

然后我就正确地输出了答案,嗯,这题一定AC了。
结果我只有40分,点开一看,TLE。
在这里插入图片描述
经过CJY DL的指导,我发现加法操作中有用的就是前k个,因此我们排一下序,选前k个DP就可以了,这时的 i 和 t 是同步的,就可以删去 t 了。
于是时间复杂度就变成了 O ( k × m a x x ) O(k\times maxx) O(k×maxx),我交上OJ,AC!
但是这种方法是有问题的。还记得我是怎么处理赋值操作的吗?

我就把赋值操作转换成加法操作,即把它修改成加上赋值数减去较小数的加法操作

万一把它变成加法操作后,那个数字被另一个装备加上了怎么办?
因此我们要对于用和不用赋值操作的情况分别DP一次,然后就可以了。
结果常数似乎有些大啊!
在这里插入图片描述

T3

我的暴力怎么爆〇了?不过不管这些了,直接AC就好了。
对于每一个点,我们都做一次最短路,保留那些对答案有贡献的单向边后,我们就得到了由n-1条边构成的DAG。然后暴力DFS跑一遍,算出每一个数被从起点出发的最短路经过了几次就可以了。


CODE

T1

#include<cstdio>
#include<algorithm>
using namespace std;
#define N 100005
int a[N],s;
int main()
{
    
    
	int n,m,k,i,x,y;
	scanf("%d%d%d",&n,&m,&k);
	k<<=1;
	for(i=1;i<=n;i++)
	{
    
    
		scanf("%d%d",&x,&y);
		if(y>=k) a[++s]=x;
	}
	sort(a+1,a+s+1);
	if(!m)
	{
    
    
		puts("0");
		return 0;
	}
	for(i=s;i>0;i--)
	{
    
    
		m-=a[i];
		if(m<1)
		{
    
    
			printf("%d\n",s-i+1);
			return 0;
		}
	}
	puts("-1");
	return 0;
}

T2

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LB long double
#define M 202001
#define N 101
struct equipment
{
    
    
	int type,num;
	bool operator <(const equipment y)const
	{
    
    return type<y.type||type==y.type&&num>y.num;}
}a[N];
int f[N][M],dp[N],val[2];
LB ans,temp,g[N];
int main()
{
    
    
	int n,m,k,i,j,max,min,start,t;
	scanf("%d%d%d",&n,&m,&k);
	for(i=0;i<n;i++) scanf("%d",&val[i]);
	max=val[0];
	for(i=1;i<=m;i++)
	{
    
    
		scanf("%d%d",&a[i].type,&a[i].num);
		if(a[i].type==2) max+=a[i].num;
	}
	sort(a+1,a+m+1);
	for(i=1;i<=m;i++)
		if(a[i].type==3) break;
	for(j=1;i<=m;i++,j++)
	{
    
    
		if(a[i].num>1) g[j]=g[j-1]+log((LB)a[i].num);
		else g[j]=g[j-1];
	}
	for(i=1;i<=m;i++)
		if(a[i].type!=2) break;
	m=i-1;
	if(n==1)
	{
    
    
		if(a[1].type==1)
		{
    
    
			a[1].type=2;
			a[1].num-=val[0];
		}
		memset(dp,128,sizeof(dp)),dp[0]=val[0];
		for(i=1;i<=m;i++) if(a[i].num>0)
			for(j=1;j<=k;j++)
				if(dp[j-1]+a[i].num>dp[j])
					dp[j]=dp[j-1]+a[i].num;
		for(i=0;i<=k;i++)
		{
    
    
			temp=dp[i];
			temp=log(temp)+g[k-i];
			if(temp>ans) ans=temp;
		}
	}
	else
	{
    
    
		start=1;
		if(a[1].type==1)
		{
    
    
			start=2;
			if(val[0]<val[1]) min=0;
			else min=1;
			t=val[min],val[min]=a[1].num;
		}
		memset(f,128,sizeof(f));
		f[start-1][val[0]]=val[1];
		for(i=start;i<=k&&a[i].num>0;i++)
		{
    
    
			for(j=val[0];j<=max;j++)
			{
    
    
				if(f[i-1][j]+a[i].num>f[i][j]) f[i][j]=f[i-1][j]+a[i].num;
				if(j>=a[i].num&&f[i-1][j-a[i].num]>f[i][j]) f[i][j]=f[i-1][j-a[i].num];
			}
		}
		for(i=0;i<=k;i++)
		{
    
    
			for(j=val[0];j<=max;j++)
			{
    
    
				temp=j,temp*=f[i][j];
				temp=log(temp)+g[k-i];
				if(temp>ans) ans=temp;
			}
		}
		if(a[1].type==1)
		{
    
    
			val[min]=t,start=1;
			memset(f,128,sizeof(f));
			f[0][val[0]]=val[1];
			for(i=start;i<=k&&a[i].num>0;i++)
			{
    
    
				for(j=val[0];j<=max;j++)
				{
    
    
					if(f[i-1][j]+a[i].num>f[i][j]) f[i][j]=f[i-1][j]+a[i].num;
					if(j>=a[i].num&&f[i-1][j-a[i].num]>f[i][j]) f[i][j]=f[i-1][j-a[i].num];
				}
			}
			for(i=0;i<=k;i++)
			{
    
    
				for(j=val[0];j<=max;j++)
				{
    
    
					temp=j,temp*=f[i][j];
					temp=log(temp)+g[k-i];
					if(temp>ans) ans=temp;
				}
			}
		}
	}
	printf("%.3LF\n",ans);
	return 0;
}

T3

#include<cstdio>
using namespace std;
#define N 1005
#define M 6005
struct edge
{
    
    
	int end,lenth,next;
}a[M];
int n,s,first[N],dis[N],ans[N],w[N][N],data[2110000];
bool exist[N];
inline void inc(int x,int y,int z)
{
    
    
	a[++s]=(edge){
    
    y,z,first[x]};first[x]=s;
	a[++s]=(edge){
    
    x,z,first[y]};first[y]=s;
}
inline void spfa(int st)
{
    
    
	int i,head=0,tail=1,u,v;
	for(i=1;i<=n;i++) dis[i]=999999998;
	dis[st]=0,data[1]=st;
	while(head<tail)
	{
    
    
		head++;
		u=data[head];
		exist[u]=0;
		for(i=first[u];i;i=a[i].next)
		{
    
    
			v=a[i].end;
			if(dis[v]>dis[u]+a[i].lenth)
			{
    
    
				dis[v]=dis[u]+a[i].lenth;
				if(!exist[v])
				{
    
    
					exist[v]=1;
					data[++tail]=v;
				}
			}
		}
	}
}
int dfs(int k,int sum)
{
    
    
	int res=sum&1;
	for(int i=first[k];i;i=a[i].next)
		if(dis[a[i].end]==dis[k]+a[i].lenth)
			res+=dfs(a[i].end,sum+a[i].lenth);
	ans[k]+=res;return res;
}
int main()
{
    
    
	int i,j,k,x,y,z,m;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
    
    
		scanf("%d%d%d",&x,&y,&z);
		inc(x,y,z);
		if(w[x][y]<z) w[x][y]=w[y][z]=z;
	}
	for(i=1;i<=n;i++)
	{
    
    
		spfa(i),s=0;
		dfs(i,0);
	}
	for(i=1;i<=n;i++) printf("%d\n",ans[i]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/huangzihaoal/article/details/86695641
今日推荐