过程
这次的题目名字好连贯哦!
兴致勃勃地点开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(m⋅k⋅max)。我却自信地认为自己能切。
打完代码后,还剩十几分钟。届时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=x→∞lim(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} 260000→280000,这个数显然是可以用整型变量储存的。况且 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| ∣a−b∣要尽量小。所以我们要使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=⎩⎪⎨⎪⎧fi−1,j−ai,fi−1,j+ai,fi−1,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 0≤i≤maxxmaxi×fm,i×gm其中 m a x x maxx maxx表示把所有的加法符文都用来升级装备1,装备1的最大值
接着我就惊奇地发现输出的答案竟然是5.075(对应160)!我摸拟了半天样例,结果也是这个呀!
这时我才发现有一个参数k,凉凉!莫非我的代码要删掉重打吗?
好像并不用!只需做一些小小的修改即可——
- f i , j f_{i,j} fi,j表示用了 i 个符文,装备1的威力值为 j 时,装备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=⎩⎪⎨⎪⎧fi−1,j−at,fi−1,j+at,fi−1,j用符文t升级装备1用符文t升级装备2不使用符文i其中 t 是一个枚举符文的变量
- 答案就变成了 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} 0≤i≤k,0≤j≤maxxmaxj×fi,j×gk−i
然后我就正确地输出了答案,嗯,这题一定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;
}