F. Mattress Run 题解

F. Mattress Run

挺好的一道题,对于DP的本质的理解有很大的帮助。
首先要想到的就是将这个拆成两个题,一个dp光求获得足够的夜晚的最小代价,一个dp光求获得足够的停留的最小代价。
显然由于这个题需要存储的信息很大,我们设状态时就要思考如何才能在规定的时间内完成而不超时。
请求有5000个,若设与请求有关的状态,例如:我们设f[i][j],表示当前在请求i,获得的资格j的最小代价,确实,因为我们知道了请求i的所有信息,包括结束时间,所在的旅馆号的信息,但在状态转移时,难免要枚举上一次的请求所在的位置,50005000,别忘了还有j的枚举再50,这样就爆了,别报侥幸心理,这个题输出方案加上你dp要做两边,常数巨大。那么考虑重新设计状态,考虑到转移时,实际上若上一次的天数不为a[i].l,我们根本不用在意他在哪个旅馆,当上一次的天数恰好为a[i].l时,我们才考虑他的旅馆。那么我们可以将天数和旅馆都设在状态里,设f[i][j][k]表示在第i天在旅馆j获得的资格为k的最小代价。先枚举请求,然后考虑每个请求会使得哪些状态发生改变,状态转移时,为了优化,我们令设dp[i][j]表示在第i天获得资格j的最小代价,方便我们省去枚举的旅馆数,之后我们另外讨论天数为a[i].l的情况即可。复杂度为500036550,轻松跑过,输出方案的话,我们大可用结构体来记录当前状态由哪一个转态得来。之后逆推一下即可。

#include<bits/stdc++.h>
using namespace std;
int n,s[2],h,m,c[5005],num;
int f[370][52][52][2],dp[370][52][2];
struct ss{int x,y,z;}Z[370][52][52][2],S[370][52][2];
//设f[i][j][k]表示第i天在j号旅馆获得的资格为k的最小代价,
//dp[i][j]表示第i天获得的资格为j的最小代价。 
struct wy{int l,r,val,id,qid;}a[5005];
inline bool cmp(wy a,wy b)
{
	return a.l<b.l;
}
inline void solve(int op)
{
	f[0][0][0][op]=0;dp[0][0][op]=0;
	for(int i=1;i<=m;++i)//枚举所有的请求。 
	{//转移来的天数分为两类,一类小于l,一类等于l 
		for(int j=0;j<a[i].l;++j)//枚举所有可以改变的状态的天数
			for(int k=0;k<=s[op];++k)
			{
				int d=min((op==0?k+a[i].r-a[i].l:k+1),s[op]);
				if(dp[j][k][op]+a[i].val<f[a[i].r][a[i].id][d][op])
				{
					f[a[i].r][a[i].id][d][op]=dp[j][k][op]+a[i].val;
					Z[a[i].r][a[i].id][d][op]=S[j][k][op];
				}
				if(f[a[i].r][a[i].id][d][op]<dp[a[i].r][d][op])
				{
					dp[a[i].r][d][op]=f[a[i].r][a[i].id][d][op];
					S[a[i].r][d][op]={a[i].r,a[i].id,d};
				}
			} 
		for(int j=0;j<=h;++j)	//等于l的,枚举从那个旅馆过来	
		{
			if(j==a[i].id) continue;
			for(int k=0;k<=s[op];++k)	//枚举上一次的状态。
			{
				int d=min((op==0?k+a[i].r-a[i].l:k+1),s[op]);
				if(f[a[i].l][j][k][op]+a[i].val<f[a[i].r][a[i].id][d][op])
				{
					f[a[i].r][a[i].id][d][op]=f[a[i].l][j][k][op]+a[i].val;
					Z[a[i].r][a[i].id][d][op]=(ss){a[i].l,j,k};
				}
				if(f[a[i].r][a[i].id][d][op]<dp[a[i].r][d][op])
				{
					dp[a[i].r][d][op]=f[a[i].r][a[i].id][d][op];
					S[a[i].r][d][op]={a[i].r,a[i].id,d};
				}
			} 
		}
	}
}
inline void work(ss x,int op)
{
	if(x.x==0||x.y==0||x.z==0) return;
	for(int i=1;i<=m;++i)
	{
		if(a[i].r==x.x&&a[i].id==x.y)
		{
			ss k=Z[x.x][x.y][x.z][op];
			int d=min((op==0?k.z+a[i].r-a[i].l:k.z+1),s[op]);
			if(d==x.z&&f[x.x][x.y][x.z][op]-a[i].val==f[k.x][k.y][k.z][op])
			{
				c[++num]=a[i].qid;
				work(k,op);
				return;
			}
		}
	}
}
int main()
{
	//freopen("1.in","r",stdin);
	scanf("%d%d%d%d%d",&n,&s[0],&s[1],&h,&m);
	for(int i=1;i<=m;++i)
	{
		scanf("%d%d%d%d",&a[i].id,&a[i].l,&a[i].r,&a[i].val);
		a[i].qid=i;
	}
	sort(a+1,a+m+1,cmp);
	memset(f,0x3f,sizeof(f));
	memset(dp,0x3f,sizeof(dp));
	solve(0);solve(1);
	int ans1=1e9,ans2=1e9,o1,o2;
	for(int i=0;i<=n;++i)
	{
		if(dp[i][s[0]][0]<ans1)
		{
			ans1=dp[i][s[0]][0];
			o1=i;
		} 
		if(dp[i][s[1]][1]<ans2)
		{
			ans2=dp[i][s[1]][1];
			o2=i;
		}
	}
	if(min(ans1,ans2)==1e9) {puts("IMPOSSIBLE");return 0;}
	else if(ans1<=ans2) work(S[o1][s[0]][0],0),puts("NIGHTS");
	else if(ans1>ans2)  work(S[o2][s[1]][1],1),puts("STAYS");
	printf("%d\n",num);
	for(int i=num;i>=1;--i) printf("%d ",c[i]);
	return 0;
} 

人一旦有了自信,就拥有了一切!

上一篇:NOIP2008 提高组题解


下一篇:浮点数的表示范围计算