这两天做到两道比较好的dp题,感觉很多dp题都这样,就是很容易想到一个O(n*n)的dp,但是O(n*n)的会超时,所以就想怎么优化,有时候可以降成O(n*log n)的,有时候是思维题,只要想到了就可以降成O(n)的dp,就是最普通的那种dp。感觉都比较类似,在这里总结一下。
codeforces 1196 D2
题意:给你一个长度为n的字符串a和一个整数k,问最少改变多少个字符能使:这个字符串中存在一个长度为k的子串是字符串是“RGBRGBRGB......”的字串。k<=n<=2e5
首先,O(n*n)的dp很好想:
令mp['R']=1,mp['G']=2,mp['B']=3。
dp[i][j][k]表示前 i 个字符中构成一个长度为k的子串(并且这个子串是以i为结尾的,即a[i-k+1]到a[i],并且mp[a[i]]==k)所需要改变的最小次数。
状态转移方程:dp[i][j][k]=min(dp[i][j][k],dp[i-1][j-1][k-1]+(mp[a[i]]==k?0:1)) 如果k等1的话,k-1改成3。
不过这显然会T(D1那个数据范围小,可以这么AC)
然后我们想怎样优化,我们可以不记录长度为1到k的子串,我们只需要记录长度为k的即可,这样也能进行状态转移。
令dp[i][j]表示前i个字符中构成一个长度为k的子串(并且这个子串是以i为结尾的,即a[i-k+1]到a[i],并且mp[a[i]]==j)所需要改变的最小次数。状态转移方程:dp[i][j]=min(dp[i][j],dp[i-1][j-1]+(mp[a[i]]==k?0:1)-f) 如果j等1的话,j-1改成3
其中f的值为0或者1,当mp[a[i-k]]==j-(k%3)时f=0,否则f为1。
详见代码:
#include
#define mem(a,b) memset((a),b,sizeof(a))
#define de cout< mp;
void init()
{
for(int p=1;p<=3;p++)
{
int num=0;
int pp=p;
for(int i=kk;i>=1;i--)
{
if(mp[a[i]]!=pp)
num++;
pp--;
if(pp<1) pp=3;
}
dp[kk][p]=num;
}
}
int main()
{
mp['R']=1;
mp['G']=2;
mp['B']=3;
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&kk);
scanf("%s",a+1);
if(n==1||kk==1) {printf("0\n");continue;}
for(int i=0;i<=n;i++)
for(int j=1;j<=3;j++)
dp[i][j]=inf;
init();
int f,K;
for(int i=kk+1;i<=n;i++)
{
for(int j=1;j<=3;j++)
{
f=0;
K=kk%3;
K=j-K;
if(K<1)
K+=3;
if(mp[a[i-kk]]!=K)
f=1;
if(mp[a[i]]==j)
{
if(j==1)
dp[i][j]=min(dp[i][j],dp[i-1][3]-f);
else
dp[i][j]=min(dp[i][j],dp[i-1][j-1]-f);
}
else
{
if(j==1)
dp[i][j]=min(dp[i][j],dp[i-1][3]+1-f);
else
dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1-f);
}
}
}
int ans=inf;
for(int i=kk;i<=n;i++)
for(int j=1;j<=3;j++)
ans=min(ans,dp[i][j]);
printf("%d\n",ans);
}
return 0;
}
codeforces 1256 E
题意:给你n个数,让你给这n个数分组(分多少组都行,但每个组的人数必须大于等于3),每个组的组值为组里的数的最大值减最小值的差,问怎样分组才能使这些组的组值的和最小。n<=2e5
先把这n个数排个序,因为肯定是希望把值相近的数分到一起嘛
首先,O(n*n)的dp还是很好想:
令dp[i]表示对前i个人分组,组值的和最小为dp[i]。dp[i]=min(dp[i],dp[j]+a[i]-a[i-j+1]) 3<=i-j && j>=3
但是我们没有必要让j循环这么多次。
如果我们把6个人分成一组,那么一定不是最优的,因为我们可以把这六个人拆成三三的两组,这样一定不比6个人一组差,所以一个组的人数只有3种情况:三人组、四人组、五人组。
令dp[i]表示对前i个人分组,组值的和最小为dp[i]。dp[i]=min(dp[i],dp[i-3]+a[i]-a[i-2],dp[i-4]+a[i]-a[i-3],dp[i-5]+a[i]-a[i-4])
#include
#define mem(a,b) memset((a),b,sizeof(a))
#define de cout<=8)
dp[i]=min(min(dp[i-3]+a[i].v-a[i-2].v,dp[i-4]+a[i].v-a[i-3].v),dp[i-5]+a[i].v-a[i-4].v);
else if(i>=7)
dp[i]=min(dp[i-3]+a[i].v-a[i-2].v,dp[i-4]+a[i].v-a[i-3].v);
else
dp[i]=dp[i-3]+a[i].v-a[i-2].v;
}
printf("%I64d ",dp[n]);
ans=0;
for(int i=n;i>=3;i--)
{
if(i>=5&&dp[i]==dp[i-5]+a[i].v-a[i-4].v)
a[i].idd=++ans,a[i-1].idd=ans,a[i-2].idd=ans,a[i-3].idd=ans,a[i-4].idd=ans,i-=4;
else if(i>=4&&dp[i]==dp[i-4]+a[i].v-a[i-3].v)
a[i].idd=++ans,a[i-1].idd=ans,a[i-2].idd=ans,a[i-3].idd=ans,i-=3;
else if(i>=3&&dp[i]==dp[i-3]+a[i].v-a[i-2].v)
a[i].idd=++ans,a[i-1].idd=ans,a[i-2].idd=ans,i-=2;
}
printf("%d\n",ans);
sort(a+1,a+n+1,cmp2);
printf("%d",a[1].idd);
for(int i=2;i<=n;i++)
printf(" %d",a[i].idd);
puts("");
return 0;
}