动态规划(一)

\(1.UVa\) \(11584\) \(Partitioning\) \(by\) \(Parlindromes\)

前言

蓝书上所说的入门题目,然而我好像并没有入门。

题意:

输入是一个由小写字母组成的字符串,你的任务是把它划分成尽量少的回文串。比如,\(racecar\)本身就是回文串;\(fastcar\)只能分成\(7\)个单字母的回文串;\(aaadbccb\)最少可分成\(3\)个回文串:\(aaa,d,bccb\)。字符串长度不超过\(1000\)

分析:

有一种似曾相识的感觉。

应该是先预处理出所有的子回文串,然后令d[i]表示在1-i这段区间中最少能划分成的回文串数。

转移应为:

\[d[i]=min(d[i],d[j-1]+1) \]

初始化为\(d[i]=i\)

看了题解后发现果然是做过的题,然而已经是做过的题了还是不能切掉,我是有多垃圾。

\(Code:\)

    /*
    	Problem ID:UVa 11584
    	Author: dolires
    	Date: 26/09/2019 21:45
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    using namespace std;
    
    const int maxn=1010;
    int f[maxn][maxn];
    int d[maxn];
    char s[maxn];
    void init()
    {
        int l=strlen(s);
        for(int i=0;i<l;++i)
        {
            for(int j=i;j>=0;--j)
            {
                if(i==j||(s[i]==s[j]&&(j+1==i||f[j+1][i-1])))//不用担心i-1会小于0,从而导致
                {//程序运行时错误,因为或运算若满足了前半部分就不会再进入后半部分,而当i=0时,j只可
                    f[i][j]=1;//能为0,故已经满足了前半部分
                }
            }
        }
    }
    
    template<class T>void read(T &x)
    {
        bool f=0;char ch=getchar();x=0;
        for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
        for(;ch>='0'&&Ch<='9';ch=getchar()) x=x*10+ch-'0';
        if(f) x=-x;
    }
    
    int main()
    {
        cin>>s;
        init();
        int l=strlen(s);
        for(int i=0;i<n;++i) d[i]=i+1;
        for(int i=0;i<l;++i)
        {
            for(int j=0;j<=i;++j)
            {
                if(f[j][i]) d[i]=min(d[i],d[j-1]+1);
            }
        }
        printf("%d\n",d[n-1]);
        return 0;
    }

略有进步的是思路在看了题结果后能比较快的转换,写代码的时候也要流畅一些了。

但是不足在:\(1.\)回文串的判断还是不会写 \(2.\)看了题解后才知道思路

所以,再写一遍预处理回文串

预处理回文串

    void init()
    {
        int l=strlen(s);
        for(int i=0;i<l;++i)
        {
            for(int j=i;j>=0;--j)
            {
                if(i==j||(s[i]==s[j]&&(j+1==i||f[j+1][i-1])))//单个字符一定是回文串,若回文
                {//长度为奇数,后面的判断条件f[j+1][i-1]=1直接使用即可,而当回文串长度为偶数时,
                    f[i][j]=1;//因为有个s[i]=s[j]且j+1==i的条件,先把回文串中心为空格向两边各延
                }//伸一个字符的情况直接特判了,然后之后的判断与奇数串相同即可
            }
        }
    }

\(2.Salesman,Seoul\) \(2008,LA\) \(4256\)

前言:

本来一开始都打算跳过这道题的,毕竟蓝书上写的入门题,但是当看了第一道入门而且还是做过的题都做不起,于是打算开始做这道题,果然我还没入门Σ( ° △ °|||)︴

题意:

给定一个包含\(n\)个点\((n<=100)\)的无向连通图和一个长度为\(L\)的序列\(A(L<=200)\),你的任务是修改尽量少的数,使得序列中的任意两个相邻的数要么相同,要么对应图中两个相邻结点。

分析:

其实是很简单的转移方程,但是我根本就没有认认真真的去想,自罚几耳光。

我们设\(d[i][j]\)表示第\(i\)个数为\(j\)时的最少修改次数,那么转移就应该是:

\[d[i][j]=d[i-1][k]+!pos[i][j] \]

\(pos[i][j]\)表示第\(i\)位是否为\(j\),是的话为\(1\),否的话则为\(0\)

其中\(k\)表示与\(j\)相同的数或者在图中与结点\(j\)相邻的结点。

\(Code:\)

    /*
    	Problem ID:LA 4256
    	Author: dolires
    	Date: 16/09/2019 22:03
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    #define inf 0x7fffffff
    using namespace std;
    
    const int maxn=210;
    
    int a[maxn];
    int g[maxn][maxn];
    int pos[maxn][maxn];
    int d[maxn];
    
    template<class T>void read(T &x)
    {
        bool f=0;char ch=getchar();x=0;
        for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
        for(;ch>='0'&&Ch<='9';ch=getchar()) x=x*10+ch-'0';
        if(f) x=-x;
    }
    
    int main()
    {
        int Max,m,n;
        read(Max);read(m);
        memset(pos,0,sizeof(pos));
        for(int i=1;i<=m;++i)
        {
            int u,v;
            read(u);read(v);
            g[u][v]=g[v][u]=1;
        }
        read(n);
        for(int i=1;i<=n;++i) g[i][i]=1;
        for(int i=1;i<=n;++i)
        {
            read(a[i]);
            pos[i][a[i]]=1;
        }
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=Max;++j)
            {
                d[i][j]=inf;
            }
        }
        for(int i=1;i<=Max;++i) d[1][j]=!pos[1][j];
        int Min=inf;
        for(int i=2;i<=n;++i)
        {
            for(int j=1;j<=Max;++j)
            {
                for(int k=1;k<=Max;++k)
                {
                    if(g[j][k])
                    {
                        d[i][j]=min(d[i][j],d[i-1][k]+!pos[i][j]);
                    }
                }
                if(i==n)
                {
                    Min=min(Min,d[i][j]);
                }
            }
        }
        printf("%d\n",Min);
        return 0;
    }

\(3.Wavio\) \(Sequence,UVa\) \(10534\)

前言:

终于不是入门题了

题意:

给定一个长度为\(n\)的整数序列,求一个最长子序列(不一定连续),使得该序列的长度为奇数\(2k+1\),前\(k+1\)个数严格递增,后\(k+1\)个数严格递减。注意,严格递增/递减意味着该序列中的两个相邻数不能相同。\(n<=10000\)

分析:

看懂题意后,不禁想吐槽一句,明明这个题才应该是入门题好吗。

就直接套最长上升子序列,正着做一次,再反着做一次。

比如设\(l[i]\)表示以第i个数结尾的最长上升子序列长度,而\(r[i]\)表示的则是以i开头的最长下降子序列长度,所以总长度就为\(min(l[i],r[i])-1\),实现即可。

\(Code:\)

    /*
    	Problem ID:UVa10534
    	Author: dolires
    	Date: 27/09/2019 18:45
    */
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstring>
    using namespace std;
    
    const int maxn=1e4+10;
    const int inf=0x7fffffff;
    
    int a[maxn],g[maxn],l[maxn],r[maxn];
    
    template<class T>void read(T &x)
    {
    	bool f=0;char ch=getchar();x=0;
    	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    	if(f) x=-x;
    }
    
    int main()
    {
    	int n;
    	read(n);
    	for(int i=1;i<=n;++i) read(a[i]);
    	for(int i=1;i<=n;++i) g[i]=inf;
    	for(int i=1;i<=n;++i)
    	{
    		int k=lower_bound(g+1,g+n+1,a[i])-g;
    		l[i]=k;
    		g[k]=a[i];
    	}
    	reverse(a+1,a+n+1);
    	for(int i=1;i<=n;++i) g[i]=inf;
    	for(int i=1;i<=n;++i)
    	{
    		int k=lower_bound(g+1,g+n+1,a[i])-g;
    		r[n-i+1]=k;
    		g[k]=a[i];
    	}
    	int ans=0;
    	for(int i=1;i<=n;++i)
    	{
    		ans=max(ans,min(l[i],r[i]));
    	}
    	printf("%d\n",ans*2-1);
    	return 0;
    }

\(4.Fewest\) \(Flops,UVa\) \(11552\)

题意:

输入一个正整数\(k\)和字符串\(S\),字符串的长度保证为\(k\)的倍数。把\(S\)的字符按照从左到右的顺序每\(k\)个分成一组,每组之间可以任意重排,但组与组之间的先后顺序应保持不变。你的任务是让重排后的字符串包含尽量少的“块”,其中的每个块为连续的相同字符。比如,\(uuvuwwuv\)可分成两组:\(uuvu\)\(wwuv\),第一组可重排位\(uuuv\),第二组可重排为\(vuww\),连起来是\(uuvvuww\),包含\(4\)个“块”。

\(|S|<=1000。\)

分析:

对于每个单独的部分,它的块就是该部分中不同字母的种类数,我们将它记为chunk[i]。

然后我们设\(d[i][j]\)表示第i组以j结尾时的最小块数,而我们通过第\(i-1\)组来转移状态。

如果第\(i-1\)组中有与第i组相同的字符,且第\(i\)组只有一个块时或者是第\(i-1\)组与第\(i\)组相同的字符不是\(j\)(即第\(i\)组末尾的字符)时,这样他们可以连起来,所以我们可以得到

\[d[i][j]=min(d[i][j],d[i-1][p]+chunk[i]-1)\quad(if(chunk[i]==1)\quad p可以为j) \]

否则,若第\(i-1\)组与第\(i\)组没有相同的字符时或者第\(i\)组不只有一个块且此时以相同的字符作为第\(i\)组的末尾时,直接将两块的块数相加即可

\[d[i][j]=min(d[i][j],d[i-1][p]+chunk[i])\quad (if(chunk[i]!=1)\quad p不可以为j) \]

然后就可以写出代码了。

本人觉得这道题还是有一定难度的。

\(Code:\)

    /*
        Problem ID:UVa11552
    	Author:dolires 
    	Date: 27/09/2019 19:42
    */
    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int maxn=1010;
    
    char s[maxn];
    int chunk[maxn];
    bool vis[maxn][30];
    int d[maxn][30];
    
    template<class T>void read(T &x)
    {
    	bool f=0;char ch=getchar();x=0;
    	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
    	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
    	if(f) x=-x;
    }
    
    int main()
    {
    	int t;
    	read(t);
    	while(t--)
    	{
    		int k;
    		read(k);
    		cin>>s;
    		int Min=0x7fffffff;
    		memset(vis,0,sizeof(vis));
    		int l=strlen(s);
    		int m=l/k;
    		for(int i=1;i<=m;++i)
    		{
    			for(int j=(i-1)*k;j<i*k;++j)
    			{
    				if(!vis[i][s[j]-'0'])
    				{
    					vis[i][s[j]-'0'];
    					++chunk[i];
    				}
    			}
    		}
    		memset(d,0x7f,sizeof(d));
    		for(int i=0;i<26;++i)
    		{
    			if(vis[1][i]) d[1][i]=chunk[1];
    		}
    		for(int i=2;i<=m;++i)
    		{
    			for(int j=0;j<26;++j)
    			{
    				if(vis[i][j])
    				{
    					for(int k=0;k<26;++k)
    					{
    						if(vis[i-1][k])
    						{
    							if(vis[i][k]&&(k!=j||(k==j&&chunk[i]==1)))
    							{
    								d[i][j]=min(d[i][j],d[i-1][k]+chunk[i]-1);
    							}
    							else d[i][j]=min(d[i][j],d[i-1][k]+chunk[i]);
    						}
    					}
    				}
    			}
    		}
    		for(int i=0;i<=26;++i) Min=min(Min,d[m][i]);
    		printf("%d\n",Min);
    	}
    	return 0;
    }

代码还是比较清晰的(我比较喜欢这种码风)。

posted on 2019-11-08 21:47  dolires  阅读(135)  评论(0)    收藏  举报

导航