返回顶部

寒假集训测试题解

T2

我感觉和小胖守皇宫类似,但我当时及没看数据范围也没读清题,直接树形背包DP,大样例炸了
改了半天,硬是忘记考虑不买优惠券的情况(苦笑)

点击查看代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,b;
vector <int> g[5005];
int d[5005],c[5005],f[5005][5005][2];
int dfs(int u)
{
	int p=1;
	f[u][0][0]=0;
	f[u][1][1]=c[u]-d[u];
	f[u][1][0]=c[u];
	for(auto v:g[u])
	{
		int siz=dfs(v);
		for(int i=min(p,n);i>=0;i--)
		{
			for(int j=1;j<=siz&&j+i<=n;j++)
			{
				f[u][i+j][0]=min(f[u][i+j][0],f[u][i][0]+f[v][j][0]);
				f[u][i+j][1]=min(f[u][i+j][1],f[u][i][1]+min(f[v][j][0],f[v][j][1]));
			}
		}
		p+=siz;
	}
	return p;
	
}
signed main()
{
//	freopen("B.in","r",stdin);
	scanf("%d%d",&n,&b);
	scanf("%d%d",&c[1],&d[1]);
//	int mii=max(mii,c[1]-d[1]);
	int x;
	for(int i=2;i<=n;i++)
	{
		scanf("%d%d%d",&c[i],&d[i],&x);
		g[x].push_back(i);
//		int mii=max(mii,c[i]-d[i]);
	}
	memset(f,0x3f,sizeof(f));
	dfs(1);
	int ans=0;
	for(int i=n;i>=0;--i){
		if(f[1][i][0]<=b || f[1][i][1]<=b){	
			ans=i;
			break;
		}
	}
	printf("%d\n",ans);
	return 0;
}
/*
6 16
10 9
10 5 1
12 2 1
20 18 3
10 2 3
2 1 5

5 10
3 1
3 1 1
3 1 2
3 1 3
3 1 4
*/

T3
序列块

  • 心得:一开始想用区间DP,但发现数据范围2为数组开不下而且时间复杂度过高,放弃了
    于是,我想到了线性DP,可不幸的是,我动态转移方程推错了(菜),做后用区间DP做超时了
区间DP代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,t,a[200005],f[20005][20005];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		for(int i=1;i<=n;i++)
		{
			a[i]=0;
			for(int j=1;j<=n;j++)f[i][j]=0;
		}
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);	
		}
		if(n==1)
		{
			printf("%d\n",1);
			continue;
		}
		int sum=0;bool flag=0;
		for(int i=1;i<=n;i++)
		{
			i+=a[i];
			if(i==n)
			{
				flag=1;
				break;
			}else if(i>n)
			{
				break;
			}
		}
		if(flag)
		{
			printf("%d\n",0);
			continue;
		}else
		{
			int ans=0;
			for(int i=1;i<=n;i++)f[i][i]=1;
			for(int len=2;len<=n;len++)
			{
				for(int i=1;i+len-1<=n;i++)
				{
					int j=i+len-1;
					if(i+a[i]==j)f[i][j]=0;
					else 
					{
						f[i][j]=j-i+1;
						f[i][j]=min({f[i][j],f[i][j-1]+1,f[i+1][j]+1});
						for(int k=i;k<j;k++)
						{
							f[i][j]=min({f[i][j],f[i][k]+f[k+1][j]});	
						}	
//						cout<<i<<" "<<j<<" "<<f[i][j]<<endl;					
					}

				}
			}
			//cout<<f[1][n]<<endl;
			printf("%d\n",f[1][n]);
		}
	}
	return 0;
}

线性DP思路:就是记录从i->n的最少操作次数,非常简单

\[f[i]=f[i+1]+1删当前元素 \]

\[f[i]=f[i+a[i]+1](i+a[i]+1<=n)继承上一状态 \]

正解
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,t,a[200005],f[20005];
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);	
//			f[i]=i;
		}
		f[n]=1;f[n+1]=0;
		for(int i=n;i>=1;i--)
		{
//			f[i]=i;
//			for(int j=i;j>=1;j--)
//			{
			if(i+a[i]<=n)
			{
				f[i]=min(f[i+1]+1,f[i+a[i]+1]);
			}
			else f[i]=f[i+1]+1;
//			}
		}
		cout<<f[1]<<endl;
	}
	return 0;
}

T4
送礼物
这道题需用到01分数规划
对于当时没学的我相当炸裂的
其实就是二分加单调队列
谁能想到,数据是真的强!!!我当时用线段树,想的是T不了几个点,没想到,几乎全T了
还Wa了一个(这是我SB)

还有我max括号写在了错位置,MD半天没调出来

首先要考虑长度为L的情况,可能最大值与最小值的距离小于l所以我们要额外考虑一下这种情况
剩下的就是l到r之间的了

\[\frac{a_(i,j)max - a(i,j)min}{j-i+k} \]

转换一下

\[mid*a_(i,j)max\pm mid*i mid*a(i,j)min\pm mid*j >=k*ans \]

点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=50001;
double ans,ANS;
double b[N];int a[N],n,k,l,r;
deque <int> q1;
void update1(int i)
{
	while(!q1.empty()&&i>=q1.front()+(r-l))q1.pop_front();
	while(!q1.empty()&&b[i]<=b[q1.back()])q1.pop_back();
	q1.push_back(i);
}
void update2(int i)
{
	while(!q1.empty()&&q1.front()-i>=(r-l))q1.pop_front();
	while(!q1.empty()&&b[i]<=b[q1.back()])q1.pop_back();
	q1.push_back(i);
}
bool check(double mid)
{
	for(int i=1;i<=n;i++)
	{
		b[i]=(double)a[i]-mid*i;
	}
	ANS=-100000000.0;
	q1.clear();
	//q1.push_back(0);
	for(int i=1;i<=n-l;i++)
	{
		update1(i);
		ANS=max(ANS,b[i+l]-b[q1.front()]);
	}
	//q1.push_back(0);
	for(int i=1;i<=n;i++)
	{
		b[i]=(double)a[i]+mid*i;
	}
	q1.clear();
//	ANS=-100000000.0;
	for(int i=n;i>l;i--)
	{
		update2(i);
		ANS=max(ANS,b[i-l]-b[q1.front()]);
	}
	return (ANS>=k*mid);
}
void fen()
{
	double L=0,R=1000.0;
	while(R-L>1e-9)
	{
		double mid=(R+L)/2;
		if(check(mid))
		{
			ans=max(ans,mid);
			L=mid;
		}else R=mid-(1e-9);
	}
	printf("%.4lf\n",ans);
}
deque <int> A,B;
//A.push_back(0);
//B.push_back(0);
void push(int i)
{
	while(!A.empty()&&a[A.back()]>=a[i])
	{
		A.pop_back();
	}
	A.push_back(i);
	while(!B.empty()&&a[B.back()]<=a[i])
	{
		B.pop_back();
	}
	B.push_back(i);	
}
void update(int i)
{
	while(!A.empty()&&i>=A.front()+l)
	{
		A.pop_front();
	}
	while(!B.empty()&&i>=B.front()+l)
	{
		B.pop_front();
	}	
}
main()
{
//	freopen("D.in","r",stdin);
	int tt;
	scanf("%lld",&tt);
	tt++;
	while(tt--)
	{
		if(tt==0) break;
		scanf("%lld%lld%lld%lld",&n,&k,&l,&r);
//		memset(a,0,sizeof(a));
//		memset(b,0,sizeof(b));
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);	
		}
		ans=0;ANS=0;
		A.clear();B.clear();
		//A.push_back(0);B.push_back(0);	
		for(int i=1;i<l;i++) push(i);
		ans=-1e9;
		for(int i=l;i<=n;i++)
		{
			update(i);push(i);
			ans=max(ans,(1.0*(a[B.front()]-a[A.front()]))/(1.0*(l+k-1)));
			//cout<<(1.0*(a[B.front()]-a[A.front()])))/(1.0*(l+k-1)<<' ';
		}
//		cout<<endl;
		//cout<<ans<<endl;
		fen();
	}
	return 0;
}

总结:关于什么情况下使用单调队列,首先看求的是什么?求的是区间中最大值最小值,我们可已考虑用单调队列
因为他的复杂度为\(O(n)\)而线段树是单词查询logn所以总共\(O(n^2logn)\)而这题测试点几乎全是50000可想而知

posted @ 2024-02-23 22:07  wlesq  阅读(10)  评论(0编辑  收藏  举报