淘淘蓝蓝的CSP-S神妙膜你赛2-淘淘蓝蓝喜欢01串 题解

问题简述

给定\(n\)个盒子,每个盒子的容器为\(b[i]\),里面装有\(a[i]\)个物品。今有\(q\)组询问,每组询问给出一个正整数\(k(k<=n)\),已知一个盒子里的一件物品转移到另一个盒子需要一单位时间,要求将所有物品转移到任意\(k\)个箱子中最少需要多少时间。

数据范围

对于前60%的数据,\(n<=20\). \(q<=100\)
对于前100%的数据,\(n<=80\). \(q<=200\). \(a[i]<=b[i]<=20\)

看到这个题,我的第一反应就是暴力

暴力很好想,先把数组排个序,直接dfs查找前(n-k)个箱子中的物品进行验证就可以了
我们会发现\(n<=80\),但\(q<=200\),所以一定会有重复的问题
这时可以开个数组记录一下搜索,这样就把\(q\)\(200\)变为\(80\)
这样你就获得了\(60\)分。。。
(其实还可以加上一些剪枝,比如如果搜的上一个盒子失败了,那这一个的盒子的容积必须小于上一个,否则就不行,但是并没有什么卵用

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
int n;
int q;
struct node{
	int a,b;
}box[85];
int ans[85];
int c[85];
int num;
int maxx;
int tot;
int totl;
int ccc;
bool cmp(node x,node y)
{
	if(x.a==y.a) return x.b<y.b;
	return x.a<y.a; 
}
bool cmp2(int a,int b)
{
	return a>b;
}
bool flag;
bool tag;
int s;
void dfs(int dep,int num,int now,int lor,int cap)
{
	if(dep>=num)
	{
		if(totl<=tot-cap)
		{
			//cout<<lor<<" "<<tot-cap<<endl;
			flag=1;
			ans[num]=lor;
		}else{
			tag=1;
			ccc=min(ccc,cap);
		}
		return;
	}
	for(int i=now+1;i<=n;i++)
	{
		if(flag==1) return;
		if(cap+box[i].b<ccc) 
		{
			if(tag==1) if(box[i].b>box[i-1].b) continue;
			dfs(dep+1,num,i,lor+box[i].a,cap+box[i].b);
		}
	}
	return;
}
int main(){
	freopen("str.in","r",stdin);
	freopen("str.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) 
	{
		box[i].a=read();
		totl+=box[i].a;
	}
	for(int i=1;i<=n;i++) 
	{
		box[i].b=read();
		tot+=box[i].b;
		c[i]=box[i].b;	
	}
	q=read();
	memset(ans,0x3f,sizeof(ans));
	sort(box+1,box+1+n,cmp);
	sort(c+1,c+1+n,cmp2);
	while(q--)
	{
		num=read();
		if(ans[n-num]<=1600)
		{
			printf("%d\n",ans[n-num]);
			continue;
		}
		if(num<=maxx) 
		{
			ans[n-num]=-1;
			puts("-1");
			continue;
		}
		s=0;
		for(int i=1;i<=num;i++)
		{
			s+=c[i];
		}
		if(s<totl) 
		{
			maxx=max(maxx,num);
			ans[n-num]=-1;
			puts("-1");
			continue;
		}	
		flag=0;
		tag=0;
		ccc=INT_MAX;
		dfs(0,n-num,0,0,0);
		if(flag==1){
			printf("%d\n",ans[n-num]);
		}else{
			ans[n-num]=-1;
			puts("-1");
		}
	}




	return 0;
}

那么,正解是什么?

动态规划

仔细地观察题面,我们会发现转移时间只和盒子剩余的体积和选择货物的大小有关
这时我们可以设一个三维\(dp\)数组
\(dp[i][j][k]\)表示在前\(i\)个盒子里,选择了\(j\)个,剩余\(k\)体积的货物的最大值
对于第\(i\)个箱子有两种情况
1.不选择\(i\)号箱 \(dp[i][j][k]=dp[i-1][j][k]\)
2.选择\(i\)号箱 \(dp[i][j][k]=dp[i-1][j-1][v-(b[i]-a[i])]+a[i]\)
所以我们就得到了转移方程

\(f[i][j][v]=max(f[i-1][j][v], f[i-1][j-1][v-(b[i]-a[i])] + a[i])\)
对于询问\(k\)个箱子最小时间,只需要在\(f[n][j][k]\)跑一遍\(j\)\(k\)判断当前空余体积\(k\)是否能容下剩余\((sum – f[n][j][k])\)个货物即可

代码:

#include<bits/stdc++.h>
using namespace std;
template <typename T>
void readin(T &x) {
	x = 0;
	T fh = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
	for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
	x *= fh;
}
void d_read(double &x) {
	x = 0.0;
	int fh = 1;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
	for (; isdigit(ch); ch = getchar()) x = x * 10 + (ch ^ 48);
	if (ch == '.') {
		double num = 1.0;
		ch = getchar();
		for (; isdigit(ch); ch = getchar()) x = x + (num /= 10) * (ch ^ 48);
	}
	x *= fh;
}

template <typename T>
void wt(T x) {
	if (x > 9) wt(x / 10);
	putchar(x % 10 + 48);
}
template <typename T>
void writeln(T x, char c) {
	if (x < 0) {
		putchar('-');
		x = -x;
	}
	wt(x);
	putchar(c);
}
int f[105][105][6055], n, a[105], b[105], ans[105], inf = 2100, lim = 2000; 
int main() {
	freopen("str.in","r",stdin);
	freopen("str.out","w",stdout);
	readin(n);
	int flg = 0;
	for (int i = 1; i <= n; i ++) {
		readin(a[i]);
		if (a[i]) flg ++;
	}
	for (int i = 1; i <= n; i ++) {
		readin(b[i]);
	} 
	for (int i = 0; i <= n; i ++) {
		for (int j = 0; j <= n; j ++) {
			for (int v = 0; v <= inf * 2; v ++) {
				f[i][j][v] = -1e9;
			}
		}
	}
	f[0][0][0] = 0;
	for (int i = 1; i <= n; i ++) {
		for (int j = 0; j <= i; j ++) {
			for (int v = 0; v <= lim; v ++) {
				if (f[i - 1][j][v] != -1e9) f[i][j][v] = max(f[i][j][v], f[i - 1][j][v]);
				if (j && v >= (b[i] - a[i]) && f[i - 1][j - 1][v - (b[i] - a[i])] != -1e9) f[i][j][v] = max(f[i][j][v], f[i - 1][j - 1][v - (b[i] - a[i])] + a[i]);
			}
		}
	}
	int sum = 0;
	for (int i = 1; i <= n; i ++) {
		sum += a[i];
	}
	for (int j = 1; j <= flg; j ++) {
		ans[j] = 1e9;
		for (int v = 0; v <= lim; v ++) {
			if (sum - f[n][j][v] <= v) ans[j] = min(ans[j], sum - f[n][j][v]);
		}
	}
	int q, k;
	readin(q);
	for (int i = 1; i <= q; i ++) {
		readin(k);
		if (ans[k] == 1e9) puts("-1");
		else writeln(ans[k], '\n');
	}
	return 0;
}

但是,还有一个很大的问题……

就是题面到底和字符串有什么关系啊喂(

posted @ 2022-07-22 15:43  Yoican  阅读(117)  评论(0编辑  收藏  举报