省选专练【SCOI2013】火柴棍数字

本文详细解析了01可达性分组背包问题,包括贪心算法的应用、记忆化搜索实现及路径查询等核心内容。通过具体实例介绍了如何处理不同情况下的背包问题,并给出了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一贪心。

引理:

对于可以取出的数字要让可达性最大。

在队首填1.

如果可达性为奇数?

那么队首是7别的是1.

坑点。

对于每个数转换为别的数,其本质是01可达性分组背包。

组:组数为n

每个组有严苛的10个物件。

我们需要维护一个常量数组

int trans[10][10]={
	0, 4, 2, 2, 3, 2, 1, 3, 0, 1,
	0, 0, 2, 0, 0, 1, 1, 0, 0, 0,
	1, 4, 0, 1, 3, 2, 1, 3, 0, 1,
	1, 3, 1, 0, 2, 1, 1, 2, 0, 0,
	1, 2, 2, 1, 0, 1, 1, 2, 0, 0,
	1, 4, 2, 1, 2, 0, 0, 3, 0, 0,
	1, 5, 2, 2, 3, 1, 0, 4, 0, 1,
	0, 1, 1, 0, 1, 1, 1, 0, 0, 0,
	1, 5, 2, 2, 3, 2, 1, 4, 0, 1,
	1, 4, 2, 1, 2, 1, 1, 3, 0, 0
};//两两变幻代价数组。
//代价出现的必要条件:A不纯包含于B 如8->1代价为0 而 1->8 代价 为5 

由于我们需要跑一个分组背包。

这个时候写法就多了。

但是我太弱了,对于Claris大神的强行写标准分组背包望其项背。

我们写记忆化搜索

int dp[500+10][7000+10];//dp(i,j)表示从低到高位的i位要在n内使用j(既获得,取出)的火柴棍(以摆放高位)放进分组背包内
//理由:常量数组num并不能满足->腾出摆放sigma 1->n 以下所有的木棍。 
bool vis[500+10][7000+10];//记忆化 

//搜索原理:
//对于n->以下的数
//摆放的代价是由 分组背包来求
//对于n以上的->它们只会是1或7 
int dfs(int pos,int remain){
	if(remain>k)
		return INF;
	//当前可行性剪枝不存在这种情况 
	if(pos==0){//搜道0了还有摆不完的说明还是不存在这种01可达性分组背包。 
		return remain==0?0:INF;
	}
	else{
		if(vis[pos][remain])
			return dp[pos][remain];//既然搜过了就记住了。 
		vis[pos][remain]=true;//已搜 
		dp[pos][remain]=INF;//或许这种情况不存在可达性。 
		for(int i=9;i>=0;i--){//从当前位向下转换,既我要把当前位变成这个。必须for 9->1要在结果最优时输出最大的 
			int nowx=dfs(pos-1,remain+num[i]-num[s[pos]])+trans[s[pos]][i];
			dp[pos][remain]=min(dp[pos][remain],nowx);
		}
		return dp[pos][remain];//返回答案。 
	}
}

然后查询一个最大当前可剥离值。

然后贪心输出前面的答案。

	for(int i=k;i>1;i--){
		if(dfs(n,i)<=k){//这里实际上是从最高位做起。 
			int remain=i;
			if(remain&1){//贪心思想。 
				printf("7");
				remain-=3;
			}
			while(remain){
				printf("1");
				remain-=2;
			}
			dfs_print(n,i);
			return 0;
		}
	}

是的可达性背包问题是没有记录路径的于是又一个坑点来了。

查询路径。

第一,可达性可以有多种,所以必须从9查到0.

第二,我们用贪心来查。

void dfs_print(int pos,int remain){//事实上最开始只是做了一个01可达性分组背包。dfs循环找出实际结果。 
	while(pos){//例行我们从最高位做起递归打印。 
		for(int i=9;i>=0;i--){//在同样的可达性下是不是使用最高位更大更好? 
			if(dfs(pos-1,remain+num[i]-num[s[pos]])<=k-trans[s[pos]][i]){//k-trans[s[pos][i]]表示:当前位使用辣么多木棍,注意不是释放,或许还需要拆东墙补西墙。 
				//dfs查询的就是还需要多少根,若果小于直接贪心,因为你从大往小搜。这也是为什么reverse的原因。 
				printf("%d",i);
				remain=remain+num[i]-num[s[pos]];
				k-=trans[s[pos]][i];
				pos--;
				break;
			}
		}
	}
}

于是乎就完了。



好的你也就完了。

考虑这个特殊的一批的数据:

3 1

干不了什么事,也做不了可达性背包。

于是查到答案结束代码

否则在最后输出原数。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
int num[10]={6,2,5,5,4,5,6,3,7,6};//常量数组。意义是->每个火柴棍最多可取多少。 

int trans[10][10]={
	0, 4, 2, 2, 3, 2, 1, 3, 0, 1,
	0, 0, 2, 0, 0, 1, 1, 0, 0, 0,
	1, 4, 0, 1, 3, 2, 1, 3, 0, 1,
	1, 3, 1, 0, 2, 1, 1, 2, 0, 0,
	1, 2, 2, 1, 0, 1, 1, 2, 0, 0,
	1, 4, 2, 1, 2, 0, 0, 3, 0, 0,
	1, 5, 2, 2, 3, 1, 0, 4, 0, 1,
	0, 1, 1, 0, 1, 1, 1, 0, 0, 0,
	1, 5, 2, 2, 3, 2, 1, 4, 0, 1,
	1, 4, 2, 1, 2, 1, 1, 3, 0, 0
};//两两变幻代价数组。
//代价出现的必要条件:A不纯包含于B 如8->1代价为0 而 1->8 代价 为5 

char ch[5000];//你只能读一个字符串是吧。 
int s[5000];//然后从低位到高位存储。
//原理:低位末尾不能添数 

int n,k;

const int maxn=4000;//没有太大特殊意义就是必须得大于k 既3500 
const int INF=maxn+1;//同上 

int dp[500+10][7000+10];//dp(i,j)表示从低到高位的i位要在n内使用j(既获得,取出)的火柴棍(以摆放高位)放进分组背包内
//理由:常量数组num并不能满足->腾出摆放sigma 1->n 以下所有的木棍。 
bool vis[500+10][7000+10];//记忆化 

//搜索原理:
//对于n->以下的数
//摆放的代价是由 分组背包来求
//对于n以上的->它们只会是1或7 
int dfs(int pos,int remain){
	if(remain>k)
		return INF;
	//当前可行性剪枝不存在这种情况 
	if(pos==0){//搜道0了还有摆不完的说明还是不存在这种01可达性分组背包。 
		return remain==0?0:INF;
	}
	else{
		if(vis[pos][remain])
			return dp[pos][remain];//既然搜过了就记住了。 
		vis[pos][remain]=true;//已搜 
		dp[pos][remain]=INF;//或许这种情况不存在可达性。 
		for(int i=9;i>=0;i--){//从当前位向下转换,既我要把当前位变成这个。必须for 9->1要在结果最优时输出最大的 
			int nowx=dfs(pos-1,remain+num[i]-num[s[pos]])+trans[s[pos]][i];
			dp[pos][remain]=min(dp[pos][remain],nowx);
		}
		return dp[pos][remain];//返回答案。 
	}
}
void dfs_print(int pos,int remain){//事实上最开始只是做了一个01可达性分组背包。dfs循环找出实际结果。 
	while(pos){//例行我们从最高位做起递归打印。 
		for(int i=9;i>=0;i--){//在同样的可达性下是不是使用最高位更大更好? 
			if(dfs(pos-1,remain+num[i]-num[s[pos]])<=k-trans[s[pos]][i]){//k-trans[s[pos][i]]表示:当前位使用辣么多木棍,注意不是释放,或许还需要拆东墙补西墙。 
				//dfs查询的就是还需要多少根,若果小于直接贪心,因为你从大往小搜。这也是为什么reverse的原因。 
				printf("%d",i);
				remain=remain+num[i]-num[s[pos]];
				k-=trans[s[pos]][i];
				pos--;
				break;
			}
		}
	}
}
int main(){
	scanf("%s%d",ch,&k);
	n=strlen(ch);
	for(int i=1;i<=n;i++)
		s[i]=ch[n-i]-'0';//类似于一个reverse操作。 
	for(int i=k;i>1;i--){
		if(dfs(n,i)<=k){//这里实际上是从最高位做起。 
			int remain=i;
			if(remain&1){//贪心思想。 
				printf("7");
				remain-=3;
			}
			while(remain){
				printf("1");
				remain-=2;
			}
			dfs_print(n,i);
			return 0;
		}
	}
	dfs_print(n,0);//什么?为什么要打?呵呵K=1你做的了01可达性分组背包? 
}


超级详细的

(*^▽^*)!!(*╹▽╹*)给个赞再走?

(๑╹ヮ╹๑)ノ Studying makes me happy


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值