【2022省选模拟】老人与海——背包DP、矩阵加速、倍增

本文介绍了如何使用矩阵加速求解物品数不超过5、质量不超过10的完全背包问题,通过预处理和二分查找技巧,将时间复杂度降低到O(n^3a^3logh + qn^2a^2logh)。关键在于理解并利用了dp数组的单调性特征。
摘要由CSDN通过智能技术生成

没 有 原 题 链 接

题目描述

在这里插入图片描述
在这里插入图片描述

题解

这是一个物品数不超过5、物品质量不超过10的计算完全背包方案数的问题,要求一个最小的背包大小,使得装满背包的方案数 ≥ h i \ge h_i hi

考虑到背包大小可能很大,我们可以用矩阵加速这个背包DP。设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示考虑前 i i i 个物品、背包大小为 j j j 的方案数,则
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − a i ] dp[i][j]=dp[i-1][j]+dp[i][j-a_i] dp[i][j]=dp[i1][j]+dp[i][jai]我们只保留有用的最大的10个 j j j,这样总状态数不超过 50,直接对这个DP矩阵加速即可。

然而这个 d p [ i ] [ j ] dp[i][j] dp[i][j] 貌似关于 j j j 并没有单调性?但是由于 a i ≤ 10 a_i\le 10 ai10,所以每连续10个 j j j 的DP值中的最大值是满足单调不降的,我们只需要通过这个二分找出包含答案的10的连续段即可。

通过预处理+把二分换成倍增,可以做到 O ( n 3 a 3 log ⁡ h + q n 2 a 2 log ⁡ h ) O(n^3a^3\log h+qn^2a^2\log h) O(n3a3logh+qn2a2logh) 的时间复杂度。

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define lll __int128
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=-1;
const ll INF=2e17;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}
const int M=55;
const lll E=1;
struct matrix{
	ll c[M][M];int n,m;matrix(){}
	matrix(int N,int M){memset(c,0,sizeof(c)),n=N,m=M;}
	inline matrix operator*(const matrix&b)const{
		matrix res=matrix(n,b.m);
		for(int i=0;i<n;i++)
			for(int k=0;k<m;k++)if(c[i][k])
				for(int j=0;j<b.m;j++)if(res.c[i][j]<INF){
					lll ad=min(E*c[i][k]*b.c[k][j],E*INF);
					res.c[i][j]+=ad;
					if(res.c[i][j]>INF)res.c[i][j]=INF;
				}
		return res;
	}
}A,B;
matrix mb[M+10];
int n,a[10],id[10][15],m;
signed main()
{
	freopen("dignity.in","r",stdin);
	freopen("dignity.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)
		for(int j=0;j<10;j++)id[i][j]=m++;
	A=matrix(1,m),B=matrix(m,m);
	for(int i=1;i<=n;i++){
		for(int j=0;j<9;j++)
			B.c[id[i][j+1]][id[i][j]]=1;
		if(i>1){
			for(int j=0;j<m;j++)
				B.c[j][id[i][9]]=B.c[j][id[i-1][9]];
		}B.c[id[i][10-a[i]]][id[i][9]]=1;
	}
	for(int i=1;i<=n;i++)A.c[0][id[i][9]]=1;
	mb[0]=B;
	for(int i=1;i<=60;i++)mb[i]=mb[i-1]*mb[i-1];
	for(int Q=read();Q--;){
		ll h=read(),t=0,lim=h*100+9;
		matrix a=A;
		for(int i=60;i>=0;i--)if(t+(1ll<<i)<=lim){
			matrix ta=a*mb[i];
			ll mx=0;
			for(int i=0;i<10;i++)
				mx=max(mx,ta.c[0][id[n][i]]);
			if(mx<h)t+=(1ll<<i),a=ta;
		}
		if(h>1)t++,a=a*mb[0];
		ll as=t-9;
		for(int i=0;i<10;i++,as++)
			if(a.c[0][id[n][i]]>=h)break;
		if(as>h*100)printf("What a pity!\n");
		else print(as);
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值