codeforces 425E

题意:对于[l1, r1], [l2, r2]...[lm, rm]线段组成的一个集合S,我们定义f(S)为最大的不相交(没有任何公共点)线段数,现在给定n及k,n表示线段范围,即任何[li, ri]有1<=li<=ri<=n,求有多少个集合使得f(S) = k。

思路:刚看到题目感觉不会,也就不多想。。

         突然问了下小胖,小胖说他做过,不难。。然后我就慢慢想了。。

         仔细想想,确实不难。。

          假设现在已经给定了一个S,那么我们怎么求f(S)?

          很显然,我们可以贪心,按照r排序,那么我们每次只要取最小r的线段,删除覆盖的,依次做完最后取到的线段肯定最多。。

          这么以来对于任意一个集合S,我们肯定可以用最小的有用线段的右端点r来表示其状态。。

          所以用f[i][j]表示最后一个有用线段右端点为i,最长有j段不相交的线段的方案数

          则递推到f[k][j+1](k>i)的状态有:

           在【i+1, k】区间内一定选了至少一条以k为右端点的线段,选法2k-i - 1

           左端点在【1, i】右端点【i+1, k】的线段可以任意选不影响f值,有2(k-i)*i

           所以 f[k][j+1] += f[i][j] *(2k-i - 1) * 2(k-i)*i

code:

 1 #include <bits/stdc++.h>
 2 #define M0(x) memset(x, 0, sizeof(x))
 3 #define M 1000000007
 4 using namespace std;
 5 typedef long long ll;
 6 const int maxn = 512;
 7 int n, m;
 8 ll dp[512][512], p[300000];
 9 
10 void solve(){
11      if (m == 0){
12          puts("1");
13          return;
14      }
15      M0(dp);
16      dp[0][0] = 1;
17      p[0] = 1;
18      for (int i = 1; i <= n * n; ++i) p[i] = (p[i-1] << 1) % M;
19      ll tmp;
20      int c = 0;
21      for (int i = 0; i < n; ++i)
22          for (int j = 0; j < m; ++j) if (dp[i][j]){
23               c = 0;
24               for (int k = i+1; k <= n; ++k){
25                     c += i;
26                     tmp = dp[i][j] * (p[(k - i)]-1) % M * p[c] % M;
27                     dp[k][j+1] = (dp[k][j+1] + tmp) % M;
28               }
29          }
30      ll ans = 0;
31      for (int i = 1; i <= n; ++i){
32            tmp = p[(n-i) * i] * dp[i][m] % M;
33            ans = (ans + tmp) % M; 
34 //           printf("%d : %lld
",i,  ans);
35      }
36      cout << ans << endl;
37 }
38 
39 int main(){
40     freopen("a.in", "r", stdin);
41     while (scanf("%d%d", &n, &m) != EOF){
42          solve();
43     }
44     return 0;
45 }
View Code
原文地址:https://www.cnblogs.com/yzcstc/p/4065898.html