区间Dp

区间Dp

Game Rooms

给你n层楼,每一层有一个喜欢游泳的人数和打乒乓的人数,你可以再每一层中建造游泳馆后者乒乓球馆,问如何建造可以使得他们需要爬楼的代价最小。也就是人数*要爬的楼数。

一点小拓展

二阶前缀和:对于一个 i 点,如果到 1 的代价是 i 的话,那么前 i 个点的代价和 也就是二阶前缀和。二阶前缀和说简单点就是一阶前缀和的前缀和。

LR 的二阶前缀和:注意蛤,这个可能和一维的有点差别,需要看面积大小来判断L和R。

思路

对于一个点,分别考虑在这个点建造两种不同的情况,然后枚举他的上一个最近的不同类型的点 j ,他们之间的cost 也就是 j+1到 i 这一段连续的相同的区间的代价。

我先找到他们的中点mid,然后 l 到 mid 往下走然后 mid+1 到 r 往上走。

d[i][0]=min(d[i][0],d[j][1]+get(j+1,i,0));
// 表示到达第i层 同时是 0/1 类型的代价,如果是0,那么前面一段中相同的可以不用考虑,因为他们可以取自己,
// 只要考虑前面一段中和当前相同的长度,也就是枚举 出不相同的位置,然后假设他后面这一段都是相同的类型,然后考虑 j+1 ~ i 中这一段 中另一种的情况,假设当前 j+1 ~ i 都是0那么他的代价肯定是 j+1 ~ i 这一段中 1 的最小代价。
d[i][1]=min(d[i][1],d[j][0]+get(j+1,i,1));

最后的结果也就是 : min(d[n][0],d[n][1]).

Code

const int N=5e3,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int pr1[N][2],pr2[N][2];
int sf1[N][2],sf2[N][2];
int a[N],b[N];
int d[N][2];
int T,n;

void inti()// 特别处理一下n+1 和 0 因为 当i等于 n 和1 的时候会用到
{
	memset(d,0x3f,sizeof d);
	d[0][0]=d[0][1]=0;	
	sf1[n+1][0]=sf1[n+1][1]=sf2[n+1][0]=sf2[n+1][1]=0;
	pr1[n+1][0]=pr1[n+1][1]=pr2[n+1][0]=pr2[n+1][1]=0;
}
// 这里上面的图有说
int getpr(int l,int r,int k)
{
	return pr2[r][k]-pr2[l-1][k]-(r-l+1)*pr1[l-1][k];//前缀和
}

int getsf(int l,int r,int k)
{
	return sf2[l][k]-sf2[r+1][k]-(r-l+1)*sf1[r+1][k];//后缀和
}

int get(int l,int r,int k)
{
	// cout<<l<<" "<<r<<endl;
	if(l==1&&r==n)return 1e18;
	if(l==1)return pr2[r][!k];
	if(r==n)return sf2[l][!k];
	// cout<<1<<endl;
    
   // 考虑中间位置,因为中间位置往下的点肯定都往下走,中间位置向上的肯定都往上走。这样才是最小的花费
	int mid=l+r>>1;
	int t=0;
	t+=getsf(l,mid,!k);
	t+=getpr(mid+1,r,!k);
	return t;
}

void solve()
{
	
	cin>>n;
	inti();
    // pr1 pr2 代表 0/1 这种馆的一阶二阶前缀
	for(int i=1;i<=n;i++)
	{
		cin>>a[i]>>b[i];
		pr1[i][0]=pr1[i-1][0]+a[i];
		pr1[i][1]=pr1[i-1][1]+b[i];
	}
	for(int i=1;i<=n;i++)pr2[i][0]=pr2[i-1][0]+pr1[i][0],pr2[i][1]=pr2[i-1][1]+pr1[i][1];
	
    
    // 初始化,因为有一半往上一半往下,所以要预处理 二阶前缀 和 二阶后缀,
    // sf1 sf2 代表 0/1 这种馆的一阶二阶后缀
	for(int i=n;i>=1;i--)
	{
		sf1[i][0]=sf1[i+1][0]+a[i];
		sf1[i][1]=sf1[i+1][1]+b[i];
		sf2[i][0]=sf2[i+1][0]+sf1[i][0];
		sf2[i][1]=sf2[i+1][1]+sf1[i][1];		
	}
	
    
    // Dp部分
	for(int i=1;i<=n;i++)
	{
		d[i][0]=d[i][1]=1e10;//初始化
		for(int j=0;j<i;j++)
		{
			d[i][0]=min(d[i][0],d[j][1]+get(j+1,i,0));
            // 这一段中的 1 肯定要么往下要么往上。
			d[i][1]=min(d[i][1],d[j][0]+get(j+1,i,1));
             // 这一段中的 0 肯定要么往下要么往上。
		}
		// cout<<min(d[i][0],d[i][1])<<endl;
	}
	cout<<min(d[n][0],d[n][1])<<endl;
}

signed main()
{
  // freopen("dorm.in","r",stdin);
  // freopen("dorm.out","w",stdout);
	kd;
	int _;_=1;
	while(_--)solve();	
	return 0;
}


Coloring Brackets

题意:

给一个合法的括号序列,问有多少种合法的染色方案:

  1. 一个括号可以染成红色、蓝色或者不染色
  2. 一对匹配的括号需要且只能将其中一个染色
  3. 相邻两个括号颜色不能相同(但都可以不染色)

思路

然后想到 用 d[x][y][c1][c2]以 x 和 y 为首尾同时 x y 的染色情况是 c1 c2 的方案数。然后在寻找做右端点的时候记录一下每个左括号匹配的右括号下标,如果可以匹配的话,就直接枚举他们的右边和左边的染色情况,如果没有完全匹配,那么就需要先递归 x ~ r[x], r[x]+1 ~ y.

转移方程:
刚好匹配:
   	 if(match[x]==y)
    {
        dfs(x+1,y-1);
        for(int i=0;i<=2;i++)
        {
            for(int j=0;j<=2;j++)
            {
                if(i!=1)d[x][y][1][0]=(d[x][y][1][0]+d[x+1][y-1][i][j])%mod;
                if(i!=2)d[x][y][2][0]=(d[x][y][2][0]+d[x+1][y-1][i][j])%mod;
                if(j!=1)d[x][y][0][1]=(d[x][y][0][1]+d[x+1][y-1][i][j])%mod;
                if(j!=2)d[x][y][0][2]=(d[x][y][0][2]+d[x+1][y-1][i][j])%mod;
            }
        }
    }

无法匹配:
//如果无法匹配的话,也就是区间Dp中的合并区间的内容,枚举出 ( ) ( )酱紫的按顺序来的颜色情况。然后判断一下 他们交界处的 颜色是不是相同。
dfs(x,match[x]);
dfs(match[x]+1,y);
for(int i=0;i<=2;i++)
{
    for(int j=0;j<=2;j++)
    {
        for(int k1=0;k1<=2;k1++)
        {
            for(int k2=0;k2<=2;k2++)
            {
                if((j==1&&k1==1)||(j==2&&k1==2))continue;
                // 这里为啥没有考虑一个括号全被染色的情况呢
                //因为它如果不合法 他的方案数会是0,所以就没有考虑这个情况了
 				d[x][y][i][k2]=(d[x][y][i][k2]+(d[x][match[x]][i][j]*d[match[x]+1][y][k1][k2])%mod)%mod;
                // cout<<d[x][y][i][k2]<<endl;
            }
        }
    }
}

Code

const int N=1000,mod=1e9+7;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int match[N];
char s[N];
stack<int>q;
int d[N][N][3][3];
//  0不染色 1蓝色 2红色。

void dfs(int x,int y)
{
	if(x+1==y)
	{
		d[x][y][0][1]=d[x][y][1][0]=d[x][y][2][0]=d[x][y][0][2]=1;
		return;
	}
	if(match[x]==y)
	{
		dfs(x+1,y-1);
		for(int i=0;i<=2;i++)
		{
			for(int j=0;j<=2;j++)
			{
				if(i!=1)d[x][y][1][0]=(d[x][y][1][0]+d[x+1][y-1][i][j])%mod;
				if(i!=2)d[x][y][2][0]=(d[x][y][2][0]+d[x+1][y-1][i][j])%mod;
				if(j!=1)d[x][y][0][1]=(d[x][y][0][1]+d[x+1][y-1][i][j])%mod;
				if(j!=2)d[x][y][0][2]=(d[x][y][0][2]+d[x+1][y-1][i][j])%mod;
			}
		}
	}
	else 
	{
		dfs(x,match[x]);
		dfs(match[x]+1,y);
		for(int i=0;i<=2;i++)
		{
			for(int j=0;j<=2;j++)
			{
				for(int k1=0;k1<=2;k1++)
				{
					for(int k2=0;k2<=2;k2++)
					{
						if((j==1&&k1==1)||(j==2&&k1==2))continue;
						d[x][y][i][k2]=(d[x][y][i][k2]+(d[x][match[x]][i][j]*d[match[x]+1][y][k1][k2])%mod)%mod;
						// cout<<d[x][y][i][k2]<<endl;
					}
				}
			}
		}
	}
}

void solve()
{
	cin>>s+1;
	int n=strlen(s+1);
	for(int i=1;i<=n;i++)
	{
		if(s[i]=='(')q.push(i);
		else match[q.top()]=i,match[i]=q.top(),q.pop();
	}
	int ans=0;
	dfs(1,n);
	for(int i=0;i<=2;i++)
	{
		for(int j=0;j<=2;j++)
		{
			ans=(ans+d[1][n][i][j])%mod;
		}
	}
	cout<<ans<<endl;
	
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}
posted @   黄小轩  阅读(24)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示