【CCC】dinner

貌似zuma挺流行,coci有一道,poj有两道,CCC有一道,poj有一道还曾是神题属性...

这道题目要求比较少,完全不会发生链式反应

考虑到正着操作不好分成或表示子状态,因此我们反着考虑操作,变成选择若干同色段,使其个数不小于K,为了完成这一操作只需消去不是我们所选择的区间即可,因此使用区间dp,f[i][j]表示区间[i,j]消掉的代价,为了完成上述转移,利用g[L][k]进行线性dp,表示前L段,必选第L段所选出的个数k的最小代价,然后用g数组即可求出f[i][j]

复杂度上g数组dp是o(n^2*k)的,f有o(n^2)状态,因此陈高远分析复杂度是o(n^4*k),最后是利用小常数跑过的,最慢的点用了0.3s

但是,考虑到我们已经利用g求出了f[i][j-1]了,求f[i][j]的时候我们又重新dp一遍g是完全不必要的,因为一直到g[j-1]其实两次dp是无差别的,所以干脆一边求g一边求f[i][j],只要i不变,已经求出的g是不会变的,因此只会做o(n)次g数组的dp,总复杂度降为o(n^3*k),可以完全无压力的过全部数据,最慢的大约只有0.05s

值得吐槽的是,我调的时候发现了3个错误,当我只犯其中特定的2个的时候只会错一个点,当只错其中一个的时候与错三个的时候是基本一样的,可见数据很不好造

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
struct pin
{
  char ch;
  int len;
}a[200];
int n,m,K;
char ch[200];
int f[200][200],g[200][20];
int L(int x) {return (x>K) ? K : x;}
void init(int s)
{
  int i,j,k,p;
  memset(g,27,sizeof(g));
  g[s][L(a[s].len)]=0;
  for (i=s+1;i<=m;i++) {
    if (a[i].len>=K) g[i][K]=f[s][i-1];else g[i][K]=g[i][0];
    j=L(a[i].len);
    for (k=s;k<i;k++)
      if (a[i].ch==a[k].ch)
	for (p=K;p>=1;p--)
	  g[i][L(j+p)]=min(g[i][L(j+p)],g[k][p]+f[k+1][i-1]);
    f[s][i]=g[i][K]+1;
    for (j=s;j<i;j++) 
      f[s][i]=min(f[s][i],g[j][K]+1+f[j+1][i]);
  }
}
int main()
{
  scanf("%d%d\n",&n,&K);
  scanf("%s\n",ch+1);
  int i,k;
  for (m=0,i=1,k=1;i<=n+1;i++,k++)
    if (ch[i]!=ch[i-1]) a[m].len=k-1,a[++m].ch=ch[i],k=1;
  m--;
  memset(f,27,sizeof(f));
  for (i=1;i<=m;i++)
    if (a[i].len>=K) f[i][i]=1,f[i][i-1]=0;
  for (i=m;i>=1;i--) init(i);
  if (f[1][m]<f[1][m+1]) printf("%d\n",f[1][m]);else printf("-1\n");
  //  for (i=1;i<=m;i++) printf("%c%d",a[i].ch,a[i].len);
  return 0;
}


你可能感兴趣的:(【CCC】dinner)