确 实 值 得 一 做 。 确实值得一做。 确实值得一做。

先 说 爆 搜 的 方 法 ( 剪 枝 ) 先说爆搜的方法(剪枝) 先说爆搜的方法(剪枝)

因 为 n 和 k 最 大 有 n k = 40000 种 状 态 因为n和k最大有nk=40000种状态 因为n和k最大有nk=40000种状态

d f s 的 话 完 全 可 以 把 某 种 状 态 是 否 可 行 记 录 在 数 组 dfs的话完全可以把某种状态是否可行记录在数组 dfs的话完全可以把某种状态是否可行记录在数组

然 后 d f s 到 某 个 显 像 管 时 , 看 看 剩 下 的 次 数 是 否 可 行 然后dfs到某个显像管时,看看剩下的次数是否可行 然后dfs到某个显像管时,看看剩下的次数是否可行

因 为 我 们 可 以 提 前 预 处 理 这 个 显 像 管 后 的 花 费 最 小 次 数 和 花 费 最 大 次 数 因为我们可以提前预处理这个显像管后的花费最小次数和花费最大次数 因为我们可以提前预处理这个显像管后的花费最小次数和花费最大次数

跑 了 63 m s 跑了63ms 跑了63ms

#include <bits/stdc++.h>
using namespace std;
const int maxn=2009;
int n,k;
string s;
int qmax[maxn],qmin[maxn];
int flag=0,val[maxn],vis[maxn][10],maxx=0,minn=1e9;
string a[11]={ "1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011"};
int work(string s,string w)
{
	int ans=0;
	for(int i=0;i<7;i++)
	if(w[i]=='0'&&s[i]=='1')	return -1;
	else if(w[i]!=s[i])	ans++;
	return ans;
}
int ans[maxn],dp[maxn][maxn];
bool conflag=false;
void dfs(int now,int hua)
{
	if(now==n+1)
	{
		if(hua==k)
			conflag=true;
		return;
	}
	if(conflag)	return;
	if(dp[now][hua]==-1)	return;
	for(int i=9;i>=0;i--)
	{
		if(vis[now][i]==-1)	continue;
		if(hua+vis[now][i]>k)	continue;
		int remain=(k-hua-vis[now][i]);
		if(remain>qmax[now+1])	continue;
		if(remain<qmin[now+1])	continue;
		ans[now]=i;
		dfs(now+1,hua+vis[now][i]);
		if(conflag)	return;
		dp[now][hua]=-1;//不能成功,说明失败 
	}
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>s;
		int ok=0;
		for(int j=0;j<=9;j++)
		{
			vis[i][j]=work(s,a[j]);
			if(vis[i][j]!=-1)	ok=1;
		}
		if(ok==0)	flag=1;
	}
	if(flag)	{cout<<-1;return 0;}
	for(int i=n;i>=1;i--)
	{
		maxx=0,minn=1e9;
		for(int j=0;j<=9;j++)
		{
			if(vis[i][j]==-1)	continue;
			maxx=max(maxx,vis[i][j]);
			minn=min(minn,vis[i][j]);
		}
		qmax[i]=qmax[i+1]+maxx;
		qmin[i]=qmin[i+1]+minn;
	}
	dfs(1,0);
	if(!conflag){cout<<-1;return 0;}
	for(int i=1;i<=n;i++)	cout<<ans[i];
}

首 先 可 以 预 处 理 每 个 显 示 屏 到 数 字 [ 0 , 9 ] 花 费 的 次 数 首先可以预处理每个显示屏到数字[0,9]花费的次数 首先可以预处理每个显示屏到数字[0,9]花费的次数

然 后 有 一 个 显 然 的 D P 然后有一个显然的DP 然后有一个显然的DP

处 理 到 i 显 示 屏 花 费 j 次 数 的 最 大 收 益 处理到i显示屏花费j次数的最大收益 处理到i显示屏花费j次数的最大收益

数 字 太 大 用 i n t 会 爆 用 s t r i n g 会 T 数字太大用int会爆用string会T 数字太大用int会爆用string会T

怎 么 办 ? 考 虑 从 后 往 前 D P \color{Red}{怎么办?考虑从后往前DP} 怎么办?考虑从后往前DP

我 们 D P 的 是 处 理 到 i 显 示 屏 时 花 费 j 次 数 能 否 形 成 数 字 我们DP的是处理到i显示屏时花费j次数能否形成数字 我们DP的是处理到i显示屏时花费j次数能否形成数字

这 样 一 来 从 前 往 后 每 次 都 从 9 往 1 选 这样一来从前往后每次都从9往1选 这样一来从前往后每次都从9往1选

只 需 要 看 一 下 当 前 次 数 减 去 消 耗 次 数 能 否 形 成 数 字 就 行 了 只需要看一下当前次数减去消耗次数能否形成数字就行了 只需要看一下当前次数减去消耗次数能否形成数字就行了

#include <bits/stdc++.h>
using namespace std;
const int maxn=2009;
string s[101]={"1110111", "0010010", "1011101", "1011011", "0111010", "1101011", "1101111", "1010010", "1111111", "1111011"};
string in;
int n,K,val[maxn],dval[maxn];
bool f[maxn][maxn];
int main()
{
	cin>>n>>K;
	for(int i=0;i<=9;i++)
	for(int j=0;j<7;j++)
		if(s[i][j]-'0')	val[i]|=(1<<(7-j-1));
	for(int i=1;i<=n;i++)
	{
		cin>>in;
		for(int j=0;j<7;j++)
			if(in[j]-'0')	dval[i]|=(1<<(7-j-1));
	}
	f[n+1][0]=1;
	for(int i=n;i>=1;i--)
	for(int j=0;j<=9;j++)
	{
		if((val[j]&dval[i])==dval[i])
		{
			int w=(val[j]^dval[i]),num=0;
			for(int k=0;k<7;k++)
				if(w&(1<<k))	num++;
			for(int k=num;k<=K;k++)
				f[i][k]|=f[i+1][k-num];
		}
	}
	if(!f[1][K]){
		cout<<-1;
		return 0;
	}
	for(int i=1;i<=n;i++)
	for(int j=9;j>=0;j--)
	{
		if((val[j]&dval[i])==dval[i])
		{
			int w=(val[j]^dval[i]),num=0;
			for(int k=0;k<7;k++)
				if(w&(1<<k))	num++;
			if(f[i+1][K-num])
			{
				cout<<j;
				K-=num;
				break;
			}
		}
	}
	return 0;
}