水法测试
第一题:小 M 培养了 n 个菌落。其中每个菌落有质量和颜色两种属性,颜色只可能为紫色或 红色。小 M 想把所有的菌落合并成一个菌落。 因为合并的过程非常费劲,小 M 每天只能进行一次合并,整个过程需要进行 n-1 天。 一次合并会将两个菌落变成一个菌落。如果原来两个菌落的颜色相同,两个菌落会进行融合, 新的菌落质量为原来两个菌落质量之和,颜色不变;如果原来两个菌落的颜色不同,两个菌 落会进行战斗,新的菌落质量为原来两个菌落质量之差,颜色为质量较大的那个菌落。需要 特殊说明的是,中间过程中如果产生了质量为 0 的菌落,这个菌落也需要参与后续合并而 不能直接舍弃;可以证明将质量为 0 的菌落视为紫色或红色都不影响后续的计算。 每天的合并结束后,小 M 都需要喂养当前的每个菌落。对于一个质量为 w 的菌落, 小 M 需要花费 w^2 单位的能量对它进行一天的喂养。 小 M 希望你帮他求出菌落的最佳合并顺序,使得喂养所消耗的总能量最少。你只需要 输出所需要的最小能量即可。
题面好长,其实很简单。
考虑一开始有n个点,每次将两两合并所形成的二叉树。
设表示i的父亲,若没有父亲那么就是他自己。初始时谁都没有父亲。
如果要合并两个点,那么需要保证将两者合并时,令即可。
通过这样的f数组,我们可以很容易就够造出这棵树的样子。
在菌落合并的时候讨论一下就好了,可以想到最后的f序列,第一个点的取值有1种,第二个点的取值有两种。。。那么就可以hash了。
记忆化搜索搞起来。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int T,n;
long long dp[4037913],coef,a[11];
int fac[11],f[11];
int ty[4037913];
bool type[11];
char c[10];
long long dfs(int x){
if(dp[coef] && ty[coef]==T) return dp[coef];
if(x==n) return 0;
long long mmin=1ll*1e18,tot=0,temp;
int p[11];
bool lt;
p[0]=0;for(int i=1;i<=n;i++) if(f[i]==i) p[++p[0]]=i,tot+=a[i]*a[i];
for(int i=1;i<=p[0];i++)
for(int j=i+1;j<=p[0];j++) {
lt=type[p[i]];temp=a[p[i]];tot-=a[p[i]]*a[p[i]]+a[p[j]]*a[p[j]];
if(type[p[i]]==type[p[j]]) a[p[i]]=a[p[i]]+a[p[j]];
else{
if(a[p[i]]>a[p[j]]) a[p[i]]-=a[p[j]];
else a[p[i]]=a[p[j]]-a[p[i]],type[p[i]]=type[p[j]];
}
coef=coef-1ll*p[j]*fac[p[j]-1]+1ll*p[i]*fac[p[j]-1];f[p[j]]=p[i];
mmin=min(mmin,tot+a[p[i]]*a[p[i]]+dfs(x+1));
a[p[i]]=temp;type[p[i]]=lt;
coef=coef-1ll*p[i]*fac[p[j]-1]+1ll*p[j]*fac[p[j]-1];f[p[j]]=p[j];
tot+=a[p[i]]*a[p[i]]+a[p[j]]*a[p[j]];
}
ty[coef]=T;
return dp[coef]=mmin;
}
int main(){
scanf("%d",&T);
fac[0]=1;for(int i=1;i<=10;i++) fac[i]=fac[i-1]*i;fac[0]=0;
while(T--){
scanf("%d",&n);coef=0;
for(int i=1;i<=n;i++){
f[i]=i;coef+=f[i]*fac[i-1];
scanf("%lld",&a[i]);
scanf("%s",c);
if(c[0]=='7') type[i]=false;
else type[i]=true;
}
printf("%lld\n",dfs(1));
}
}
第二题:平平去海边度假,海边有一片美丽的鹅卵石滩。平平在鹅卵石滩上捡了 $n$ 块美丽的 鹅卵石,并把它们排成一个序列,其中排第 i 位的鹅卵石的美丽度为 a_i。平平想从里面 按照原序列的顺序挑选出一个鹅卵石的子序列,使得在这个子序列里的后一块鹅卵石的美 丽度不比前一块低。平平还想知道,他这么做能得到的最长的子序列长度是多少。 平平想了想,认为这个问题很 naive,于是他决定将上述鹅卵石序列首尾相连,组成 一个鹅卵石环,然后计算在这个环上的满足要求的最长子序列的长度。
就是求n次的最长不降子序列。
只要复杂度不是满的,就可以通过此题。。。
因为保证数据随机,所以最长上升子序列的长度就是期望的。
所以我们滑动窗口,很容易就可以在这个序列后面加一个点,但是在前面删一个点很难。
直接暴力重构即可!总共会重构次,每次重构。所以总的时间复杂度就是的。
还有很多很多水法。。。。(因为数据随机所以真的很好过
第三题:平平有一个项链。这个项链上串了 N 个珠子,其中第 i 个珠子上有 a_i 的能量值(a_i 不一定非负)。 对于一段连续的珠子,平平定义其美丽程度为它们的能量值之和。自然地,一个项链的 美观值定义为它的所有连续子段的美丽程度的最大值。 平平得到了一把神奇剪刀,他可以把这个项链的恰好一个连续子段剪下来,然后翻转这 个子段,再拼接回去得到一个新项链。 平平想利用这把剪刀,得到一个美观值尽可能大的新项链。那么问题来了:请你告诉平 平,他的新项链的美观值最大可以是多少。
当时考试脑子呆瓜了。就差最后一步。
首先这题如果正数的个数<=1的话,直接输出Top 2就可以了。
否则的话,考虑答案一定是最大两段之和。
因为这个翻转肯定会对答案产生贡献,就找最大两段,然后把一段的右端点+1到另一段的右端点翻转就可以得到一个连续的序列。
对于环上最大两段之和,它一定是序列上的两段或者总和减去最小的两段。
那么就做完了,把问题转化为序列上最大两段之和,直接处理一个前缀最大子段和,再从后面倒着做就好了。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int n;
int a[200010];
long long f[200010],g[200010];
int t=0;
long long mmax=-1e18,tot,cmax;
long long solve(){
long long ans=-1e18;
for(int i=1;i<=n;i++) f[i]=max(f[i-1],0ll)+a[i],g[i]=max(g[i-1],f[i]);
for(int i=n;i>=1;i--) f[i]=max(f[i+1],0ll)+a[i],ans=max(ans,f[i]+g[i-1]);
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]),t+=(a[i]>0),tot+=a[i];
if(a[i]>mmax) cmax=mmax,mmax=a[i];
else if(a[i]>cmax) cmax=a[i];
}
if(t<=1) {
printf("%lld",mmax+cmax);
return 0;
}
mmax=solve();
for(int i=1;i<=n;i++) a[i]=-a[i];
mmax=max(mmax,tot+solve());
printf("%lld\n",mmax);
}
当时考试就是想出来环上最大两段之和,就直接O(n^2)滚粗了。