[NOI2009]诗人小G(决策单调性)

题意

有n个字符串,将其按顺序分组,每组的代价是总长度与常数L的差的绝对值的P次方,要使所有组的代价之和最小

a n s = ∑ k ( ∑ i ∈ g r o u p k l e n [ i ] ) P ans=\sum_{k}(\sum_{i \in group_k}len[i])^P ans=k(igroupklen[i])P

最小化 a n s ans ans

思路

首先我们考虑暴力。

s u m [ i ] = ∑ j = 1 i l e n [ j ] sum[i]=\sum_{j=1}^{i}len[j] sum[i]=j=1ilen[j] f [ i ] f[i] f[i]表示前i个字符串分组的最小价值。

那么

f [ i ] = m i n j = 0 j &lt; i ( f [ j ] + ∣ s u m [ i ] − s u m [ j ] − L ∣ P ) f[i]=min_{j=0}^{j &lt; i}(f[j]+|sum[i]-sum[j]-L|^P) f[i]=minj=0j<i(f[j]+sum[i]sum[j]LP)

然后我们来优化这个 O ( n 2 ) O(n^2) O(n2)的DP。

我们可以发现 y = ∣ k 1 − x ∣ P + k 2 y=|k_1-x|^P+k_2 y=k1xP+k2形如( k 1 , k 2 k_1,k_2 k1,k2是常数)(P=1的时候变成直线就好了):

在这里插入图片描述

假如 k 1 = s u m [ j ] − L , k 2 = f [ j ] , x = s u m [ i ] k_1=sum[j]-L,k_2=f[j],x=sum[i] k1=sum[j]L,k2=f[j],x=sum[i],那么画出一堆不同的 s u m [ j ] sum[j] sum[j]对应的函数应该是这样的:

在这里插入图片描述
由于我们要最小化 f [ i ] f[i] f[i],所以我们将会取 x = s u m [ i ] x=sum[i] x=sum[i]时函数值最小的那个。

直接观察发现,因为两个函数的形状是一样的,不同之处只在于位置,那么两个函数肯定是在某一点相交,然后在这个店左侧一个函数值小,右侧另一个小。

或者这么说,这些长得像二次函数的函数满足斜率递增(导函数单调递增我并不会求导所以不想这么说)。所以 x x x相同时,不论 x x x为何值,两个函数斜率的大小关系是固定的,通俗地说就是一个函数一直增长得比另一个快。

所以我们可以用决策单调性来优化这个DP。

在单调队列里我们存下一些函数,然后存下他到什么时候会被下一个函数超过从而再也没有翻身的机会。

假如当 x = s u m [ i ] x=sum[i] x=sum[i]已经大于队头被超过的位置了,那么 h e a d + + head++ head++。最后用队头更新 f [ i ] f[i] f[i]

假如新加的函数超过 q u e [ t a i l ] que[tail] que[tail]函数的位置要小于 q u e [ t a i l ] que[tail] que[tail]超过 q u e [ t a i l ] − 1 que[tail]-1 que[tail]1的位置,那么 q u e [ t a i l ] que[tail] que[tail]也是没用的,因为函数将会长成这个样子,那么蓝色的就是被丢掉的 q u e [ t a i l ] que[tail] que[tail]

在这里插入图片描述

注意

1.果然还是画图比较好理解,当时做的时候想得死去活来。。。

2.这道题虽然限制了 a n s &gt; 1 0 18 ans &gt; 10^{18} ans>1018就不用再做,但是要是把大于 1 0 18 10^{18} 1018的数都设成一个常数 i n f inf inf的话是会出锅的,因为在找函数交点的时候不管用 &gt; &gt; >还是 ≥ \ge 都会难以判断一些情况,在这里不再赘述。所以暴力开 l o n g &ThickSpace; d o u b l e long \; double longdouble就好了。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long double LD;
typedef long long LL;
const LL inf = 1e18+1000;
const int N = 1e5+10, L = 40;
int T, n, l, p, len[N];
char s[N][L];
LD f[N];
int pre[N], que[N], h, t, bey[N];

inline LD fpow(LD x, int y){
	LD ret = 1;
	while (y){
		if (y&1) ret *= x;
		x = x*x;
		y >>= 1;
	}
	return ret;
}

LD get_val(int leni, int j)
{
	return f[j]+fpow(abs(leni-len[j]-l-1), p);
}

int get_bey(int j, int k)
{
	int l = 1, r = len[n], mid, ret = r+1;
	while (l <= r){
		mid = (l+r)>>1;
		if (get_val(mid, k) <= get_val(mid, j))
			ret = mid, r = mid-1;
		else l = mid+1;
	}
	return ret;
}

void print_ans(int now)
{
	if (now == 0) return;
	print_ans(pre[now]);
	for (int i = pre[now]+1; i <= now; ++ i){
		printf("%s", s[i]);
		if (i != now) putchar(' ');
	}
	puts("");
}

int main()
{
	scanf("%d", &T);
	for (; T--; ){
		scanf("%d%d%d", &n, &l, &p);
		for (int i = 1; i <= n; ++ i)
			scanf("%s", s[i]), len[i] = len[i-1]+strlen(s[i])+1;
		que[h = t = 1] = 0;
		memset(bey, 0, sizeof(bey));
		for (int i = 1; i <= n; ++ i){
			while (h < t && bey[h] <= len[i]) ++h;
			pre[i] = que[h]; f[i] = get_val(len[i], que[h]);
			while (h < t && get_val(bey[t-1], i) <= get_val(bey[t-1], que[t])) --t;
			bey[t] = get_bey(que[t], i); que[++t] = i;
		}
		if (f[n] > 1e18) puts("Too hard to arrange");
		else{
			printf("%lld\n", (LL)(f[n]+0.5));
			print_ans(n);
		}
		for (int i = 1; i <= 20; ++ i) putchar(45);
		puts("");
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值