之所以再次写起了博客,是因为我感觉自己的懒惰性太强了,集训队里很多队员刷题都是过几百的,而自己偶然看了下自己在hdu只做了50多道,poj只有40多,更不要说zju,spoj神马的呢,做题数应该是个位数。。。。
今天决定得以后每天得刷一定数量的题,但是刷题没人督促怎么行,于是博客充当了这个角色,每天花个时间写写博客,督促和见证自己的成长吧。。。
我决定从我最薄弱的dp刷起。。。
说说今天做的几道题吧。。。
(一):Uva LiveArchive3882
(http://livearchive.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=21page=show_problem&problem=1883)
题目大意:约瑟夫环问题,问最后一个出队的人。
题解: 经典题目。由于存在子问题,考虑递推,有如下公式(编号为0-n-1,从第一个人数起):
f[0]=0;
f[i]=(f[i-1]+k)%i;
题目是编号从1开始,并且开始是让编号为m的出队。
只要稍微改一下最后结果即可。
while( cin>>n>>k>>m ){
if( n==0 && k==0 && m==0 ) break;
int cur=0;
for(int i=2;i<n;++i){
cur=(cur+k)%i;
cout<<"i "<<i<<" cur "<<cur<<endl;
}
cout<<(cur+m)%n+1<<endl;
}
(二):UVa 10635
题目大意:求 两个序列的lcs,(其中每个序列里的数是各不相同的,序列长度达到10^5)
题解:将 lcs 化成 LIS 即可,方法是拿前一个序列的数映射一下即可
LIS用O(n log(n) )的搞即可
关键代码:
for(size_t i=0;i<V.size();++i){
g.push_back(INF);
}
int ans=0;
for(size_t i=0;i<V.size();++i){
int p=lower_bound(g.begin(),g.end(),V[i])-g.begin();
if( p > ans ){ ans=p; }
g[p]=V[i];
}
cout<<"Case "<<Cnt++<<": "<<ans+1<<endl;
(三):UVa 11825
状态压缩dp(枚举子集)
题意很费解,大意是有N台电脑,每台电脑有N(N<=16)个服务,一个黑客可以在每台电脑上跑一个(只能跑一个)让一种服务崩溃的病毒,并且这个病毒可以传播到跟他邻接的顶点,问最多这个黑客可以让多少个服务崩溃?(每台电脑上都是N个不同服务,每台电脑上跑的都是一样的服务,让一个服务崩溃是让所有的电脑上的这个服务崩溃)
当时就想到状态压缩,但是一直没想到好的状态,因为影响因素很多,比如说N台电脑,N个服务,到底是以哪个为状态,后来还是忍不住搜题解,看了别人的状态之后顿悟。。。
设dp[state] 为已经在t台电脑上跑病毒的最大让服务崩溃数(state 就是 t的压缩状态,0表示没选,1表示选择了)
状态转移方程:dp[state]=max(dp[state-substate])+1 substate 是state的子状态,对substate的约束条件是必须有:
状态substate能传播到N台电脑
剩下的只有敲代码了:
bool judge(int cur){
int ans=cur;
int target=((1<<N)-1);
for (int i = 0; i < N; ++i) {
if (cur & (1 << i)) {
for (int t = Head[i]; t != -1; t = e[t].next) {
int v = e[t].v;
ans |= (1 << v);
}
}
}
return ans==target;
}
int tot=(1<<N);
for(int i=0;i<tot;++i){
cover[i]=judge(i);
}
solve(tot-1);
cout<<"Case "<<Cnt++<<": "<<dp[tot-1]<<endl;
int solve(int cur){
if( cur == 0 ) return 0;
if( dp[cur] != -1 ){
return dp[cur];
}
dp[cur]=0;
// 枚举cur所在状态的子集(经典方法)
int sub=cur;
// 当sub减为0 时; 再为-1时,(-1)&anynumber=anynumber; 故跳出循环
do{
// cur^sub: 由于 sub 是cur的子集,即等于 cur-sub
if( cover[sub] ) dp[cur]=max(dp[cur],solve(cur^sub)+1);
// 不断向下枚举
sub=((sub-1)&cur);
}while( sub != cur );
return dp[cur];
}
(四):UVa 10859
题意: 简单树形dp题
记录两个状态 dp[r][0] 表示 不选当前节点
dp[r][1]表示 选择当前节点
还有由于要维护是否看住了两条路,定义两个状态
cnt[r][0] 表示 不选当前节点时 看住两条路的最大值
cnt[r][1] 表示 选择当前节点 看住两条路的最大值
转移方程很简单:
dp[r][0]=S(dp[child_i][1]) (S 为求和)
dp[r][1]=S(min(dp[child_i][0],dp[child_i][1]))
cnt的转移方程很类似,就是要注意相等的时候处理一下即可
还有注意可能题目会给个森林给你,注意处理一下即可。
void dfs(int u){
vis[u]=true;
d[u][0]=0;d[u][1]=1;cnt[u][0]=cnt[u][1]=0;
for(int t=Head[u]; t!=-1 ; t=e[t].next ){
int v=e[t].v;
if( !vis[v] ){
dfs(v);
d[u][0]+=d[v][1];
cnt[u][0]+=cnt[v][1];
if( d[v][1] == d[v][0] ){
d[u][1]+=d[v][1];
cnt[u][1]+=max(cnt[v][1]+1,cnt[v][0]);
}
else if( d[v][1] < d[v][0] ){
d[u][1]+=d[v][1];
cnt[u][1]+=cnt[v][1]+1;
}
else{
d[u][1]+=d[v][0];
cnt[u][1]+=cnt[v][0];
}
}
}
}
很值得一提的是,这道题卡了我两个小时以上,原因是我写memset(vis,false,sizeof(vis))时,不小心写成了
memset(vis,false,sizeof(false))
坑爹是样例都过了,而且我一直只测一组数据,毫无疑问,初始化的问题,一组数据时怎么都测不出来的,后来偶然间用了两个用例,才看出的bug。。。。
各种坑爹错误你伤不起啊。。。。
Ok,明天继续
Ps:明天集训队内数学专题比赛(各种伤不起啊,求rp。。。)
CSDN 代码排版的各种伤不起(太烂了!!!!),如果有时间会考虑换一个博客,这CSDN博客太扯淡了。。。