dp 专题系列(二):LA3983 Robotruck,LA4794 Sharing Chocolate,LA4394 String Painter,LA4327 Parade,Uva 10817

      这几天做的多校联合状态很不好,而且多校的题目数据也略坑爹(居然赛后知道有输出文件为空的数据。。。。)。。。

   说说这几天做的dp题目吧。。。

   dp感觉是越来越切不动了,各种状态不会想。。。

  (一):Live Archive 3983

   先说说这个Robotruck这题吧,题目还是很好的,状态还是比较好想的(但我就是没转移出来。。蒟蒻啊)

  状态:dp[i] 表示第i个物品输送完毕并且回到了起点

    Dis(i,j) 表示从起点出发送出从j+1到i这些物品并回到原点的时间

   转移方程: dp[i]=dp[j]+Dis(j+1,i),其中 Weight[i]-Weight[j]<=C

    根据这个方程,我们能得到一个O(n*C)复杂度的算法

    核心代码如下:

for(int i=1;i<=n;++i){
			int cur=w[i],tot=Dis(P[i].x,P[i].y,0,0);
			for(int j=i-1;cur<=C&&j>=0;--j){
				d[i]=min(d[i],d[j]+tot+Dis(P[j+1].x,P[j+1].y,0,0));
				cur+=w[j];
				tot+=Dis(P[j].x,P[j].y,P[j+1].x,P[j+1].y);
			}
//			cout<<"d["<<i<<"] "<<d[i]<<endl;
		}


    但是我们可以把这个dp方程改写一下:

    dp[i]=dp[j]+Cal(i)-Cal(j)+Distance(i)+Distance(j)

    其中Cal(i)表示从起点按顺序走到达i点的总距离,Distance(i)表示起点到i节点的距离

     这样我们就发现可以用单调队列优化了,由于dp[j]-cal(j)+Distance(j)是变量,我们把它放入队列,每次维护一个最小值的队列即可,这样时间复杂度可以达到O(n),优化之后的程序rank直接跑到前五了

    全部代码:

#define LIM 100010
int w[LIM];
int re[LIM];
struct qnode {
	int index, value;
};
struct MyQueue {
	int head, tail;
	qnode q[LIM];
	void init() {
		head = tail = 0;
	}
	void pushmin(int x, int y) {
		while (tail > head && q[tail - 1].value > y) {
			tail--;
		}
		q[tail].index = x;
		q[tail++].value = y;
	}
	void pop(int id,int cap) {
		while ( head < tail && ( w[id] - w[q[head].index] + re[q[head].index] > cap )) {
				head++;
		}
	}
	int Head() {
		return q[head].value;
	}
};

MyQueue qmin;
struct node{
	int x,y;
}P[LIM];
int d[LIM];
int Sum[LIM];
int Dis(int x1,int y1,int x2,int y2){
	return Abs(x1-x2)+Abs(y1-y2);
}
int main()
{
	int T;
	scanf("%d",&T);
	int Cnt=1;
	while( T-- ){
		int n,C;
		scanf("%d%d",&C,&n);
		int x,y,z;
		d[0]=0;w[0]=0;P[0].x=0;P[0].y=0;re[0]=0;
		Sum[0]=0;
		for(int i=1;i<=n;++i){
			scanf("%d%d%d",&x,&y,&z);
			P[i].x=x;P[i].y=y;
			re[i]=z;
			w[i]=w[i-1]+z;
			Sum[i]=Sum[i-1]+Dis(P[i-1].x,P[i-1].y,x,y);
		}
		qmin.init();
		for(int i=1;i<=n;++i){
			int tot=Dis(P[i].x,P[i].y,0,0);
			qmin.pushmin(i,d[i-1]-Sum[i]+tot);
			qmin.pop(i,C);
			int t=qmin.Head();
			d[i]=t+Sum[i]+tot;
//			cout<<"d["<<i<<"] "<<d[i]<<endl;
		}
//		printf("Case %d: %d\n",Cnt,d[n]);
		if( Cnt!=1 ) puts("");
		printf("%d\n",d[n]);
		Cnt++;
	}
	return 0;
}


(二):Live Archive4794 Sharing Chocolate

   这道题是哈尔滨world  final 题(好像是当时最水的一道题)

   其实看到n<=15时,我们就可以想到状态压缩了,状态比较麻烦,我为了降维,状态

为dp[state][max(width,height)],state为是选了将要分的巧克力的压缩状态,后一维为高与宽的最大值,由于前面状态已经能表示巧克力小块的总数,所以这个状态可以唯一确定一个巧克力的状态。这个状态表示此巧克力能否分下去

   状态转移:dp[cur][w]=(dp[sub_1][w]&&dp[cur-sub_1][w])||(dp[sub_1][h]&&dp[cur-sub_1][h]) 

  其中h能由w算得,sub_1表示能切的一个子状态,cur-sub_1表示切了之后剩下的一个子状态。

  具体代码:

 

#define LIM 32780
#define MAXN 110
int d[LIM][MAXN];
int val[20];
int Sum[LIM];
int n;
void init(int tot,int width){
	memset(d,-1,sizeof(d));
}
int solve(int state,int width){
	if( state==0 ) return 0;
	if( d[state][width] != -1 ){
		return d[state][width];
	}
	if( (state&(state-1)) == 0 ) return d[state][width]=1;
	int sub=state&(state-1);
	int hei=Sum[state]/width;
	while( sub > 0 ){
		int cur=(state^sub);
		int ss=Sum[sub],sc=Sum[cur];
		if( ss%width==0 && sc%width==0 ){
			if( solve(sub,max(width,ss/width)) && solve(cur,max(width,sc/width)) ){
				return d[state][width]=1;
			}
		}
		if( ss%hei==0 && sc%hei==0 ){
			if( solve(sub,max(hei,ss/hei)) && solve(cur,max(hei,sc/hei)) ){
				return d[state][width]=1;
			}
		}
		sub=state&(sub-1);
	}
	return d[state][width]=0;
}
int main()
{
	int Cnt=1;
	while( cin>>n ){
		if( n==0 ) break;
		int width,height;
		cin>>width>>height;
		int S=0;
		for(int i=0;i<n;++i){
			cin>>val[i];
			S+=val[i];
		}
		cout<<"Case "<<Cnt++<<": ";
		if( S != width*height ){
			cout<<"No"<<endl;
			continue;
		}
		int tot=(1<<n)-1;
		init(tot,max(width,height));
		for(int i=0;i<=tot;++i){
			Sum[i]=0;
			for(int j=0;j<n;++j){
				if( i&(1<<j) ){
					Sum[i]+=val[j];
				}
			}
		}
		if( solve(tot,max(width,height)) ){
			cout<<"Yes"<<endl;
		}
		else{
			cout<<"No"<<endl;
		}
	}
	return 0;
}


(三):Live Archive 4394    String Painter

08年成都现场赛题目,开始想了好久,不太懂,其实一开始就想出了状态,但是在转移

上就卡死了,而且涉及两个串,就有点搞不清了,后来又看了大牛博客,开始懂了

首先应该对目标态进行分析,得出一些结论

具体见:http://hi.baidu.com/angellunamaria/blog/item/bef2ee34eb2a6146241f1472.html


#define LIM 110
string s1,s2;
int dp[LIM][LIM][30];
int f[LIM];
int solve(int s,int e,int col){
//	cout<<endl<<"first "<<endl;
//	cout<<"s "<<s<<" e "<<e<<" col "<<col<<endl;
	if( s > e ) return 0;
	if(dp[s][e][col]!=-1){
		return dp[s][e][col];
	}
	int &ans=dp[s][e][col];
	ans=INF;
	if( col == 0 ){
		for(int i=s;i<=e;++i){
			if( s2[i] == s2[s] ){
				ans=min(ans,solve(s,i,s2[s]-'a'+1)+1+solve(i+1,e,col));
			}
		}
	}
	else{
		int l=s,r=e;
//		if( s==4 && e==6 )
//		cout<<"l "<<l<<" r "<<r<<endl;
		bool flagl=false,flagr=false;
		while( l <= e ){
			if( s2[l] == col-1+'a' ){
				l++;
			}
			else{
				flagl=true;
			}
			if( s2[r] == col-1+'a' ){
				r--;
			}
			else{
				flagr=true;
			}
			if( flagl && flagr ){
				break;
			}
		}
//		if( s==4 && e==6)
//		cout<<"l "<<l<<" r "<<r<<endl;
		if( l > e ){
			ans=0;
		}
		else{
			for(int i=l;i<=r;++i){
				if( s2[i] == s2[l] )
				ans=min(ans,solve(l,i,s2[l]-'a'+1)+1+solve(i+1,r,col));
			}
		}
	}
//	cout<<"s "<<s<<" e "<<e<<" col "<<col<<endl;
//	cout<<"ans "<<ans<<endl;
	return ans;
}
int main()
{
    while( cin>>s1>>s2 ){
    	int len=s1.size();
    	memset(dp,-1,sizeof(dp));
//    	cout<<solve(0,len-1,0)<<endl;
    	for(int i=0;i<len;++i){
    		for(int j=len-1;j>=i;--j){
    			solve(i,j,0);
//    			cout<<"dp["<<i<<"]["<<j<<"][0] "<<dp[i][j][0]<<endl;
    		}
    	}
    	for(int i=0;i<len;++i){
    		if( s1[i] == s2[i] ){
    			if( i==0 ) f[i]=0;
    			else f[i]=f[i-1];
    		}
    		else{
    			if( i==0 ) f[i]=1;
    			else{
    				f[i]=dp[0][i][0];
    				for(int j=0;j<i;++j){
    					f[i]=min(f[i],f[j]+dp[j+1][i][0]);
    				}
    			}
    		}
//    		cout<<"f["<<i<<"] "<<f[i]<<endl;
    	}
    	cout<<f[len-1]<<endl;
    }
    return 0;
}


(四):Live Archive 4327    Parade

这好像是08年北京现场赛题,其实状态不是很难想,很难搞定的是优化状态转移

dp[i][j]表示在第i第j个位置最大可能值为多少

状态方程dp[i[j]=dp[i+1][t]+Dis(j,t) (其中Len(j,t)<=K)

这个题的状态方程我们发现其实跟刚才讲的Robotruck这题差不多,这提示我们又可以用单调队列优化

分两个方向优化,从左到右与从右到左

#define LIM 10010
int Suml[LIM],Sump[LIM];
int N,M,K;
struct qnode {
	int index, value;
};
struct MyQueue {
	int head, tail;
	qnode q[LIM];
	void init() {
		head = tail = 0;
	}
	void pushmax(int x, int y) {
		while (tail > head && q[tail - 1].value < y) {
			tail--;
		}
		q[tail].index = x;
		q[tail++].value = y;
	}
	void pop(int u) {
		while (head < tail && (Suml[u]-Suml[q[head].index] > K) ) {
			head++;
		}
	}
	int Head() {
		return q[head].value;
	}
};

MyQueue  qmax;
#define MAXN 110
int a[MAXN][LIM];
int b[MAXN][LIM];
int c[LIM],d[LIM];
//int q[LIM];
void solve(int u){
	for(int i=0;i<=M;++i){
		d[i]=-INF;
	}
	Suml[0]=0;Sump[0]=0;
	for(int i=1;i<=M;++i){
		Suml[i]=Suml[i-1]+b[u][i];
		Sump[i]=Sump[i-1]+a[u][i];
	}
	qmax.init();
	for(int i=0;i<=M;++i){
		qmax.pushmax(i,c[i]-Sump[i]);
		qmax.pop(i);
		int t=qmax.Head();
		d[i]=max(d[i],Sump[i]+t);
	}
	qmax.init();
	Suml[M]=0;Sump[M]=0;
	for(int i=M-1;i>=0;--i){
		Suml[i]=Suml[i+1]+b[u][i+1];
		Sump[i]=Sump[i+1]+a[u][i+1];
	}
	for(int i=M;i>=0;--i){
		qmax.pushmax(i,c[i]-Sump[i]);
		qmax.pop(i);
		int t=qmax.Head();
		d[i]=max(d[i],Sump[i]+t);
	}
	for(int i=0;i<=M;++i){
		c[i]=d[i];
	}
}
int main()
{
	while( scanf("%d%d%d",&N,&M,&K) ){
		if( N==0 && M==0 && K==0 ) break;
		N++;
		for(int i=1;i<=N;++i){
			for(int j=1;j<=M;++j){
//				cin>>a[i][j];
				scanf("%d",&a[i][j]);
			}
		}
		for(int i=1;i<=N;++i){
			for(int j=1;j<=M;++j){
//				cin>>b[i][j];
				scanf("%d",&b[i][j]);
			}
		}
		memset(c,0,sizeof(c));
		for(int i=N;i>=1;--i){
			solve(i);
		}
		int ans=0;
		for(int i=0;i<=M;++i) ans=max(ans,c[i]);
//		cout<<ans<<endl;
		printf("%d\n",ans);
	}
	return 0;
}

(五)UVa10817    Headmaster’s Headache

一个三进制状态压缩的dp题+01背包

其他不想说了,主要注意三进制状压的方法


#define LIM 66000
#define MAXN 110
int S,M,N;
int d[LIM];
int state[MAXN][10];
int w[MAXN];
int p[10];
int num[10];
int Target;
void pre(){
	p[0]=1;
	for(int i=1;i<=9;++i){
		p[i]=p[i-1]*3;
	}
}
void init(){
	Target=0;
	memset(state,0,sizeof(state));
	int tot=p[S]-1;
	for(int i=0;i<=tot;++i){
		d[i]=INF;
	}
}
void Change(int x){
	int t=Target/p[x]%3;
	if( t<2 ){
		Target+=p[x];
	}
}
void Change(int u,int x){
	state[u][x]++;
	if( state[u][x]>2 ){
		state[u][x]=2;
	}
}
void GetSub(int cost){
	int tot=p[S]-1;
	for(int i=0;i<S;++i){
		num[i]=Target/p[i]%3;
	}
	for(int i=0;i<=tot;++i){
		bool flag=true;
		for(int j=0;j<S;++j){
			int t=i/p[j]%3;
			if( t > num[j] ){
				flag=false;
				break;
			}
		}
		if( flag ){
			d[i]=cost;
		}
	}
}
void solve(){
	int tot=p[S]-1;
	for(int i=0;i<N;++i){
		for(int j=tot;j>=0;--j){
			int tmp=0;
			for(int k=0;k<S;++k){
				int t=j/p[k]%3;
				t=max(t-state[i][k],0);
				tmp+=p[k]*t;
			}
			d[j]=min(d[j],d[tmp]+w[i]);
		}
	}
	printf("%d\n",d[tot]);
}

int main()
{
	char Str[100];
	pre();
    while( scanf("%d%d%d",&S,&M,&N)!=EOF ){
    	if( N==0 && M==0 && S==0 ) break;
    	int x;
    	int ans=0;
    	init();
    	for(int i=0;i<M;++i){
    		scanf("%d",&x);
    		ans+=x;
    		gets(Str);
    		int len=strlen(Str);
    		int pos=0;
    		while( (++pos) < len ){
    			sscanf(Str+pos,"%d",&x);
    			--x;
    			Change(x);
    			for(; pos<len && isdigit(Str[pos]);pos++)
    				;
    		}
    	}
    	for(int i=0;i<N;++i){
    		scanf("%d",&w[i]);
    		gets(Str);
    		int len=strlen(Str);
    		int pos=0;
			while ((++pos) < len) {
				sscanf(Str + pos, "%d", &x);
				--x;
				Change(i,x);
				for (; pos < len && isdigit(Str[pos]); pos++)
					;
			}
    	}
    	GetSub(ans);
    	solve();
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值