–>题目传送门<–
题意:
牛牛有m个内存块,现在他要执行n条指令,每条指令是长度为m的01字符串,如果s[i]=1则代表需要访问第i块内存,如果是0则不需要。每次执行一条指令所需要付出的代价为k^2, k为新访问内存的数量,即之前的指令都没有访问到的内存数量。
可以随意安排执行顺序,但必须全部执行,问最小的执行代价。
题解:
先说一下正确的解法:状压dp
题目给的数据为1至20,很明显就是告诉我们可以使用状压dp去做。
这里我们利用dp[i][j]代表执行了i条指令,n条指令的执行状态为j(j的二进制表示即为执行状态,
从低位向高位数第i位如果为1则第i条指令已被执行,为0则第i条指令未被执行)时的最小代价。
再利用一个val[i][\j]代表执行了i条指令,n条指令的执行状态为j时的内存访问情况。依旧用二进制表示第i块内存是否被访问过。(1代表曾被访问过,0表示未被访问过)。
状态转移方程为:
dp【i】【j | (1<<k)】=
min(dp【i】【j | (1<<k)】,dp【i-1】【j】+cost(val【i-1】【j】,a【k】);
注:如果是二维数组则需要使用滚动数组,否则MLE
注:可以优化为1维,每一个dp【j】保存的即为状态为j的最小代价,每一次状态转移比较时都是和前一次比较,所以相当于新值与旧值比较。
AC代码:(二维+滚动数组)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define Re register
#define U unsigned
#define forn(i,a,b) for(Re int i = a;i <= b;++i)
#define nfor(i,a,b) for(Re int i = a;i >= b;--i)
#define CLR(i,a) memset(i,a,sizeof(i))
#define BR printf("--------------------\n")
#define pb push_back
#define Rep(i,a,b) for(int i=a;i<=b;i++)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
const double PI=atan(1.)*4.;
const int maxn=1e6+5;
const int inf = 0x3f3f3f3f;
int dp[2][1<<20],val[2][1<<22];
int a[22];
string s;
int n,len;
int cal(string s)//转换为十进制数字表示状态
{
int res=0,b=1;
for(int i=0;i<len;i++)
{
res+=(s[i]=='1'?b:0);
b<<=1;
}
return res;
}
int cost(int from,int to)//计算从前一个状态转移到下一个状态所需的代价
{
int res=0;
for(int i=0;i<22;i++)
{
if((from&(1<<i))==0&&(to&(1<<i)))
res++;
}
return res*res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(dp,inf,sizeof(dp));//初始化
cin>>n>>len;
for(int i=0;i<n;i++)
{
cin>>s;
a[i]=cal(s);
dp[0][1<<i]=cost(0,a[i]);//初始化,第一次执行的时候执行第i条指令的代价
val[0][1<<i]=a[i];//同时记录改状态下内存块被访问的状态
}
int p,q;//滚动数组的下标
for(int i=1;i<n;i++)
{
p=i&1,q=(i-1)&1;//p表示本次操作,q代表上一次操作
for(int j=1;j<(1<<n);j++)
{
if(dp[q][j]==inf)continue;//如果j状态不存在
for(int k=0;k<n;k++)
{
if(!(j&(1<<k)))//如果j状态下未执行第k条指令
{
int tmp=dp[q][j]+cost(val[q][j],a[k]);//计算代价
if(tmp<dp[p][j|(1<<k)])
{
dp[p][j|(1<<k)]=tmp;
val[p][j|(1<<k)]=val[q][j]|a[k];//同步
}
}
}
dp[q][j] = inf;//初始化下一次执行时,状态为j的最小代价。
}
}
//最终答案为执行了n条指令,且每一条指令都被执行(j的状态为111111...)
cout<<dp[(n-1)&1][(1<<n)-1]<<endl;
return 0;
}
下面说一下我的贪心方法:(WA 望引以为戒)
设k为执行该指令所需访问新内存块的数量。
每一次都选取k最小的指令执行,执行过后对还未被执行过的指令更新k值(即在本次执行中访问的新内存块,在其后就不算时新的访问了),然后对于本次执行选取的指令,令其k值为1000005(相当于本题的正无穷),每次都进行排序。
每次选取k最小的执行,直至结束。
WA的贪心代码:(末尾给了错误原因加数据)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
bool vis[25];
struct node
{
string s;
int cnt;
}a[25];
bool cmp(node a,node b)
{
return a.cnt<b.cnt;
}
int main()
{
int n,len,ans=0;
cin>>n>>len;
for(int i=0;i<n;i++)
{
cin>>a[i].s;
int cnt=0;
for(int j=0;j<len;j++)
{
if(a[i].s[j]=='1')
cnt++;
}
a[i].cnt=cnt;
}
for(int i=n;i<25;i++)a[i].cnt=25;
memset(vis,false,sizeof(vis));
sort(a,a+n,cmp);
int zx=0;
while(zx!=n)
{
ans+=a[0].cnt*a[0].cnt;
string ss=a[0].s;
for(int i=0;i<len;i++)
{
if(ss[i]=='1')
vis[i]=true;
}
a[0].cnt=1000005;
for(int i=1;i<n-zx;i++)
{
string ss=a[i].s;
for(int j=0;j<len;j++)
{
if(ss[j]=='1'&&vis[j])
{
a[i].s[j]='0';
a[i].cnt--;
}
}
}
zx++;
sort(a,a+n,cmp);
}
cout <<ans<< endl;
return 0;
}
/*
给组数据(贪心最小时面临同样是最小的时候做的选择不同结果也可能会不同)
3 4
1001
0110
0111
*/
欢迎评论!