【刷题】【dp】【贪心】D. Nastya and Scoreboard
题意:(来自谷歌翻译)
丹尼斯买了鲜花和糖果后(你将在下一个任务中了解这个故事),去和娜斯佳约会,要求她成为一对。现在,他们坐在咖啡馆里,最后……丹尼斯要求她在一起,但是……娜斯佳没有给出任何回答。
可怜的男孩因此非常沮丧。他很伤心,以至于他用数字打了某种记分牌。数字显示方式与电子钟相同:每个数字位置由 7 段组成,可以打开或关闭以显示不同的数字。图片显示了所有 10 个十进制数字是如何显示的:
打孔后,一些段停止工作,即如果它们更早发光,则某些段可能会停止发光。但丹尼斯记得有多少根棍子在发光,现在又有多少根在发光。丹尼斯准确地打破了 k段,他知道现在哪些棍子正在工作。
Denis 提出了一个问题:如果您恰好打开 k 棒(现在已关闭),棋盘上可能出现的最大数量是多少? 允许数字包括前导零。
输入:
第一行包含整数 n ( 1 ≤ n ≤ 2000 ) — 记分板上的位数和 k ( 0 ≤ k ≤ 2000 ) — 停止工作的段数。
接下来的 n 行包含一个长度为 7 的二进制字符串,其中第 i 编码记分牌的第 i 数字。 记分牌上的每个数字由 7 段组成。我们对它们进行编号,如下图所示,如果第 i 根棍子不发光,则二进制字符串的第 i 位为 0 00,如果它发光,则为 1 。然后一个长度为 7 的二进制字符串将指定现在哪些段正在发光。
因此,序列“1110111”、“0010010”、“1011101”、“1011011”、“0111010”、“1101011”、“1101111”、“1010010”、“1111111”、“1111011”从 0 开始依次编码所有数字到 9 包括在内。 输出 输出一个由 n 位组成的单个数字 -
例子:
输入
1 7
0000000
输出
8
输入
2 5
0010010 0010010
输出
97
输入
3 5
0100001
1001001
1010011
输出
-1
解题思路:
显然可以设计状态 f[i][j] 表示处理完前i个,花费j,得到的最大数字,
记得初始化,则得到 f[n][k] 就是恰好花费k得到的最大数字串
然后,我就从wa变成了mle,
压缩状态到 f[2][j] 还在mle
代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; //0-9对应的01字符串,10进制表示 int str[10]={1110111,10010,1011101,1011011,111010,1101011,1101111,1010010,1111111,1111011}; const int M=128,N=2010; int str[10]={1110111,10010,1011101,1011011,111010,1101011,1101111,1010010,1111111,1111011}; const int M=128,N=2010; int mod[2][8]; int add[M][10]; //从原来状态到新状态,需要添加的笔画数 int sta[N],n,k; //1-n个初始情况,二进制 int trans_sta[15]; //0-9的数字亮灯情况,转化为二进制 int ans[N],res; string f[2][N]; //超内存了... 嘶 void prepare() { mod[0][0]=mod[1][0]=1; for(int i=1;i<=7;i++) { mod[0][i]=(1<<i); mod[1][i]=mod[1][i-1]*10; } // for(int i=0;i<128;i++) { for(int j=0;j<10;j++) { for(int p=1;p<=7;p++) { int a=i%mod[0][p]/mod[0][p-1]; int b=str[j]%mod[1][p]/mod[1][p-1]; if(a && !b ) { add[i][j]=-1; break; } else if(!a && b ) add[i][j]++; } } } //get_trans_sta[0-9] for(int i=0;i<=9;i++) { for(int p=1;p<=7;p++) { int b=str[i]%mod[1][p]/mod[1][p-1]; trans_sta[i]+=b*mod[0][p-1]; } } } void work() { res=k; for(int i=1;i<=n && res>=0 ;i++) { int mn=10,pos=0; for(int p=9;p>=0;p--) if(add[sta[i]][p]!=-1 && add[sta[i]][p]<mn) mn=add[sta[i]][p],pos=p; res-=mn; // cout<<mn<<" "<<res<<endl; if(mn==10 || res<0 ) res=-1; ans[i]=pos; } } void init() { cin>>n>>k; for(int i=1;i<=n;i++) { string s; cin>>s; for(int j=0;j<7;j++) sta[i]=(sta[i]<<1)+s[j]-'0'; } } void Max(string &a,string b) { if(a=="-1" ) { a=b; return ; } int len=a.length(); for(int i=0;i<len;i++) if(a[i]!=b[i] ) { if(a[i]<b[i] ) a=b; return ; } } void dp() { // for(int i=1;i<=n;i++) printf("%d",ans[i]); // printf("\n"); for(int j=1;j<=k;j++) f[0][j]="-1"; //f[0][0]=""; for(int i=1;i<=n;i++) { int nw=i%2,pre=nw^1; for(int j=0;j<=k;j++) f[nw][j]="-1"; for(int p=9;p>=0;p--) { int cos=add[sta[i]][p]; if(cos!=-1 ) { // bool pt=false; // if(i==n ) pt=true; for(int j=cos;j<=k;j++) { // if(cos==0 ) cout<<" "<<j-cos<<" "<<p<<" "<<f[i-1][j-cos]<<endl; if(f[pre][j-cos]!="-1" ) Max(f[nw][j],f[pre][j-cos]+(char)(p+'0') ); } } } } // for(int i=0;i<=k;i++) // cout<<" "<<i<<" "<<f[n][i]<<endl; cout<<f[n%2][k]; } int main() { prepare(); init(); work(); if(res<0 ) printf("-1"); else if(res==0 ) for(int i=1;i<=n;i++) cout<<ans[i]; else dp(); return 0; }
不得已,只能加入贪心的操作:
思路来自:codeforces1341 D. Nastya and Scoreboard(dp + 贪心)_牛客博客 (nowcoder.net)
able[ i ][ j ]表示从第 i 个数字开始到最后,点亮 j 个显示管,能否显示出数字。(从后向前)
现预处理出原始的每个显示屏变到某个数字需要点亮的显示管数,或者不可以变成某个数字
贪心时从前向后,从9到0(保证数字尽量大)
顺利ac,代码如下:
#include<bits/stdc++.h> using namespace std; //0-9对应的01字符串,10进制表示 int str[10]={1110111,10010,1011101,1011011,111010,1101011,1101111,1010010,1111111,1111011}; const int M=128,N=2010; int mod[2][8]; int add[M][10]; //从原来状态到新状态,需要添加的笔画数 int sta[N],n,k; //1-n个初始情况,二进制 int trans_sta[15]; //0-9的数字亮灯情况,转化为二进制 int ans[N],res; string f[2][N]; //超内存了... 嘶 void prepare() { mod[0][0]=mod[1][0]=1; for(int i=1;i<=7;i++) { mod[0][i]=(1<<i); mod[1][i]=mod[1][i-1]*10; } // for(int i=0;i<128;i++) { for(int j=0;j<10;j++) { for(int p=1;p<=7;p++) { int a=i%mod[0][p]/mod[0][p-1]; int b=str[j]%mod[1][p]/mod[1][p-1]; if(a && !b ) { add[i][j]=-1; break; } else if(!a && b ) add[i][j]++; } } } //get_trans_sta[0-9] for(int i=0;i<=9;i++) { for(int p=1;p<=7;p++) { int b=str[i]%mod[1][p]/mod[1][p-1]; trans_sta[i]+=b*mod[0][p-1]; } } } bool able[N][N];//表示从第i个位置到后面所有的位置,花费j,能否顺利拼成数字串 void work()// 修改1 { able[n+1][0]=true; for(int i=n;i>0;i--) { for(int p=0;p<=9;p++) { int cos=add[sta[i]][p]; if(cos!=-1) for(int j=cos;j<=k;j++) if(able[i+1][j-cos] ) able[i][j]=true; } } } void init() { cin>>n>>k; for(int i=1;i<=n;i++) { string s; cin>>s; for(int j=0;j<7;j++) sta[i]=(sta[i]<<1)+s[j]-'0'; } } void Max(string &a,string b) { if(a=="-1" ) { a=b; return ; } int len=a.length(); for(int i=0;i<len;i++) if(a[i]!=b[i] ) { if(a[i]<b[i] ) a=b; return ; } } void dp() { // for(int i=1;i<=n;i++) printf("%d",ans[i]); // printf("\n"); for(int j=1;j<=k;j++) f[0][j]="-1"; //f[0][0]=""; int sum=0; for(int i=1;i<=n;i++) { for(int p=9;p>=0;p--) { int cos=add[sta[i]][p]; if(cos==-1 ) continue; if(sum+cos<=k && able[i+1][k-sum-cos] )//修改2 { cout<<p; sum+=cos; break; } } } } int main() { prepare(); init(); work(); if(!able[1][k] ) printf("-1"); else dp(); return 0; }
同理,也可以用dfs剪枝处理此题,但是个人认为这个思路更好