其他分享
首页 > 其他分享> > 首师大附中集训第五天水法测试

首师大附中集训第五天水法测试

作者:互联网

水法测试

      第一题:小 M 培养了 n 个菌落。其中每个菌落有质量和颜色两种属性,颜色只可能为紫色或 红色。小 M 想把所有的菌落合并成一个菌落。 因为合并的过程非常费劲,小 M 每天只能进行一次合并,整个过程需要进行 n-1 天。 一次合并会将两个菌落变成一个菌落。如果原来两个菌落的颜色相同,两个菌落会进行融合, 新的菌落质量为原来两个菌落质量之和,颜色不变;如果原来两个菌落的颜色不同,两个菌 落会进行战斗,新的菌落质量为原来两个菌落质量之差,颜色为质量较大的那个菌落。需要 特殊说明的是,中间过程中如果产生了质量为 0 的菌落,这个菌落也需要参与后续合并而 不能直接舍弃;可以证明将质量为 0 的菌落视为紫色或红色都不影响后续的计算。 每天的合并结束后,小 M 都需要喂养当前的每个菌落。对于一个质量为 w 的菌落, 小 M 需要花费 w^2 单位的能量对它进行一天的喂养。 小 M 希望你帮他求出菌落的最佳合并顺序,使得喂养所消耗的总能量最少。你只需要 输出所需要的最小能量即可。

      题面好长,其实很简单。

      考虑一开始有n个点,每次将两两合并所形成的二叉树。

      设f[i]=i表示i的父亲,若没有父亲那么就是他自己。初始时谁都没有父亲。

      如果要合并两个点x,y,那么需要保证f[x]=x,f[y]=y;将两者合并时,令f[x]=f[y]=min(x,y)即可。

      通过这样的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次的最长不降子序列。

       只要复杂度不是满的,就可以通过此题。。。

       因为保证数据随机,所以最长上升子序列的长度就是期望\ln n的。

       所以我们滑动窗口,很容易就可以在这个序列后面加一个点,但是在前面删一个点很难。

       直接暴力重构即可!总共会重构\ln n次,每次重构n \log_2n。所以总的时间复杂度就是O(n\log_2 n\ln 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)滚粗了。

标签:师大附中,include,coef,int,水法,long,序列,第五天,菌落
来源: https://blog.csdn.net/Deep_Kevin/article/details/97677688