题目描述
牛牛的计算机一共有m块内存,现在有n条指令,每条指令是一个01序列
如果指令的第i个字符为1,说明这条指令需要访问第i块内存,从0开始
每条指令的执行代价为k^2, k为新访问内存的数量,即之前的指令都没有访问到的内存数量
你可以随意安排n条指令的执行顺序,求执行完所有指令的最小代价
输入描述
第一行先输入一个整数n和m(1≤n,m≤20)
接下来每行输入一个01字符串,长度不超过20
输出描述
输出一个整数
输入样例 1
3 3 111 001 010
输出样例 1
3
输入样例 2
5 5 11101 00111 10101 00000 11000
输出样例 2
9
输入样例 3
3 4 1000 1100 1110
输出样例 3
3
备注:
子任务1:n <= 5 子任务2:n <= 10 子任务3:无限制
解题思路
题目说可以随意安排指令的顺序,我试过暴力总是会超时,最多为75.92%
不行就换一种想法吧,我们可以试一试状态压缩,我觉得状态压缩其实和暴力有点像
我们可以用一个二进制表示的数字来表示指令的执行状态,假设有5条指令,那就用一个长度为5的二进制数字存储指令的执行状态,数为xxxxx,从右往左数,如果当前位上为1表示第i条指令被执行过,反之则没有
用一个数组dp[i]来表示执行状态为i时的最小执行代价,mem[i]表示执行指令状态为i时已被访问的内存
则我们就可以用两个for循环嵌套,第一个枚举状态,第二个枚举指令,当发现状态和指令不匹配时,将执行指令后的代价与状态最小代价比较(状态为之前状态加上指令得来),获取最小的
核心代码
for(int j=0;j<(1<<n);j++){//枚举状态
for(int k=0;k<n;k++){//枚举指令
if(!(j&(1<<k))){//当状态和指令不匹配时
int ans=dp[j]+solve(mem[j],sme[k]);//获取执行指令后的代价
if(dp[j|(1<<k)]>ans){//与状态的当前最小代价相比较
dp[j|(1<<k)]=ans;
mem[j|(1<<k)]=mem[j]|sme[k];
}
}
}
}
其中的solve为函数,是用来解决在当前状态下执行指令的执行代价
函数体
int solve(int a,int b){//求出在当前状态下执行指令的执行代价
int ans=0;
for(int i=0;i<m;i++){
if((a&(1<<i))==0&&(b&(1<<i))) ans++;//当内存不匹配时加1
}
return ans*ans;
}
题解代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
int dp[(1<<20)+5],mem[(1<<20)+5];// dp[i]表示执行的指令状态为i时的最小代价,mem[i]表示执行指令状态为i时已被访问的内存
int sme[22];//标记每条指令访问那些内存
char s[22];
int n,m;
int solve(int a,int b){//求出在当前状态下执行指令的执行代价
int ans=0;
for(int i=0;i<m;i++){
if((a&(1<<i))==0&&(b&(1<<i))) ans++;//当内存不匹配时加1
}
return ans*ans;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>s;
for(int j=0;j<m;j++){//标记每条指令访问那些内存
if(s[j]=='1') sme[i]=sme[i]|(1<<j);
}
}
memset(dp,INF,sizeof(dp));
dp[0]=0;
for(int j=0;j<(1<<n);j++){//枚举状态
for(int k=0;k<n;k++){//枚举指令
if(!(j&(1<<k))){//当状态和指令不匹配时
int ans=dp[j]+solve(mem[j],sme[k]);//获取执行指令后的代价
if(dp[j|(1<<k)]>ans){//与状态的当前最小代价相比较
dp[j|(1<<k)]=ans;
mem[j|(1<<k)]=mem[j]|sme[k];
}
}
}
}
cout<<dp[(1<<n)-1]<<endl;
return 0;
}