2020CCPC长春站 J


题意

在一张图中画圆,需要保证

  1. 每个圆圆心在(x)轴上,且是一个整数点

  2. 每个圆圆内任意一点的(x)坐标均在([0,n])

  3. 每个圆的半径是个整数,且不超过(5)

  4. 每个圆与其他任意一个圆的交点最多只能有一个

已知已经存在(k)个圆(保证初始图合法),求有多少种画法能够使得最后整张图满足上述条件

(什么也不画也是一种画法)




题解

(本题还有状压dp/记忆化搜索等解法)

由于每个圆圆心都是(x)轴上的整数点,且半径也是整数,故与(x)轴恒存在两个交点

所以可以将某个圆表示成一条线段((x,r) ightarrow[x-r,x+r])

那么限制也就变成了没有线段的顶点位于其余线段内

使用(dp[l][r][0])表示如果不画线段([l,r])代表的圆,有多少种画法

使用(dp[l][r][1])表示如果线段([l,r])代表的圆,有多少种画法

通过已经存在的圆,再定义两个数组

(used[l][r])表示原图中已经存在线段([l,r])代表的圆

(nouse[l][r])表示不能再画线段([l,r])代表的圆


对于每个已经存在的((x,r)),将其转化为线段([l,r]=[x-r,x+r])

直接标记(used[l][r]=true)

再对不能画的圆的左右端点进行枚举

如果(Lin[0,l-1], Rin[l+1,r-1])或者(Lin[l+1,r-1], Rin[r+1,n])

就表示线段([L,R])代表的圆与([l,r])代表的圆有两个交点,此时标记(nouse[L,R]=true)


接下来考虑区间dp

区间长度(len=r-l)从小((2))枚举到大((n)

则对于(dp[l][r][0]),可以由左顶点在(l)的可行方案加上右顶点在(r)的可行方案转移得来,以保证所有圆与([l,r])这个大圆不冲突

据区间dp的通用方法,其中一个方案数可以直接由长度(-1)直接转移得到(这里直接转移顶点在(l)的可行方案

所以得到(dp[l][r][0]+=dp[l][r-1][0]+dp[l][r-1][1])

此时对于顶点在(r)的可行方案,如果仍然用上述方法转移到(dp[l][r][0]),发现会在不画的情况下出现重复计数(即(dp[l][r-1][0])(dp[l+1][r][0])不互相独立)

所以对于另一个方案数,需要通过进一步枚举计算来得到

这里我们枚举存在一个圆,左端点为(m),右端点为(r),又因为题目中半径限制为(5),故枚举只需要进行至多(5)

转移方案为(dp[l][r][0]+=dp[m][r][1] imes(dp[l][m][0]+dp[l][m][1]))

对于(dp[l][r][1]),如果在线段([l,r])能够不冲突地放置一个圆,那么(dp[l][r][1]=dp[l][r][0])成立

其后,如果原图中已经存在([l,r])的圆,那么(dp[l][r][0]=0),表示这个圆不能不放


综上,转移方程为(以右端点开始枚举)

[dp[l][r][0]=sum left { egin{aligned} dp&[l][r-1][0]\ dp&[l][r-1][1]\ dp&[m][r][1] imes(dp[l][m][0]+dp[l][m][1]) end{aligned} ight . \ dp[l][r][1]= left { egin{aligned} dp[l][r][0]&, r-lle 10 && (r-l) mod 2=0\ 0&, else end{aligned} ight . \ dp[l][r][0]= left { egin{aligned} dp[l][r][0]&, used[l][r]=false\ 0&, else end{aligned} ight . ]

如果以左端点开始枚举,转移方程为

[dp[l][r][0]=sum left { egin{aligned} dp&[l+1][r][0]\ dp&[l+1][r][1]\ dp&[l][m][1] imes(dp[m][r][0]+dp[m][r][1]) end{aligned} ight . \ dp[l][r][1]= left { egin{aligned} dp[l][r][0]&, r-lle 10 && (r-l) mod 2=0\ 0&, else end{aligned} ight . \ dp[l][r][0]= left { egin{aligned} dp[l][r][0]&, used[l][r]=false\ 0&, else end{aligned} ight . ]




程序

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;

bool used[1050][1050];
bool nouse[1050][1050];
ll dp[1050][1050][2];

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    
    int n,k,x,r;
    cin>>n>>k;
    for(int i=1;i<=k;i++)
    {
        cin>>x>>r;
        used[x-r][x+r]=true;
        for(int R=x-r+1;R<x+r;R++)
            for(int L=0;L<x-r;L++)
                nouse[L][R]=true;
        for(int L=x-r+1;L<x+r;L++)
            for(int R=x+r+1;R<=n;R++)
                nouse[L][R]=true;
    }
    
    for(int i=0;i<n;i++)
        dp[i][i+1][0]=1;
    
    for(int len=2;len<=n;len++)
    {
        for(int l=0,r=len;r<=n;l++,r++)
        {
            if(nouse[l][r])
                continue;
            
            dp[l][r][0]=(dp[l+1][r][0]+dp[l+1][r][1])%mod;
            
            for(int m=l+2;m<=min(l+10,r);m+=2)
                dp[l][r][0]=(dp[l][r][0]+dp[l][m][1]*(dp[m][r][0]+dp[m][r][1]))%mod;
            
            if((r-l)%2==0&&r-l<=10)
                dp[l][r][1]=dp[l][r][0];
            
            if(used[l][r])
                dp[l][r][0]=0;
        }
    }
    
    cout<<(dp[0][n][0]+dp[0][n][1])%mod<<'
';
    
    return 0;
}
原文地址:https://www.cnblogs.com/stelayuri/p/13954926.html