zoukankan      html  css  js  c++  java
  • P3283 [SCOI2013]火柴棍数字 DP

    题意:

    给定一个 (n) 位的数字,求最多移动 (k) 根火柴棒, 能形成的最大值

    范围&性质: (1le n le 500,1le k le 3500)

    分析:

    首先有几个很显然的贪心性质:

    1. 位数越多越好
    2. 补的前几位都是 1 (最高位有可能是7)

    所以问题转化成对于原序列,再拿出尽可能多的火柴棒的前提下,留下的最大值是多少

    直接DP,设 (f_{i,j,k}) 表示从小到大第 (i) 位 此时有 (j) 根空闲的火柴棒 这一位是否变成 (0)的情况下最小的移动次数, 转移的时候就枚举这一位变成了什么,所以需要提前预处理出任意两个数之间转化时会增加多少空闲的火柴棒,增加多少移动次数

    代码:

    #include<bits/stdc++.h>
    
    using namespace std;
    
    namespace zzc
    {
    	//8 从左到右 从上到下 依次标号 
    	const int maxn = 3505;
    	int n,m,sum,len,use;
    	short f[maxn>>1][maxn<<1][2];
    	int del[11][10],add[11][10];
    	//int s[10]={111,96,62,124,105,93,95,100,127,125};
    	int s[10]={126,48,107,121,53,93,95,112,127,125};
    	char ch[maxn];
    	
    	void init()
    	{
    		for(int i=0;i<=9;i++)
    		{
    			for(int j=0;j<=9;j++)
    			{
    				for(int k=0;k<=6;k++)
    				{
    					if(!(s[i]>>k&1)&&(s[j]>>k&1)) del[i][j]++,add[i][j]++;
    					if((s[i]>>k&1)&&!(s[j]>>k&1)) add[i][j]--;
    				}
    			}
    		}
    		for(int i=0;i<=9;i++)
    		{
    			for(int k=0;k<=6;k++)
    			{
    				if(s[i]>>k&1) add[10][i]++,del[10][i]++;
    			}
    		}
    	}
    	
    	void work()
    	{
    		init();
    		scanf("%s",ch+1);
    		scanf("%d",&m);
    		n=strlen(ch+1);
    		reverse(ch+1,ch+n+1);
    		for(int i=1;i<=n;i++) ch[i]-='0';
    		for(int i=1;i<=n;i++) sum+=del[10][ch[i]];
    		len=sum>>1;
    		for(int i=n+1;i<=len;i++) ch[i]=10;
    		for(int i=0;i<=len;i++) for(int j=-m;j<=m;j++) f[i][j+maxn][0]=m+1,f[i][j+maxn][1]=m+1; 
    		f[0][maxn][0]=0;
    		for(int i=0;i<len;i++)
    		{
    			for(int j=-m;j<=m;j++)
    			{
    				for(int k=0;k<=1;k++)
    				{
    					if(f[i][j+maxn][k]<=m)
    					{
    						for(int x=0;x<=9;x++) 
    						f[i+1][j+maxn+add[ch[i+1]][x]][x==0]=min(f[i+1][j+maxn+add[ch[i+1]][x]][x==0],(short)(f[i][j+maxn][k]+del[ch[i+1]][x]));
    					}	
    				}
    			}
    		}
    		while(f[len][maxn][0]>m) len--;
    		for(int i=0;i<=len;i++) for(int j=-m;j<=m;j++) f[i][j+maxn][0]=min(f[i][j+maxn][0],f[i][j+maxn][1]);
    		sum=0;
    		for(int i=len;i;i--)
    		{
    			for(int j=9;j>=0;j--)
    			{
    				sum+=add[ch[i]][j];
    				use+=del[ch[i]][j];
    				if(f[i-1][maxn-sum][0]<=m-use)
    				{
    					printf("%d",j);
    					break;
    				}
    				sum-=add[ch[i]][j];
    				use-=del[ch[i]][j];
    			}
    		}
    	}
    
    }
    
    int main()
    {
    	zzc::work();
    	return 0;
    }
    
    
  • 相关阅读:
    计总与排名SUM和RANK函数
    计算获取最小值和最大值
    列值中获取第一个非空的值
    连续数字使用连接符替换
    展开中断或忽略的序号
    以连接字符截取字符串
    逗号分割字符串经存储过程存入数据表中
    符号分割的字符串转换为XML
    MS SQL Server的STRING_SPLIT和STRING_AGG函数
    MS SQL Server的LTRIM,RTRIM和TRIM函数
  • 原文地址:https://www.cnblogs.com/youth518/p/13951668.html
Copyright © 2011-2022 走看看