洛谷2157

看到b的范围只有7,可以往搜索和状压方向想

因为搜索出现频率太低,先考虑状压

显然应该将b的意义设为状压集合

所以设f[i][j][k]表示现在打完饭的人是[1,i1]以及j所表示的集合(这里j所表示的集合不太好解释,举个例子,如j在二进制表示下为1010,则表示i+1i+3已经打完了饭;若为1011,则表示ii+1i+3已经打完了饭),最后一个打饭的人是i+k8k7)所用时间最小值(显然k这一维需要偏移数组,但下文为了方便,就暂不偏离)

注意这里的状态不能设置为先打完饭的人是[1,i1],然后再是j所表示的集合打饭。否则下面的数据会出错

1
9
9 4
5 7
9 3
8 1
6 6
3 5
4 5
9 4
3 0

正确答案应该是19,一种正确顺序:4 1 3 8 6 9 5 7 2

首先判断当前状态是否合法

如果存在某一个没打饭的人,后面有一个人已经打了饭且这个人的位置比这个没打饭的人的忍耐度还大,那就不合法(这个过程小模拟即可,不是本文重点,故不再赘述)

在当前状态合法之后,分情况DP

  1. k1

即此时最后一个打饭的人是在i之前,所以f[i][j][k]=min(f[i+k][(j<<k)|((1<<k)1)][0])

这个方程是什么意思呢?最后一个打饭的人是i+k,从i+k+1一直到i1都已经打好了饭,再加上j集合里的人也已经打好了饭,所以总的已经打好了饭的人的集合就是(j<<k)|((1<<k)1),而且第三维为0(因为最后一次打的饭的人刚好就为i+k

  1. 0k7

那我们就去掉j里面的第k个人即可,有新集合o=j^(1<<k)

此时在讨论在新集合状态下,最后一个打饭的人是谁,如果仍然是i前面的人,那么跟第一条类似,您可以自己推一下

如果是在i及之后,那么定下一个人转移即可

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1010;
int n;
int t[N],b[N];
int f[N][1<<8][16];
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-f;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-48;s=getchar();}
    return x*f;
}
bool valid(int i,int j)
{
	int temp=j;
	for(int k=0;k<=7&&i+k<=n;k++)
	{
		if((j>>b[i+k]+1)&&!(temp&(1<<k))) return 0;
		j>>=1;
	}
	return 1;
}
int main()
{
	int c=read();
	while(c--)
	{
		n=read();
		for(int i=1;i<=n;i++)
		t[i]=read(),b[i]=read();
		memset(f,0x3f,sizeof(f));
		for(int i=0;i<=7;i++)
		if(valid(1,1<<i))
		f[1][1<<i][i+8]=0;//初始化
		for(int i=1;i<=n;i++)
		{
			for(int j=0;j<(1<<8);j++)
			{
				if(!valid(i,j)) continue;
				for(int k=-8;k<=-1;k++)
				{
					if(i+k<=0) continue;
					if(i+k+b[i+k]<i-1) continue;
					if((j>>b[i+k]+k+1)) continue;
					if((j<<-k)>=(1<<8)) continue;
					f[i][j][k+8]=min(f[i][j][k+8],f[i+k][(j<<-k)|((1<<-k)-1)][8]);
				}
				for(int k=0;k<=7;k++)
				{
					if(!(j&(1<<k))) continue;
					int o=(j^(1<<k));
					for(int l=-8;l<=-1;l++)
					{
						if(i+l<=0) continue;
					    if(i+l+b[i+l]<i-1) continue;
					    if((o>>b[i+l]+l+1)) continue;
					    if((o<<-l)>=(1<<8)) continue;//这里一堆都是合法性判断,您可以尝试理解
					    f[i][j][k+8]=min(f[i][j][k+8],f[i+l][(o<<-l)|((1<<-l)-1)][8]+(t[i+k]|t[i+l])-(t[i+k]&t[i+l]));
					}
					for(int l=0;l<=7;l++)
					{
						if(!(o&(1<<l))) continue;
						f[i][j][k+8]=min(f[i][j][k+8],f[i][o][l+8]+(t[i+k]|t[i+l])-(t[i+k]&t[i+l]));
					}
				}
			}
		}
		int ans=0x7fffffff;
		for(int i=0;i<=min(7,n);i++)
		for(int j=0;j<=i;j++)
		ans=min(ans,f[n-i][(1<<i+1)-1][j+8]);
		printf("%d\n",ans);
	}
    return 0;
}

后记和感想

这题其实我做了12个小时,最开始做的时候是在6个月前,当时一直磕这道题,花了8小时,最后得到了0分的好成绩。当时我都已经想哭了,想着自己为什么这么蠢,也想着这么多时间花了也没什么用(当时连题解也没有看懂)。后来我一直都很怕这道题,看见他就只想逃避(一朝被蛇咬十年怕井绳?),最后今天(2021-9-29)鼓起勇气重新做,花了4个小时(包括多次更改状态,边界条件代码细节等等)最终才A了。看着绿色的AC,只能说心情真的很好。也许这就是OI的魅力吧,为了只是最后一刻的盛开。很可惜我马上就要退役了,事实证明了我确实没啥天赋,也许不能再体会这种令人面红耳赤的感觉了。假如您再看这篇题解,如果您有天赋和资质,那么恭喜您,未来的金牌在等着您!如果您跟我一样只是一个天赋平平的普通人,那么没关系,人各有志,普通人也有普通人的活法。不管您是谁,祝您幸福。

posted @   最爱丁珰  阅读(24)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示