bzoj 1563 诗人小G 决策单调性优化dp

bzoj上这道题是权限题,然鹅我没权限

AcWing上有:https://www.acwing.com/problem/content/description/306/

对于普通的dp方程,很快可以写出:

设句子长度的前缀和为sum数组,句子长度数组为a数组

dp[i]:以句子i结尾的最小不协调度 , len :标准长度

但很明显这样的时间复杂度是O(N^2) ,过不了

可以用决策单调性来优化dp

决策单调性的前提需要满足四边形不等式。

决策单调性大概就是假如dp[i]以dp[j]为最优决策更新来源,则对于所有x>i,dp[x]的最优决策来源>=j

先(粗略)的证明一下可以使用决策单调性。

先对式子变一下型,相同的凑一起:

则需要证明:

观察val的形态,决定让i相同的项相同的凑一起

式子变形:

展开,设val(i+1,j) = x   val(i,j)= y

因为x>y,所以只需要证明

在t>0的地方单调递增(ps:我上面的式子好像漏写了绝对值符号QAQ)

对其求导:

很明显当p,t,c都大于0的时候式子大于0.

 

 

因为我使用的是优先队列,所以还不需要二分寻找

而具体过程大概为:

使用(loc,l,r) 表示 l,r  区间的最优决策来源dp[j] 的下标j=loc

一开始先插入(0,1,n),然后for(int i=1;i<=n;i++)进行遍历

对于每一次遍历:

1.先排除过时决策,排除队列左端(loc,i,j) 中j<i 的

2.试取队列左端作为dp[i]的最优选择

3.取出队尾,记为(locr,lr,rr),然后以dp[i]更新决策,具体表现为:

if以dp[i]更新lr优于以dp[locr]来更新,那么最右边的决策就不要了

else if以dp[i]更新rr优于以dp[locr]来更新,就(看代码,感觉不好说明。。)

else 二分查找lr,rr,找出一个点w使得小于w部分以dp[locr]更新更好,大于等于w部分以dp[i]来更新更好,然后修改rr=w-1

然后插入新决策

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 7;
#define ld long double
int n, len, p;
int sum[maxn];
ld dp[maxn];
const ld ed = 1e18;
struct node {
	int l, r, loc;
}q[maxn];
ld calc(int i, int j) {// i : 前一次结束的位置 j:后一次结束的位置
	//int flag = 1;
	ld ans = 1;
	ld num = abs((ld)sum[j] - sum[i] + j - i - 1 - len);
	for (int i = 1; i <= p; i++) ans *= num;
	return ans + dp[i];
}
void insert(int i, int& L, int& R) {
	int flag = -1;
	while (L <= R) {
		if (calc(q[R].loc, q[R].l) >= calc(i, q[R].l)) flag = q[R--].l; 
		else {
			if (calc(q[R].loc, q[R].r) > calc(i, q[R].r)) { 
				int ll = q[R].l, rr = q[R].r;
				while (ll < rr) {
					int mid = (ll + rr) >> 1;
					if (calc(i, mid) > calc(q[R].loc, mid)) ll = mid + 1;
					else {
						rr = mid;
					}
				}
				q[R].r = ll - 1;
				flag = ll;
			}
			break;
		}
	}
	if (flag != -1) { //加入新决策
		q[++R].loc = i;
		q[R].l = flag;
		q[R].r = n;
	}
}
int main() {
	int t; cin >> t;
	while (t--) {
		scanf("%d %d %d", &n, &len, &p);
		sum[0] = 0;
		for (int i = 1; i <= n; i++) {
			char s[50];
			scanf("%s", s);
			sum[i] = strlen(s) + sum[i - 1];
		}
		int q_l = 1, q_r = 0;
		q[++q_r].l = 1;
		q[q_r].r = n;
		q[q_r].loc = 0;
		for (int i = 1; i <= n; i++) {
			while (q_l < q_r && q[q_l].r < i) //排除过时决策
				q_l++; 
			q[q_l].l = i + 1;
			dp[i] = calc(q[q_l].loc, i);
			insert(i, q_l, q_r);
		}
		if (dp[n] > ed) {
			cout << "Too hard to arrange" << endl;
		}
		else {
			cout << (long long)dp[n] << endl;
		}
		cout << "--------------------" << endl;
	}
	return 0;
}



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值