J. Abstract Painting
tag:dp
题目链接
题意:
有个很抽象的人要画一幅抽象画,这个抽象画只需要画圆圈就完事了(我上我也行)
需要满足以下条件
- 圆心都必须在x轴上的[1,n]上,且必须都是整数点。(2≤n≤10^3)
- 圆的半径为整数,只能取{1,2,3,4,5}。
- 圆和圆之间最多一个交点,即要么相切要么不相交。
此外原本x轴上就有一些圆
我们需要求可行的绘画方案数(ps: 一个圆都不画也算一种方案)
做法:
这道题的做法主要有区间dp和状压dp两种,我认为区间dp好写很多,下面我们只讲区间dp的做法
首先我们可以将圆抽象为区间,圆心x,半径r,我们可以表示为[x-r,x+r]
如果两个区间交叉着了,那么显然不合法
在进行区间dp之前我们需要两个数组来辅助,fix[l][r]标记原本就有的圆,ban[l][r]标记不合法的圆
我们设 dp[l][r][0/1] 表示区间l,r之内的方案数,0、1表示这些方案中存不存在圆[l,r]
首先我们可以想到一个很简单的转移,如果我们可以画出一个[l,r]的圆,那么显然 dp[l][r][1] = dp[l][r][0]
剩下的转移方法就比较细节,请配合代码注释食用
代码:
#define fst std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout << std::fixed << std::setprecision(20)
#define le "\n"
#define ll long long
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
const int mod=1e9+7;
bool fix[1050][1050],ban[1050][1050];//标记原本就有的圆,
ll dp[1050][1050][2];
int main(){
int n,k,x,r;
cin>>n>>k;
for(int i=1;i<=k;i++){
cin>>x>>r;
fix[x-r][x+r]=true;
for(int i=0;i<x-r;i++){
for(int j=x-r+1;j<x+r;j++){
ban[i][j]=true;
}
}
for(int i=x-r+1;i<x+r;i++){
for(int j=x+r+1;j<=n;j++){
ban[i][j]=true;
}
}
}
for(int i=0;i<n;i++) dp[i][i+1][0]=1;//最小的圆是[i,i+2]
for(int len=2;len<=n;len++){
for(int l=0,r=len;r<=n;l++,r++){
if(ban[l][r]) continue;
dp[l][r][0]=(dp[l+1][r][0]+dp[l+1][r][1])%mod;
//首先继承一下 [l+1,r] 的方案数,现在左边多了一个点l,我们固定左端点l,枚举右端点来更新状态
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];
//能画个左右端点为l,r的圆,那就直接套上去,此时画或者不画方案数一致
if(fix[l][r]) dp[l][r][0]=0;
//原本就有画在l,r的园,不可能不画
}
}
cout<<(dp[0][n][0]+dp[0][n][1])%mod<<le;
return 0;
}
标签:1050,std,int,Abstract,2020,区间,Painting,fix,dp
From: https://www.cnblogs.com/touchfishman/p/17125974.html