[校内模拟]点

Description

数轴上有n个点,第i个点的坐标为xi
你需要把每个点左移d或者右移d,然后用一些线段去覆盖所有点
一条线段[l,r]的代价为a+b(r-l)
求将所有点覆盖的最小代价
n,d,xi<=150

Solution

niubi题
先把d*2,问题变成,有n个恋恋,每个恋恋可以向左移动d,或者不动
考虑d小的情况,我们可以设F[i][s]表示当前做到位置i,i往左d个位置的点被覆盖的状态为s
设m=max(xi),复杂度为O(m2^d)
考虑d大的情况,我们把所有数按%d分类
考虑如下的矩阵
0,d,2d,3d…
1,d+1,2d+1,3d+1…
2,d+2…

上一个Dp相当于从上往下再从左往右Dp
当d大的时候我们可以从左往右再从上往下Dp
注意最后一行和第一行也是连着的于是我们需要预先知道第一行的状态
于是复杂度为O(m2^(2m/d))
平衡一下,总复杂度为 O ( m 2 2 m ) O(m2^{\sqrt {2m}}) O(m22m )

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

const int S=(1<<18)+5,M=150,inf=1e9;

int n,d,a,b,x,f[S],g[S];
bool vis[M<<2];

int main() {
	freopen("point.in","r",stdin);
	freopen("point.out","w",stdout);
	scanf("%d%d%d%d",&n,&d,&a,&b);d<<=1;b=min(a,b);
	fo(i,1,n) {scanf("%d",&x);vis[x+d]=1;}
	int ans=inf;
	if (d<=18) {
		fo(s,0,(1<<d)-1) f[s]=inf;f[0]=0;
		fo(i,1,M+d) {
			fo(s,0,(1<<d)-1)
				if (vis[i]&&!(s&1)) g[s]=f[s>>1|(1<<d-1)];
				else g[s]=min(f[s>>1],f[s>>1|(1<<d-1)])+(s&1?(s&2?b:a):0);
			fo(s,0,(1<<d)-1) f[s]=g[s];
		}
		fo(s,0,(1<<d)-1) ans=min(ans,f[s]);
	} else {
		int w=(M+d)/d+1;
		fo(t,0,(1<<w)-1) {
			bool ok=1;
			fo(i,1,w-1) if (vis[i*d]&&!(t>>i&1)&&!(t>>(i-1)&1)) {ok=0;break;}
			if (!ok) continue;
			fo(s,0,(1<<w)-1) f[s]=inf;
			int ss=0;fo(i,0,w-1) ss=ss<<1|(t>>i&1);
			f[ss]=0;fo(i,0,w-1) if (t>>i&1) f[ss]+=a;
			for(int i=1;;i+=d) {
				if (i>w*d) i=i%d+1;
				if (i==d) break;
				fo(s,0,(1<<w)-1)
					if (vis[i]&&!(s&1)&&!(s&2)) g[s]=inf;
					else g[s]=min(f[s>>1]+(s&1?a:0),f[s>>1|(1<<w-1)]+(s&1?b:0))
					+(i%d==d-1&&(t>>(i/d+1)&1)&&(s&1)?b-a:0);
				fo(s,0,(1<<w)-1) f[s]=g[s];
			}
			fo(s,0,(1<<w-1)) ans=min(ans,f[s]);
		}
	}
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值