dp 专题系列(一)(UVa 10635,UVa 11825,UVa 10859,Uva LiveArchive 3882 )

    之所以再次写起了博客,是因为我感觉自己的懒惰性太强了,集训队里很多队员刷题都是过几百的,而自己偶然看了下自己在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博客太扯淡了。。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值