codeforces_Round#243(Div 1 )_E dp

看着比较吓人的dp。大意是:对于区间集合S = { [L1,R1],[L2,R2],[L3,R3]...[Lk,Rk] }, (1<=Li<=Ri<=n),定义f(S)为:可以从S中选出的互不相交区间的数量,的最大值。给定n和p,问满足f(S) = p 的 S 的数量有多少个。

这里S的区间可以是冗余的。这里是f(S)=p而不是|S| = p。

想想一下对于S,我们求解f(S)的过程。这个过程是贪心的,即在互不相交区间数量尽可能多的情况下,我们让最靠右的区间的右端点最靠左。这一点启发下面的dp。


粗略的想一下,假设用F[i][j]表示n = i时满足f(S) = j 的S的数量,则F[i[[j] 分为两类:i作为区间右端点存在和i不是区间右端点。不作为的时候很简单,直接取来F[i-1][j];当i作为区间右端点出现在S中的时候,假设我们枚举i可以向左伸展的最远距离[l,i],发现又分两种情况:[l,i] 区间的出现使得 j 增大,这时候要F[i-1][j-1]来更新;[l,i] 的出现没有使得j增大,这时候要靠F[i-1][j]更新。但是,我们怎么知道F[i-1][j-1]中的哪些 S+[l,i] 之后变大呢?其实可以想到,我们用贪心求f(S)的时候,选取了一个区间集合s,这个集合的一个特征是,它是最靠左的。设其最靠右的线段为[L,R], 那么R<l, f(S+[l,i]) = f(S)+1;否则f(S+[l,i]) = f(S)。 在满足f(S)的时候,所取的区间尽可能靠左。于是对每个S记录一个左值left, left定义为:left = min{ max{ R | [L,R]∈subS∈f(S)} }  left = min{ right(subS) | subS∈f(S) } , right(s) = max{R|[L,R]∈subS}, 简言之,最右边的区间最靠左。这样,对于区间[l,i],当l>left是,j变大。

于是给F加一维,得到状态定义如下:
F[i][j][k]表示n == j 时满足 f(S) = k && left(S) = i 的S的数量。其中,k<=i<=j.

转移方程:F[i][j][k] = F[i][j -1][k] * 2^i. 

边界:F[i][i][k] (1<=i<=k<=n).

ps.解释一下若以j为区间右端点,则其左端点l<=i。[1,j],[2,j]...[i,j]互不干扰,共2^i-1中组合;若不宜j为右端点,则直接取F[i][j-1][k],1种可能。
令:g[i][k] = F[i][i][k].对于g[i][k]的求解,枚举g[s][k-1],(k-1<=s<i), 则s+1...i中每个值和1~s组成区间不会影响k,有(2^s)^(i-s)中组合;为了从k-1转移到k,必须至少在[s+1,i]...[i,i]中选一个,有2^(i-s)-1中组合。由乘法原理,有(2^s)^(i-s)*(2^(i-s)-1)*g[s][k-1]。

于是完整表述为:

  1. 转移方程:F[i][j][k] = F[i][j -1][k] * 2^i.  边界:F[i][i][k] = g[i][k].
  2. 目标函数:ans = sigma{ F[i][n][k], k<=i<=n}
  3. 转移方程:g[i][k] = sigma{ (2^s)^(i-s)*(2^(i-s)-1)*g[s][k-1],0<k-1<=s<i) },边界:g[i][i] = 2^i-1

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <stack>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <algorithm>
#include <cmath>
#define clr(A) memset(A,0,sizeof(A))
#define mm 505
#define eps  (1e-8)
#define gcd(A,B) __gcd(A,B)
using namespace std;
typedef long long LL;
typedef pair<int,int > P;
typedef unsigned int ULL;
const int INF = 10000000;
const int mod = 1000000007;
LL F[mm][mm];
LL f[mm*mm];
int main()
{
     int n,K;
     while(~scanf("%d%d",&n,&K)){
         clr(F);
        f[0] = 1;
        for(int i=1;i<=n*n;i++)
        f[i] = f[i-1]*2 % mod;
        F[0][0] = 1;
        for(int i =1;i<=n;i++)F[i][1] = (f[i]-1) % mod;

        for(int i = 1;i<=n;i++)
        for(int k = 2;k<=i;k++)
        for(int s = k-1;s<i;s++)
        F[i][k] = (F[i][k]+F[s][k-1]*f[i*s-s*s] % mod*(f[i-s]-1) % mod) % mod;

        int ans = 0;
        for(int i = K;i<=n;i++)
        ans = (ans+f[i*(n-i)]*F[i][K] % mod)  % mod;
        printf("%d\n",ans);
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值