原题链接 https://www.luogu.com.cn/problem/P1504
闲话时刻
这道题是一道 暴力 dp好题,dp 的方法和平常的不大一样,也许是我的脑回路清奇,总之还是值得做一下的 。
题目大意
有 n 组数,每组数都是从大到小排列(好像没什么用),现在从每组数中删去一些数使得每组数的和相等,问这个相等的和最大是多少;
题解
首先我们看到数据范围比较小,脑子中的第一个思路应该是暴力;
暴力什么呢?
一个简单的想法就是把每一组在一波瞎搞之后所能得到的所有可能的高度都记录下来,然后从大到小去枚举最后高度,如果每一组瞎搞之后都能达到这个高度,那么这个高度就是最优解了;
我的思路就是这样,只不过求每一组的所有可能的高度我是用的 dp 来求的,当然是暴力的复杂度qwq
状态设置
dp [ i ][ j ]:第 i 组的积木在瞎搞之后能否达到 j 的高度;
状态转移
假如说 dp [ i ][ j ] 是合法的,那么我们再移走一块积木 a [ k ],那么也是合法的,即:
dp [ i ][ j-a [ k ] ] = dp [ i ][ j- a [ k ] ] | dp [ i ][ j ](要么这个高度本来就合法,要么是 j 通过移走 a [ k ] 使其变得合法,所以中间是 | 运算)
枚举顺序
有个小细节就是:要先枚举每一组的积木再去枚举高度
这样的话才能保证每一块积木只可能被删除一次,如果枚举反了的话会出现一积木多用的现象;
答案
我们从大到小去枚举高度,如果存在一个高度使得每一组积木都可以拼成,那么直接输出并结束程序就OK了;
Code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read()
{
char ch=getchar();
int a=,x=;
while(ch<''||ch>'')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>=''&&ch<='')
{
a=(a<<)+(a<<)+(ch-'');
ch=getchar();
}
return a*x;
}
const int N=;
int n;
int len[N],sum[N];
int a[N][N],dp[N][N*N]; //dp[i][j]:第i个城堡进行若干操作后能否得到高度为j的情况
int main()
{
n=read();
for(int i=;i<=n;i++)
{
int x=read();
while(x!=-)
{ //len[i]:第i组积木的个数
a[i][++len[i]]=x; //a[i][j]:第i组的第j个积木的高度
sum[i]+=x; //sum[i]:第i组积木的最大高度(一个积木也没有删掉的情况)
x=read();
}
dp[i][sum[i]]=;
}
for(int i=;i<=n;i++)
for(int j=;j<=len[i];j++) //注意这里一定要先枚举每一组的积木
for(int k=;k<=sum[i];k++) //再枚举高度
dp[i][k]|=dp[i][k+a[i][j]]; //通过dp[i][k+a[i][j]]删除a[i][j]来使dp[i][k]变得合法
for(int j=;j>=;j--)
{
bool bj=;
for(int i=;i<=n;i++)
{
if(!dp[i][j]) //只要有一组拼不成就白搭
{
bj=;
break;
}
}
if(bj==) //如果都能拼成的话,此时的高度一定是最优解
{
printf("%d\n",j);
return ;
}
}
return ;
}