您的位置:首页 > 其它

洛谷Oj-P1441 砝码称重-深搜+DP/Bitset

2018-03-17 09:59 330 查看
题目描述:

现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0)。

代码①:

int n,m;
int w[30];
bool book[30];
set<int> s;//想到用集合来去重
int ans = -inf;
void dfs(int cnt)
{
if(cnt == m)//如果被丢掉的砝码数到了m
{
int sum;
for(int i = 1; i <= (1 << n) - 1; ++i)//子集枚举
{
sum = 0;
for(int j = 0; j <= n - 1; ++j)
{
if(i & (1 << j) && book[j] == false)//如果i的第j位上为1且第j个砝码没被丢掉
sum += w[j];
}
s.insert(sum);//加入集合
}
ans = max(ans,(int)s.size());
s.clear();//重置集合
}
for(int i = 0; i <= n - 1; ++i)//效率极低,因为求的是全排列,有许多重复的状态
{
if(book[i] == false)
{
book[i] = true;
dfs(cnt + 1);

4000
book[i] = false;
}
}
return;
}
int main()
{
cin >> n >> m;
for(int i = 0; i <= n - 1; ++i)//注意下标
cin >> w[i];
dfs(0);
if(m == 0)
cout << ans << endl;
else
cout << ans - 1 << endl;//子集枚举时会出现要用的砝码和被去掉的砝码相同的情况,导致重量0可被称出
return 0;
}


代码②:

int n,m;
int w[30];
bool book[30];
set<int> s;
int ans = -inf;
int main()//双重子集枚举,超时
{
cin >> n >> m;
for(int i = 0; i <= n - 1; ++i)
cin >> w[i];
//枚举去掉的砝码的子集
for(int i = 0; i <= (1 << n) - 1; ++i)
{
int cnt = 0;
memset(book,0,sizeof(book));
for(int j = 0; j <= n - 1; ++j)
{
if(i & (1 << j))
{
cnt++;
book[j] = true;
}
}
if(cnt == m)//如果i的二进制中出现了m个1
{
int sum = 0;
//枚举砝码的子集
//若j从0开始,会把sum=0包括进去
for(int j = 1; j <= (1 << n) - 1; ++j)
{
sum = 0;
for(int k = 0; k <= n - 1; ++k)
//j的第k位为1,i的第k位为0
if((j & (1 << k)) && book[k] == false)
sum += w[k];
s.insert(sum);
ans = max(ans,(int)s.size());
}
s.clear();
}
}
if(m == 0)
cout << ans << endl;
else
cout << ans - 1 << endl;
return 0;
}


代码③:

int n,m;
int w[30];
bool book[30];
bool mark[5000][30];//判断重复状态的数组
int sz = 1;
set<int> s;
int ans = -inf;
void copy()
{
for(int i = 1; i <= n; ++i)
if(book[i] == true)
mark[sz][i] = true;
sz++;
return;
}
bool check_1(bool a[],bool b[])
{
for(int i = 1; i <= n; ++i)
if(a[i] != b[i])
return false;
return true;
}
bool check_2()
{
for(int i = 1; i <= sz - 1; ++i)
for(int j = 1; j <= n; ++j)
if(check_1(book,mark[i]) == true)
return true;
return false;
}
void dfs(int cnt)
{
if(cnt == m)
{
if(check_2() == true)//加入了重复状态判断以剪枝,依然超时。因为全排列的搜索重复状态太多了
return;
copy();
int sum;
for(int i = 1; i <= (1 << n) - 1; ++i)
{
sum = 0;
for(int j = 0; j <= n - 1; ++j)
{
if(i & (1 << j) && book[j] == false)
sum += w[j];
}
s.insert(sum);
}
ans = max(ans,(int)s.size());
s.clear();
}
for(int i = 0; i <= n - 1; ++i)
{
if(book[i] == false)
{
book[i] = true;
dfs(cnt + 1);
book[i] = false;
}
}
return;
}
int main()
{
cin >> n >> m;
for(int i = 0; i <= n - 1; ++i)
cin >> w[i];
dfs(0);
if(m == 0)
cout << ans << endl;
else
cout << ans - 1 << endl;
return 0;
}


代码④:

int n,m;
int w[30];
bool book[30];
bool dp[2010];
int ans = -inf;
void solve()//01背包+滚动数组
{
memset(dp,0,sizeof(dp));
dp[0] = true;//初始化
for(int i = 1; i <= n; ++i)//对于每一个砝码
{
if(book[i] == true)//如果被丢掉了
continue;//跳过
for(int j = 2000; j >= 0; --j)//最大重量20*100 = 2000。倒序判断
if(dp[j] == true && j + w[i] <= 2000)//如果重量j能被称出且在重量j的基础上加上w[i]不超重
dp[j + w[i]] = true;//则重量j + w[i]也能被称出
}
int tot = 0;
for(int i = 1; i <= 2000; ++i)
if(dp[i] == true)
tot++;
ans = max(ans,tot);//更新答案
return;
}
void dfs(int id,int cnt)//对当前的砝码,有丢与不丢两种选择,2^n种结果,这正是子集的大小(全排列的大小为n!)。这是枚举子集的搜索!!!
{
if(cnt > m)//针对cnt剪枝,丢得太多啦
return;
if(id == n + 1)//针对id的边界条件
{
if(cnt == m)
solve();
return;
}
dfs(id + 1,cnt);//不去掉砝码id,对砝码id + 1进行操作
book[id] = true;//去掉砝码id
dfs(id + 1,cnt + 1);//去掉砝码id,对砝码id + 1进行操作
book[id] = false;//我从来没有动过砝码id,别瞎说啊
return;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
cin >> w[i];
dfs(1,0);
cout << ans << endl;
return 0;
}


代码⑤:

int n,m;
int w[30];
int ans = -inf;
int count_one(int x)//数出x的二进制中1的个数
{
int cnt = 0;
for(int i = 0; i <= n - 1; ++i)
if(x & (1 << i))
cnt++;
return cnt;
}
int main()
{
cin >> n >> m;
for(int i = 0; i <= n - 1; ++i)
cin >> w[i];
for(int i = 0; i <= (1 << n) - 1; ++i)
{
if(count_one(i) == n - m)//这儿用的是n-m,更好
{
bitset<2010> b;
b[0] = 1;
for(int j = 0; j <= n - 1; ++j)
if(i & (1 << j))
b = b | b << w[j];
ans = max(ans,(int)b.count());
}
}
cout << ans - 1 << endl;//不要忘了排除重量0
return 0;
}


代码⑥:

int n,m;
int w[30];
bool book[30];
bool dp[2010];
int prev;// !
int ans = -inf;
void solve()
{
memset(dp,0,sizeof(dp));
dp[0] = true;
for(int i = 1; i <= n; ++i)
{
if(book[i] == true)
continue;
for(int j = 2000; j >= 0; --j)
if(dp[j] == true && j + w[i] <= 2000)
dp[j + w[i]] = true;
}
int tot = 0;
for(int i = 1; i <= 2000; ++i)
if(dp[i] == true)
tot++;
ans = max(ans,tot);
return;
}
void dfs(int cnt)//子集枚举搜索的另一种写法。不断地丢砝码
{
if(cnt == m)
{
solve();
return;
}
for(int i = prev + 1; i <= n; ++i)
{
book[i] = true;//丢掉
prev = i;//上一个被丢的砝码的编号
dfs(cnt + 1);
book[i] = false;//捡回来
}
return;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
cin >> w[i];
dfs(0);
cout << ans << endl;
return 0;
}


解决方法:

以下是代码⑥的搜索树。

以数据

5 2

1 2 3 4 5

为例

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: