区间DP

[CQOI2007] 涂色

题意:

你有一个无色木板,每次给一个区间涂色,会覆盖,求最少多少次才能变成跟目标木板一样

保证都是大写字母

\({n<=50}\)

40pts

\({n<=10}\)

对于如此小的数据范围,加上很容易发现,最多的次数和长度相等
所以我们可以直接上大法师,加上一点剪枝即可
想信各位大佬一定会,所以不贴代码了
实则是某个晚上睡不着口糊的,根本没写

100pts

直接dp
不难想到,外层枚举一个len,第二层枚举一个左端点
然后dp分两种:
如果c[i]==c[j]
先让dp[i][j]=dp[i][j-1]
然后枚举一个中间点k,dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])
显然很对
实则刚开始想偏了
然后就完了

#include <bits/stdc++.h>
#define in :
#define int long long
#define inf 0x3f3f3f3f3f3f3f3fll
#define _inf -4485090715960753727ll
#define fr(a, b, c) for (int a = b; a <= c; ++a)
#define fb(a, b, c) for (int a = b; a >= c; --a)
#define IfFast
using namespace std;

namespace fast {
#ifdef IfFast
void read(int &a) {
  int s = 0, f = 1;
  char ch = getchar();
  while (!(ch <= '9' && ch >= '0')) {
    if (ch == '-') {
      f = -1;
    }
    ch = getchar();
  }
  while (ch <= '9' && ch >= '0') {
    s = s * 10 + ch - '0';
    ch = getchar();
  }
  a = s * f;
  return void();
}

void read(char &a) {
  char x = getchar();
  while (x == ' ' || x == '\r' || x == '\n' || x == '\t') {
    x = getchar();
  }
  a = x;
  return void();
}

void print(int x) {
  if (x < 0) {
    putchar('-');
    x = -x;
  }
  if (x < 10) {
    putchar(x + '0');
  } else {
    print(x / 10);
    putchar(x % 10 + '0');
  }
  return void();
}
#endif
template <typename T> void read(T &a) {
  cin >> a;
  return void();
}

template <typename T, typename... Args> void read(T &a, Args &...args) {
  read(a);
  read(args...);
  return void();
}

inline int read() {
  int a;
  read(a);
  return a;
}

template <typename T> void print(T a) {
  cout << a;
  return void();
}

template <typename T, typename... Args> void print(T a, Args... args) {
  print(a);
  print(args...);
  return void();
}
} // namespace fast

using fast::print;
using fast::read;
using us = unsigned;
int n;
int ans = 0;
int cnt = 0;
int s[60];
int dp[60][60]; // 从i到j满足条件的最小值

signed main() {
#ifndef IfFast
  ios::sync_with_stdio(false);
#endif
  // freopen("1.in", "r", stdin);
  string sl;
  read(sl);
  n = sl.size();
  for (us i = 0; i < n; i++) {
    s[i + 1] = sl[i] - 'A';
  }
  memset(dp, 0x3f, sizeof(dp));
  for (us i = 1; i <= n; i++) {
    for (us j = 0; j < i; j++) {
      dp[i][j] = 0;
    }
    dp[i][i] = 1;
  }
  for (us len = 2; len <= n; len++) {
    for (us i = 1; i <= n - len + 1; i++) {
      if (const us j = i + len - 1; s[i] == s[j]) {
        dp[i][j] = dp[i][j - 1];
      } else {
        for (us k = i; k <= j; k++) {
          dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
        }
      }
    }
  }
  print(dp[1][n], '\n');
  return 0;
}

双倍经验(n<=300,甚至是个紫)
所以我是两道紫题(确信

[ZJOI2016] 线段树

题意:

给一个长为n的非负序列,有q次操作,每次随机等概率选一个区间(共\(({n*(n+1)\over2})^q\)个),将区间中的每个数都变成区间最大值,问每个位置最后数的期望\(*({n*(n+1)\over2})^q\)模1e9+7的结果

Solution:

首先,各位大佬肯定一眼就看出了,期望乘上这一长串东西就是这个位置每个可能的数乘上是这个数的方案数
然后,发现每次转移和第几次操作有关,和区间最大值有关
所以很容易(并非)得出一个神仙状态
\({f_{x,i,l,r}}\)表示第i轮操作,[l,r]中的数都<=x的方案数
然后注意力惊人就可以得出转移式子

${f_{x,i,l,r}}={f_{x,i-1,l,r}*g(l,r)+\sum_{j=1}^{l-1}{f_{x,i-1,j,r}*(j-1)+\sum_{k=r+1}^nf_{x,i-1,l,k}*(n-k)}}$

\(f_{x,i-1,l,r}*g(l,r)\)这一串表示上一轮[l,r]已经满足条件,并且经历了一次对这个区间没有效果的转移

有三种情况:

  1. 选的区间在[1,l-1]之间
  2. 选的区间在[l,r]之间
  3. 选的区间在[r+1,n]之间

以上三行总结成一句话就是不跨[l,r]的操作
由此很容易的出\({g(l,r)}\)的式子

$g(l,r)={l*(l-1)\over2}+{(r-l+1)*(r-l+2)\over2}+{(n-r)*(n-r+1)\over2}$

接着,我们开个数组\({h_{x,i}}\)
表示第i个位置最终数字<=x的方案数
显然我们在第q次后统计一下即可

$h_{x,i}={\sum_{l=1}^{i}\sum_{r=i}^{n}}f_{x,i,l,r}$

最后答案

$ans_i={\sum_xx*h_{x,i}}$

复杂度这一块,
显然啥都不管直接写能直接写到\({O(q*n^4)}\)甚至\({O(q*n^5)}\)
加个前缀和能优到\({O(q*n^3)}\)
据某些大佬所言,这玩意在数据随机的情况下能干到\({O(q*n^2)}\)
我也不知道把哪个n随机没了
并且两个晚上拼尽全力无法战胜,最后果断放弃

所以老实优化

我们不难发现,最外层的x基本没啥用,提供个初值后就可以退场了
然后就可以把这些东西和到一块dp
把dp初值一改就行了,因为其他式子和x都没啥关系

$if(min(a[l-1],a[r+1]>maxx))$
${dp_{0,l,r}=maxx-min(a[l-1],a[r+1])}$
maxx表示[l,r]中的最大值

原因很显然:考虑\(ans[i]\)最后的式子,把\({h_{x,i}}\)展开,结合\({f_{x,0,l,r}=1,if(maxx{<}min(a[l-1],a[r+1]))}\)
然后中间许多方案数是一样的,只有乘的x不一样,所以可以抵消,抵消完了就是这个式子了

神仙初始化

#include<bits/stdc++.h>
#define in :
#define int long long
#define inf 0x3f3f3f3f3f3f3f3fll
#define _inf -4485090715960753727ll
#define fr(a,b,c) for(int a=b;a<=c;++a)
#define fb(a,b,c) for(int a=b;a>=c;--a)
#define IfFast
using namespace std;

namespace fast {
#ifdef IfFast
	void read(int &a) {
		int s = 0, f = 1;
		char ch = getchar();
		while (!(ch <= '9' && ch >= '0')) {
			if (ch == '-') {
				f = -1;
			}
			ch = getchar();
		}
		while (ch <= '9' && ch >= '0') {
			s = s * 10 + ch - '0';
			ch = getchar();
		}
		a = s * f;
		return void();
	}

	void read(char &a) {
		char x = getchar();
		while (x == ' ' || x == '\r' || x == '\n' || x == '\t') {
			x = getchar();
		}
		a = x;
		return void();
	}

	void print(int x) {
		if (x < 0) {
			putchar('-');
			x = -x;
		}
		if (x < 10) {
			putchar(x + '0');
		} else {
			print(x / 10);
			putchar(x % 10 + '0');
		}
		return void();
	}
#endif
	template<typename T>
	void read(T &a) {
		cin >> a;
		return void();
	}

	template<typename T, typename... Args>
	void read(T &a, Args &... args) {
		read(a);
		read(args...);
		return void();
	}

	inline int read() {
		int a;
		read(a);
		return a;
	}

	template<typename T>
	void print(T a) {
		cout << a;
		return void();
	}

	template<typename T, typename... Args>
	void print(T a, Args... args) {
		print(a);
		print(args...);
		return void();
	}
}

using fast::read;
using fast::print;
using us = unsigned;
constexpr int N = 400;
constexpr int mod = 1e9 + 7;
int n, q;
int a[N + 10];
int lr[2][N + 10][N + 10];
int rl[2][N + 10][N + 10];
int f[2][N + 10][N + 10];

inline int g(const int x, const int y) {
	return x * (x - 1) / 2 + (n - y + 1) * (n - y) / 2 + (y - x + 1) * (y - x + 2) / 2;
}

signed main() {
#ifndef IfFast
	ios::sync_with_stdio(false);
#endif
	// freopen("1.in", "r",stdin);
	read(n, q);
	for (us i = 1; i <= n; i++) {
		read(a[i]);
	}
	a[0] = a[n + 1] = inf;
	for (us i = 1; i <= n; i++) {
		int maxx = 0;
		for (us j = i; j <= n; j++) {
			maxx = max(maxx, a[j]);
			if (i == 1 && j == n) {
				f[0][i][j] = maxx;
			} else if (min(a[i - 1], a[j + 1]) > maxx) {
				f[0][i][j] = (maxx - min(a[i - 1], a[j + 1]) + mod) % mod;
			}
		}
	}
	for (us i = 1; i <= q; i++) {
		const us next = i & 1;
		const us now = next ^ 1;
		for (us j = 1; j <= n; j++) {
			for (us k = n; k >= j; k--) {
				rl[now][j][k] = rl[now][j][k + 1] + f[now][j][k] * (n - k) % mod;
				rl[now][j][k] %= mod;
			}
		}
		for (us j = 1; j <= n; j++) {
			for (us k = j; k <= n; k++) {
				lr[now][j][k] = lr[now][j - 1][k] + f[now][j][k] * (j - 1) % mod;
				lr[now][j][k] %= mod;
				f[next][j][k] = (f[now][j][k] * g(j, k) % mod + lr[now][j - 1][k] + rl[now][j][k + 1]) % mod;
			}
		}
	}
	for (us i = 1; i <= n; i++) {
		int ans = 0;
		for (us j = 1; j <= i; j++) {
			for (us k = i; k <= n; k++) {
				ans += f[q & 1][j][k];
				ans %= mod;
			}
		}
		print(ans, ' ');
	}
	return 0;
}

忽略幽默读入输出
还有第二道咋是个退紫的黑

posted @ 2025-10-29 16:18  愿初  阅读(14)  评论(0)    收藏  举报