动态规划(一)
\(1.UVa\) \(11584\) \(Partitioning\) \(by\) \(Parlindromes\)
前言
蓝书上所说的入门题目,然而我好像并没有入门。
题意:
输入是一个由小写字母组成的字符串,你的任务是把它划分成尽量少的回文串。比如,\(racecar\)本身就是回文串;\(fastcar\)只能分成\(7\)个单字母的回文串;\(aaadbccb\)最少可分成\(3\)个回文串:\(aaa,d,bccb\)。字符串长度不超过\(1000\)。
分析:
有一种似曾相识的感觉。
应该是先预处理出所有的子回文串,然后令d[i]表示在1-i这段区间中最少能划分成的回文串数。
转移应为:
初始化为\(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\)时的最少修改次数,那么转移就应该是:
\(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\)组末尾的字符)时,这样他们可以连起来,所以我们可以得到
否则,若第\(i-1\)组与第\(i\)组没有相同的字符时或者第\(i\)组不只有一个块且此时以相同的字符作为第\(i\)组的末尾时,直接将两块的块数相加即可
然后就可以写出代码了。
本人觉得这道题还是有一定难度的。
\(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;
}
代码还是比较清晰的(我比较喜欢这种码风)。