2025年寒假算法天梯赛 02

A. 少废话,你___多少?

  • 因为次大值最大值“必有一战”得到的\(2z_{m-1}>z_m\)则无解的判据并不是无关紧要的特例,而是后续解题的一个很重要的先决条件
  • \((l[i],i)\)之间的数和\(i\)一起处理不管怎么样都很讨厌,类似“费用提前计算”的尝试都无果而终,于是可以尝试“费用延后计算”,当前只考虑\(i\)自己和\([1,l[i]]\)区间数的方案,而且一旦下一个最大值出现,\((l[i],i)\)就好处理了
  • 如果忽略掉数与数之间的空位,转移起来就很麻烦,于是考虑填数的方法,当前的方案要给未来留下余地。这样,\(i\)就只能填在当前的第1个空位中,转移方程水到渠成
#include <bits/stdc++.h>
using namespace std;
int a[1000005],l[1000005];
long long f[1000005],g[1000005];
const int mod=998244353;
int power(int n,int p)
{
	if(p==0)
	{
		return 1;
	}
	long long tmp=power(n,p/2);
	if(p%2==1)
	{
		return tmp*tmp%mod*n%mod;
	}
	return tmp*tmp%mod;
}
long long jc[1000005],jcinv[1000005];
void pre()
{
	jc[0]=1;
	for(int i=1;i<=1000000;i++)
	{
		jc[i]=jc[i-1]*i%mod;
	}
	jcinv[1000000]=power(jc[1000000],998244351);
	for(int i=1000000-1;i>=0;i--)
	{
		jcinv[i]=jcinv[i+1]*(i+1)%mod;
	}
}
long long A(int n,int m)
{
	return jc[n]*jcinv[n-m]%mod;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	pre();
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	if(2*a[n-1]>a[n])
	{
		cout<<0<<endl;
		return 0;
	}
	int p=0;
	for(int i=1;i<=n;i++)
	{
		while(p+1<i&&2*a[p+1]<=a[i])
		{
			p++;
		}
		l[i]=p;
	}
	for(int i=1;i<=n;i++)
	{
		f[i]=g[l[i]]*jcinv[n-l[i]-1]%mod;
		f[i]=(f[i]+A(n-1,l[i]))%mod;
		g[i]=(g[i-1]+f[i]*jc[n-l[i]-2]%mod)%mod;
	}
	cout<<f[n]<<endl;
	return 0;
}

B. shoot the tank!!

  • 典型的完全背包模型,有趣的是也可以用图论方法解决,只要把血量状态定义为节点就好了。因为dijkstra算法的时间复杂度只和n有关,所以这样做显然也是正确的
#include <bits/stdc++.h>
using namespace std;
int a[1005],b[1005],f[10005];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int h,n;
	cin>>h>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i]>>b[i];
	}
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=h;j++)
		{
			f[j]=min(f[j],f[max(j-a[i],0)]+b[i]);
		}	
	}
	cout<<f[h]<<endl;
	return 0;
}

D. 超进化:冒泡排序

  • 【观察性质】
  • 冒泡排序的性质:
  • 经过 i 次扫描后,数列的末尾 i 项必然是最大的 i 项
  • 每⼀轮,每个位置的值最多往前移动⼀位
  • 因⽽,冒泡排序k轮以后,第1个位置必定是前k+1个位置中最⼩的,第2个位置必定是前k+2个位置中,除去第1个位置现在的数字外,最⼩的,后续同理。可以用堆维护
  • 【分析过程】
  • 不断讨论细节会把自己弄晕的!拒绝晕倒,尝试使用数学符号
  • 一轮排序后的序列状态是:
  • \(min(a_1,a_2),min(max(a_1,a_2),a_3),min(max(a_1,a_2,a_3),a4),...,max(a_1,a_2,...,a_n)\)
  • 两轮排序后的序列状态是:
  • \(min(a_1,a_2,a_3),min(max(min(a_1,a_2),min(max(a_1,a_2),a_3)),min(max(a_1,a_2,a_3),a4))\)
  • 分析\(min(max(min(a_1,a_2),min(max(a_1,a_2),a_3)),min(max(a_1,a_2,a_3),a4))\)
  • =\(min(rank_2(a1,a2,a3),min(max(a_1,a_2,a_3),a4))\) 【不妨设\(a_1<a_2<a_3\)
  • =\(min(a_2,min(a_3,a_4))\)=\(min(a_2,a_3,a_4)\) 也可以分析出结果
#include <bits/stdc++.h>
using namespace std;
int a[200005],b[200005];
priority_queue<int,vector<int>,greater<int> >q;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	int T;
	cin>>T;
	while(T--)
	{
		long long k;
		cin>>k;
		for(int i=1;i<=k/(n-1)+1&&i<=n;i++)
		{
			q.push(a[i]);
		}
		int i=1;
		while(i<=n)
		{
			b[i]=q.top();
			q.pop();
			if(i+k/(n-1)+1<=n)
			{
				q.push(a[i+k/(n-1)+1]);
			}
			i++;
		}
		for(int j=1;j<=k%(n-1);j++)
		{
			if(b[j]>b[j+1])
			{
				swap(b[j],b[j+1]);
			}
		}
		for(int i=1;i<=n;i++)
		{
			cout<<b[i]<<" ";
		}
		cout<<endl;
	}
	return 0;
}
posted @ 2025-02-06 06:20  D06  阅读(22)  评论(0)    收藏  举报
//雪花飘落效果