折半+dp之限制转状态+状压:CF1767E

https://vjudge.net/problem/CodeForces-1767E/origin

首先40,必然折半。然后怎么做?

分析性质。每次可以走1步or2步,等价什么?等价任意相邻2个必选一个!然后就可以建图

这个图是个限制图,我们折半后可以进行状压。dp的过程是限制转状态。

首先分别的,前后内部都必须满足。然后对于交织在两部分的限制,我们枚举其中一边哪些不选,必然可以对应另外那边哪些必选。得到的集合求其最小合法超集即是答案。

#include
using namespace std;
#define int long long
inline int read(){int x=0,f=1;char ch=getchar(); while(ch<'0'||
ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define Z(x) (x)*(x)
#define pb push_back
//mt19937 rand(time(0));
//mt19937_64 rand(time(0));
//srand(time(0));
#define N 300010
#define M 21
//#define mo
int n, m, i, j, k, T;
int ans, f[1<<M], b[M<<1][M<<1]; 
int a[N], s, t, mid, cost[M<<1]; 
int k1, k2, s1, s2; 

void cun(int x, int y) {
	b[x][y]=b[y][x]=1; 
}

int check(int s) {
	if(k1<mid && (s&s1)==0) return 0; 
	if(k2<mid && (s&s2)==0) return 0; 
	for(int i=0; i<mid; ++i)
	 	for(int j=0; j<mid; ++j)
	 		if(b[i][j] && (!(s&(1<<i)) && !(s&(1<<j)))) return 0; 
	return 1; 
}

int check2(int s) {
	if(k1>=mid && (s&s1)==0) return 0; 
	if(k2>=mid && (s&s2)==0) return 0; 
	for(int i=mid; i<m; ++i)
		for(int j=mid; j<m; ++j) 
			if(b[i][j] && (!(s&(1<<i-mid)) && !(s&(1<<j-mid)))) return 0; 
	return 1; 
}

void Least(int s, int &t) {
	for(int i=mid; i<m; ++i)
		for(int j=0; j<mid; ++j) {
//			printf("(%d %d) %d\n", i, j); 
			if(b[i][j] && !(s&(1<<i-mid))) t|=(1<<j); 
		}
			
}

signed main()
{
//	freopen("in.txt", "r", stdin);
//	freopen("out.txt", "w", stdout);
//	T=read();
//	while(T--) {
//
//	}
	n=read(); m=read(); mid=m/2; ans=1e18; 
//	printf("%lld\n", mid); 
	for(i=1; i<=n; ++i) a[i]=read()-1; 
	s1=(1<<a[1]); s2=(1<<a[n]); 
	k1=a[1]; k2=a[n]; 
	if(k1>=mid) s1=(1<<a[1]-mid); 
	if(k2>=mid) s2=(1<<a[n]-mid); 
//	printf("%d %d | %d %d\n", k1, k2, s1, s2); 
	for(i=1; i<n; ++i) cun(a[i], a[i+1]); 
	for(i=0; i<m; ++i) cost[i]=read(); 
	memset(f, 0x3f, sizeof(f)); 
	for(s=(1<<mid)-1; s>=0; --s) {
		if(check(s)) {
//			printf("> %d ", s); 
			for(i=k=0; i<mid; ++i) if(s&(1<<i)) k+=cost[i]; 
//			printf("%lld\n", k); 
			f[s]=min(f[s], k); 
			
		}
		for(i=0; i<mid; ++i) if(s&(1<<i))
				f[s-(1<<i)]=min(f[s-(1<<i)], f[s]); 
	}
		
	for(s=0; s<(1<<m-mid); ++s) {
		if(check2(s)) {
//			printf(">> %d ", s);
			t=0; Least(s, t); 
			for(i=k=0; i<m-mid; ++i) if(s&(1<<i)) k+=cost[i+mid]; 
//			printf("%lld %lld\n", k, t); 
			ans=min(ans, k+f[t]); 
		}
	}
	printf("%lld", ans); 
	return 0;
}

 

你可能感兴趣的:(折半,状压,限制转状态,dp)