WEEK16 周记 限时CSP模拟——区间动态规划_宇宙狗的危机

一、题意

1.简述

在这里插入图片描述

2.样例

Input1

1
6
3 6 9 18 36 108

Output1

Yes

Input2

2
2
7 17
9
4 8 10 12 15 18 33 44 81

Output2

No
Yes

Hint

在这里插入图片描述

二、算法

主要思路

第一种思路,暴力模拟。

int insert(int root,int pelt,int st,int ed){
    //root是当前子树的根节点,pelt是父节点的元素值,st和ed是该子树的元素范围
	int lth = ed-st;
	if(lth<=0) return 1;
	int rtn;
	for(int i=st;i<ed;i++){
		if(root == 0||gcd(a[i],pelt)>1){
			int r1 = insert(1,a[i],st,i);
			int r2 = insert(1,a[i],i+1,ed);
			if(r1&&r2) return 1;
		}
	}
	return 0;
}

思路就是一个子树能不能行,就是看其左右子树是不是都行,也即存在与根节点 g c d > 1 gcd>1 gcd>1的左右子树。
显然,这种递归的思路,与DP的思路的区别在于,递归会进行很多重复的操作。所以会超时。只能过前2个点( n ≤ 5 n\le5 n5)。


换用区间DP的思路。
一开始考虑的状态和转移方程为,设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]为以 [ a i , a j ] [a_i,a_j] [ai,aj]为元素范围(数据是升序的),以 a k a_k ak为根节点的树是否能成立。
转移方程为:当 l e n g t h ( [ i , j ] ) = 1 length([i,j])=1 length([i,j])=1时, f = 1 f=1 f=1。当 l e n g t h ( [ i , j ] ) ≤ 1 length([i,j])\le1 length([i,j])1时, f [ i ] [ j ] [ k ] = f [ i ] [ k − 1 ] [ k k 1 ] ∣ ∣ f [ k + 1 ] [ j ] [ k k 2 ]        g c d ( a [ k k 1 ] , a [ k ] ) > 1 且 g c d ( a [ k k 2 ] , a [ k ] ) > 1 f[i][j][k]=f[i][k-1][kk_1]||f[k+1][j][kk_2] \ \ \ \ \ \ gcd(a[kk_1],a[k])\gt 1 且 gcd(a[kk_2],a[k])\gt 1 f[i][j][k]=f[i][k1][kk1]f[k+1][j][kk2]      gcd(a[kk1],a[k])>1gcd(a[kk2],a[k])>1
当然,上述式子中如果 k − 1 k-1 k1 k + 1 k+1 k+1超出了 [ i , j ] [i,j] [i,j]的范围,则直接将对应的 f f f项设置为 1 1 1,因为空子树是成立的。
显然,这样的DP需要 f f f在每个维度都需要700个空间,所以总共的空间 700 × 700 × 700 700 \times 700 \times 700 700×700×700是大于限制的。
如果将数组的空间变小一点能过前5个点。

void getF(){
	for(int i=1;i<=n;i++){
		f[i][i][i] = 1;
	}
	for(int lth=2;lth<=n;lth++){
		for(int i=1;i<=n-lth+1;i++){
			int ed = i+lth-1;
			for(int k=i;k<=ed;k++){
				bool lok = 0,rok = 0;
				if(k-1>=i){
					for(int kk=i;kk<=k-1;kk++){
						if(f[i][k-1][kk]){
							if(gcd(a[k],a[kk])>1){
								lok = 1;break;
							}
						}
					}
				}
				else lok = 1;
				if(k+1<=ed){
					for(int kk=k+1;kk<=ed;kk++){
						if(f[k+1][ed][kk]){
							if(gcd(a[k],a[kk])>1){
								rok = 1;break;
							}
						}
					}
				}
				else rok = 1;
				f[i][ed][k] = lok&&rok;
			}
		}
	} 
}

继续换一种DP的思路。
状态:设 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]为以 [ a i , a j ] [a_i,a_j] [ai,aj]为元素范围(数据是升序的),当最后一维为 0 0 0时,表示以 a i a_i ai为根,以 [ a j , a i − 1 ] [a_j,a_{i-1}] [aj,ai1]为左子树的子树成立(这里的成立只需要左子树成立且左子树的根节点和当前子树的根节点 g c d > 1 gcd>1 gcd>1);同理,当最后一维为 1 1 1时,表示以 a i a_i ai为根,以 [ a i + 1 , a j ] [a_{i+1},a_j] [ai+1,aj]为右子树的子树成立。
转移方程: f [ i − 1 ] [ j ] [ 1 ] = f [ i − 1 ] [ j ] [ 1 ] ∣ ∣ g c d ( a [ k ] , a [ i − 1 ] )      f [ k ] [ i ] [ 0 ] = 1   a n d   f [ k ] [ j ] [ 1 ] = 1 f[i-1][j][1]=f[i-1][j][1]||gcd(a[k],a[i-1]) \ \ \ \ f[k][i][0]=1\ and\ f[k][j][1]=1 f[i1][j][1]=f[i1][j][1]gcd(a[k],a[i1])    f[k][i][0]=1 and f[k][j][1]=1 f [ j + 1 ] [ i ] [ 0 ] = f [ j + 1 ] [ i ] [ 0 ] ∣ ∣ g c d ( a [ k ] , a [ j + 1 ] )      f [ k ] [ i ] [ 0 ] = 1   a n d   f [ k ] [ j ] [ 1 ] = 1 f[j+1][i][0]=f[j+1][i][0]||gcd(a[k],a[j+1]) \ \ \ \ f[k][i][0]=1\ and\ f[k][j][1]=1 f[j+1][i][0]=f[j+1][i][0]gcd(a[k],a[j+1])    f[k][i][0]=1 and f[k][j][1]=1
这样的方程只需要3层循环就够了,最后一层循环为对 k k k的遍历。而上次一个DP算法,则需要4层循环,所以降低了时间复杂度。但还是 O ( n 3 ) O(n^3) O(n3)

三、代码

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<map>
#include<vector>
#include<queue>
using namespace std;
int gcd(int a,int b){return b == 0 ? a : gcd(b,a%b);}
int a[710]; 
int n; 
bool f[710][710][2];//[0]表示ai为根的子树,是否左子树aj ai-1能够成立,[1]则反之 
bool gcdval[710][710];  //ai与aj是否gcd>1 
void getGcd(){
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			gcdval[i][j] = gcd(a[i],a[j])>1?1:0;
			gcdval[j][i] = gcdval[i][j]; 
		}
	}
}
void getF(){
	for(int i=0;i<=n;i++){
		f[i][i][0] = 1; f[i][i][1] = 1;
	}
	for(int lth=1;lth<=n;lth++){
		for(int i=1;i<=n-lth+1;i++){
			int ed = i+lth-1;
	
			for(int k=i;k<=ed;k++){
				//f[i][ed][k] = 0;    //更新之前先清空,可能没用
				if(f[k][i][0]&&f[k][ed][1]){
					f[i-1][ed][1] |= gcdval[i-1][k];
					f[ed+1][i][0] |= gcdval[ed+1][k];
				}
			}
		}
	} 
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
		}
		memset(f,0,sizeof(f));
		memset(gcdval,0,sizeof(gcdval));
		getGcd();
		getF();
		bool isok = 0;
		for(int k=1;k<=n;k++){
			if(f[k][1][0]&&f[k][n][1]){
				isok = 1;break;
			}
		}
		if(isok)
			printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}
/*
1
6
3 6 9 18 36 108
*/
/*
2
2
7 17
9
4 8 10 12 15 18 33 44 81
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值