Luogu2157 [SDOI2009] 学校食堂 - dp -

题目链接:https://www.luogu.com.cn/problem/P2157

题解:
注意每个决策会跟其在前面还没有选过的人有关(bi),会和上一次选的人有关
考虑 \(dp[i][S][k]\) 表示考虑到第 \(i\) 个人,\([i,i+7]\) 是否选了的状态是 \(S\) ,最后一个选的位置是 \(i+k\)
转移:

  • \(dp[i][S][k] \rightarrow dp[i+1][S>>1][k-1]\),其中 \(S\& 1=1\),这就是当前人已经取完了的情况,注意此时不参与下面的转移
  • \(dp[i][S][k] + f(k,l) \rightarrow dp[i][S|(1<<l)][l]\),其中 \(f(k,l)\) 就是做这个菜的时间,而且有 \(l \notin S\)

细节若干:

  • \(k \in [-8,7]\) -8 是因为极端情况下 \(b_i=7\),然后这个人等后面7个人都吃完了再吃,这样转移到的是 \(i+8\)
  • S 枚举到 \((2^8-1)\),而不是 \(2^{b_i+1}-1\),这是因为可能出现下面的第三种情况
  • 题意里的小细节:注意是当前人才会愤怒,而且比如说当前第 \(i\) 个人的时候让 \(i+2\) 先走,然后该 \(i+1\) 走的时候,此时 \(i+2\) 已经没有了,紧跟着的第一个人从 \(i+3\) 算起,这也就意味着不能预处理,只能根据状态 \(S\) 求出当前人最远能容忍到哪里
// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 1005;

int n,a[maxn],b[maxn];
ll dp[maxn][(1 << 8)+5][17];
int st[maxn][9];

void ck(ll &a,ll b){a = min(a, b);}

void solve(){
	memset(dp,0x3f,sizeof dp);
	memset(st,0,sizeof st);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]),st[i][0]=1;
	
	for(int k=1;k<=7;k++){
		for(int i=1;i+k<=n;i++){
			if(k <= b[i]){
				st[i][k] = 1;
				for(int j=i+1,t=k;j<=i+k-1;j++,t--){
					st[i][k] &= st[j][t-1];
				}
			}
		}
	}
	
	for(int k=0;k<=b[1];k++)if(st[1][k]){
		dp[1][1 << k][k + 8] = 0;
	}
	
	for(int i=1;i<=n;i++)
		for(int S=0;S<=(1 << b[i]+1)-1;S++){
			for(int k=max(-8, -i+1);k<=7&&i+k<=n;k++){
				if(S & 1){
					ck(dp[i+1][S >> 1][k+7], dp[i][S][k+8]);
					continue;
				}
				if(k >=0){
					if((S&(1<<k)) && k <= b[i]);
					else continue;
				}
				// k + 8
				int fi = inf;
				for(int l=0;l<=b[i]&&i+l<=n;l++)if((S&(1<<l)) == 0){
					if(i+l>fi)break; 
					fi = min(fi, i+l+b[i+l]);
					int T = S ^ (1<<l);
					ck(dp[i][T][l + 8], dp[i][S][k + 8] + (a[i+k]|a[i+l]) - (a[i+k]&a[i+l]));
				}
			}
		}
	ll res = inf;
	for(int i=-8;i<=0;i++)ck(res, dp[n][1][i + 8]);
	cout << res << '\n';
}

signed main(){
//	freopen("Luogu2157.in","r",stdin);
	int te=1;scanf("%d",&te);
	while(te--)solve();

	return 0;
}
posted @ 2022-12-20 21:25  SkyRainWind  阅读(14)  评论(0编辑  收藏  举报