• 【洛谷P1415】拆分数列【dp】


    题目大意:

    题目链接:https://www.luogu.org/problemnew/show/P1415
    给出一列数字,需要你添加任意多个逗号将其拆成若干个严格递增的数。如果有多组解,则输出使得最后一个数最小的同时,字典序最大的解(即先要满足最后一个数最小;如果有多组解,则使得第一个数尽量大;如果仍有多组解,则使得第二个数尽量大,依次类推……)。


    思路:

    由于要先把最后一位尽量小,考虑先求出最后一位的最小答案。
    f[i]f[i]表示在满足前ii位的划分单调递增时,以第ii位为末,且这一次的划分的值尽量小的时候划分的首位。
    简单来说,就是找到一个jj满足在jj开始划分可以满足单调递增且jioverline{jsim i}尽量小的方案数。
    显然只需要让jj尽量大就可以了。就倒序枚举jj,如果f[j1]j1<jioverline{f[j-1]sim j-1}<overline{jsim i}就让f[i]=jf[i]=j
    其中xyoverline{xsim y} 可以预处理搞定。
    接下来求出满足前面尽量大的答案。
    g[i]g[i]表示在满足后ii位的划分满足单调递增时,以第ii位为首,且这一次的划分的值尽量大的时候划分的末位。
    显然初始化d[f[n]]=nd[f[n]]=n
    推方程思路和ff基本一样。
    注意处理前导0。可以用sum[x][y]sum[x][y]表示xyoverline{xsim y}的前导0个数。
    这样的时间复杂度就是O(n3)O(n^3)的。实际上跑起来会小得多。


    代码:

    #include <cstdio>
    #include <string>
    #include <iostream>
    using namespace std;
    
    const int N=510;
    int n,a[N],f[N],g[N],sum[N][N];
    string t[N][N];
    
    bool check(int S1,int T1,int S2,int T2)
    {
    	if (T1-S1-sum[S1][T1]<T2-S2-sum[S2][T2]) return 1;
    	if (T1-S1-sum[S1][T1]>T2-S2-sum[S2][T2]) return 0;
    	string s1=t[S1+sum[S1][T1]][T1],s2=t[S2+sum[S2][T2]][T2];
    	for (int i=0;i<min(s1.size(),s2.size());i++)
    	{
    		if (s1[i]<s2[i]) return 1;
    		if (s1[i]>s2[i]) return 0;
    	}
    	return 0;
    }
    
    void dp1()
    {
    	f[1]=1;
    	for (int i=2;i<=n;i++)
    		for (int j=i;j>=1;j--)
    			if (check(f[j-1],j-1,j,i))
    			{
    				f[i]=j;
    				break;
    			}
    }
    
    void dp2()
    {
    	g[f[n]]=n;
    	int k=0;
    	for (int i=f[n]-1;!a[i]&&i;i--) g[i]=n,k++;
    	for (int i=f[n]-1-k;i>=1;i--)
    		for (int j=f[n]-1;j>=i;j--)
    			if (check(i,j,j+1,g[j+1]))
    			{
    				g[i]=j;
    				break;
    			}
    	int i;
    }
    
    int main()
    {
    	while (scanf("%1d",&a[++n])==1)
    		for (int i=1;i<=n;i++)
    		{
    			t[i][n]=t[i][n-1]+(char)(a[n]+48);
    			if (sum[i][n-1]==max(n-i,0)&&!a[n]) sum[i][n]=sum[i][n-1]+1;
    				else sum[i][n]=sum[i][n-1];
    		}
    	n--;
    	dp1();
    	dp2();
    	int j=g[1];
    	for (int i=1;i<=n;i++)
    	{
    		printf("%d",a[i]);
    		if (i==j&&i!=n)
    		{
    			j=g[i+1];
    			putchar(',');
    		}
    	}
    	return 0;
    }
    

    这道题思路还是不难,但是敲了我3h3h。。。恐怖如斯
    话说这道题有要求O(nlogn)O(nlog n)的加强版 o P2282[HNOI2003]历史年份,好像是用hash+hash+二分判断两个子串的大小,线段树维护f的最大值。。。orz

  • 相关阅读:
    6、加法算术
    5、找出最大和最小的数
    4、计算并输出圆的面积和周长
    2、函数y=f(x)
    1、两数的平方和
    单片机中断寄存器知识点总结
    创建PCB原理图的模板
    电机知识
    结合实例谈谈航拍全景的方法和技巧
    航拍技巧
  • 原文地址:https://www.cnblogs.com/hello-tomorrow/p/11998229.html
Copyright © 2020-2023  润新知