失踪人口回归
多日不见,博主在偷懒百忙之中,又重操旧业,开始写写博客了,这次呢,主要是因为一道非常恶心好的题。
嗯,就是标题中的UVa10817了。(可能UVa的加载有点慢,这里帖vjudge上面的地址)
这道题呢,相信大家也不陌生,就是某本算法竞赛书上面的例题了。
我第一眼没去看它的分析,而是自己想了想我们之前做的几道状压dp,
我觉得这不就是一道三进制的状压dp吗??
书上写的是记忆化搜索,同学也都差不多用的记忆化搜索,但是我就是不服了,
难道我就是不能自己用递推写法A一道状压dp???
(噩梦开始)
首先呢, d[3s−1] d [ 3 s − 1 ] 就是我们的目标状态,而初始状态,因为有固定教师,就有点麻烦了
for(int i=1;i<=m;i++)
{
c[i]=geti();
while( (k=geti()) )
{
st[k]++;
if(pp) break;
}
}
for(int i=1;i<=s;i++)
S += Pow3[i-1]*min(2,st[i]);
其中,S是我们的初始状态,geti()是读入整数的函数
写到这里,我不得不说说这道题的读入了。
直接的scanf肯定是不能满足的了,有的人就用了fgets 或者 cin.getline。
我还是想了想,用几分钟写了一个这道题专用的读入
见下方
bool pp;
int geti()
{
int x,f=1;char c; pp=0;
while((c=getchar())<'0' || c>'9')
{
if(c == '-') f=-1;
if(c == -1) return -1;
}
x = c-'0';
while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
if(c == '\n') pp=1;
return x*f;
}
写完这玩意,又花了几十分钟写了个状压dp的基本模型(当然是三进制版的)
inline int K(int x,int y)
{
int res=0;
for(int i=0;i<10;i++)
res += Pow3[i]*min(2,x%Pow3[i+1]/Pow3[i] + y%Pow3[i+1]/Pow3[i]);
return res;
}
void Solve()
{
int S=0; d[0] = 0;
for(int i=1;i<=s;i++)
S += Pow3[i-1]*min(2,st[i]);
for(int S1=0;S1for(int i=1;i<=n;i++)
d[K(S1,V[i])] = min(d[S1] + a[i],d[K(S1,V[i])]);
ans = d[ Pow3[s]-S-1 ];
for(int i=1;i<=m;i++)
ans += c[i];
}
然后华丽丽地WA掉
我只好上udebug的网站去拿数据,然后开始了漫漫的调试之路。
我发现数据里面有一些居然是inf!
就顺藤摸瓜,发现我的枚举顺序是由0到(目标状态-初始状态)的。
for(int S1=0;S1for(int i=1;i<=n;i++)
d[K(S1,V[i])] = min(d[S1] + a[i],d[K(S1,V[i])]);
而我的K(增加)函数却会向上取
inline int K(int x,int y)
{
int res=0;
for(int i=0;i<10;i++)
res += Pow3[i]*min(2,x%Pow3[i+1]/Pow3[i] + y%Pow3[i+1]/Pow3[i]);
return res;
}
意思是说,假如应该达到的状态是0 1 0 0 1
最优解上的某一集合是0 1 0 1 1
加入后就“越过”了这个状态。
(因为本题只要求至少2个老师,可以超出)
这样的话就会发现我找不到这个解,于是就出错了。
改了改,写成这样:
inline int K(int x,int y)
{
int res=0;
for(int i=0;i<10;i++)
res += Pow3[i]*min(2,x/Pow3[i]%3 + y/Pow3[i]%3);
return res;
}
void Solve()
{
int S=0;
for(int i=1;i<=s;i++)
S += Pow3[i-1]*min(2,st[i]);
d[S] = 0;
for(int S1=S;S1for(int i=1;i<=n;i++)
d[K(S1,V[i])] = min(d[S1] + a[i],d[K(S1,V[i])]);
ans = d[ Pow3[s]-1 ];
for(int i=1;i<=m;i++)
ans += c[i];
}
似乎没有问题了。。。
但是在udebug上还是有一些数据过不了,我只好暂时停一下了,叫同学看看。
但是请了好几个大神,都说无能为力。
逆天之行必遭天谴
毕竟弄了一上午了,还是先暂时放一放吧。
下午老师给我们回顾了一下背包问题,(感谢背包)
再次开始调试这道题的时候,已经是晚上了。
晚上的气氛比较沉闷,正好是调试代码的好时候。
用udebug的数据弄了1个多小时,我竟然突发奇想,想到下午的背包问题,
用几个自造数据试了试,终于找到一个错误了。
咳咳(正文开始)
相信大家学过背包的,一定都知道01背包如果要用滚动数组的话,就必须要从大到小枚举。
而这道题就类似于01背包,可以举一个数据:
状态 | 集合 |
---|---|
目标状态 | 2 2 2 2 2 |
中间状态 | 0 0 0 0 0 |
某一元素 | 1 1 1 1 1 |
第一次加入后:
状态 | 集合 |
---|---|
目标状态 | 2 2 2 2 2 |
中间状态 | 1 1 1 1 1 |
某一元素 | 1 1 1 1 1 |
如果再加一次:
状态 | 集合 |
---|---|
目标状态 | 2 2 2 2 2 |
中间状态 | 2 2 2 2 2 |
某一元素 | 1 1 1 1 1 |
这个元素就用了两次。
改了一下,就像这样:
inline int K(int x,int y)
{
int res=0;
for(int i=0;i<10;i++)
res += Pow3[i]*min(2,x/Pow3[i]%3 + y/Pow3[i]%3);
return res;
}
void Solve()
{
int S=0;
for(int i=1;i<=s;i++)
S += Pow3[i-1]*min(2,st[i]);
d[S] = 0;
for(int i=1;i<=n;i++)
for(int S1=Pow3[s]-1;S1>=S;S1--)
if(d[S1] != inf)
d[K(S1,V[i])] = min(d[S1] + a[i],d[K(S1,V[i])]);
ans = d[ Pow3[s]-1 ];
for(int i=1;i<=m;i++)
ans += c[i];
}
我想,我不会不明不白地过一道题,要被安排过得明明白白。
为什么我们一般的二进制状压dp不用倒着枚举?
留给读者思考。。(诶~你们下手轻点,还是偷偷留一个~)
而这道题给我最大的收获,也许远远不止这一点经验,它告诉我,即使再难的题,也怕我们不懈的攻关精神。
在别人将就地抄书时,还有几个人在苦苦地调试自己的想法,在默默中提升自己。
#include
#include
#include
#include
#include
#include
using namespace std;
bool pp;
int geti()
{
int x,f=1;char c; pp=0;
while((c=getchar())<'0' || c>'9')
{
if(c == '-') f=-1;
if(c == -1) return -1;
}
x = c-'0';
while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
if(c == '\n') pp=1;
return x*f;
}
const int M=20,N=100,inf=0x3f3f3f3f;
int Pow3[12];
int s,m,n,ans;
int c[M+5],a[N+5];
int st[12],V[N+5];
int d[7000];
void Prepare()
{
memset(d,0x3f,sizeof(d));
Pow3[0]=1;
for(int i=1;i<=10;i++)
{
Pow3[i] = Pow3[i-1]*3;
st[i] = 0;
}
for(int i=1;i<=N;i++) V[i] = 0;
int k;
m=geti(); n=geti();
for(int i=1;i<=m;i++)
{
c[i]=geti();
while( (k=geti()) )
{
st[k]++;
if(pp) break;
}
}
for(int i=1;i<=n;i++)
{
a[i]=geti();
while( (k=geti()) )
{
V[i] += Pow3[k-1];
if(pp) break;
}
}
for(int i=1;i<=s;i++)
st[i] = min(st[i],2);
}
inline int K(int x,int y)
{
int res=0;
for(int i=0;i<10;i++)
res += Pow3[i]*min(2,x/Pow3[i]%3 + y/Pow3[i]%3);
return res;
}
void Solve()
{
int S=0;
for(int i=1;i<=s;i++)
S += Pow3[i-1]*st[i];
d[S] = 0;
for(int i=1;i<=n;i++)
for(int S1=Pow3[s]-1;S1>=S;S1--)
if(d[S1] != inf)
d[K(S1,V[i])] = min(d[S1] + a[i],d[K(S1,V[i])]);
ans = d[ Pow3[s]-1 ];
for(int i=1;i<=m;i++)
ans += c[i];
}
int main()
{
while( (s=geti()) )
{
Prepare();
Solve();
printf("%d\n",ans);
}
return 0;
}