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

水法测试

      第一题:小 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)滚粗了。

上一篇:PTA 甲级 1009 Product of Polynomials (25 分) 多项式相乘


下一篇:在JS中获取文件点之后的后缀字符