结论太小,以致于大家都看不见它

280 篇文章 1 订阅
220 篇文章 2 订阅

题目

题目描述
众所周知, H a n d I n D e v i l \sf HandInDevil HandInDevil 是又强又假又短的神仙。也兼任马桶一职。

现在 H a n d I n D e v i l \sf HandInDevil HandInDevil n n n 句假话要说。正如标杆已经指出的那样, H a n d I n D e v i l \sf HandInDevil HandInDevil 说一句话,会付出代价。对于第 i i i 句假话, H a n d I n D e v i l \sf HandInDevil HandInDevil k i k_i ki 个备选选项,其中第 j    ( 1 ⩽ j ⩽ k i ) j\;(1\leqslant j\leqslant k_i) j(1jki) 句假话会让 H a n d I n D e v i l \sf HandInDevil HandInDevil 失去 j j j[最高机密],但是会让祂获得 a i , j a_{i,j} ai,j 的愉悦度。

现在 H a n d I n D e v i l \sf HandInDevil HandInDevil 想知道,如果祂失去了 t t t[最高机密],祂最多能获得多少愉悦度?请对于所有 t ∈ [ n , ∑ k i ] t\in[n,\sum k_i] t[n,ki] 都求出答案。

数据范围与提示
因为 H a n d I n D e v i l \sf HandInDevil HandInDevil 很假,所以 n ⩽ 1 0 5 n\leqslant 10^5 n105 。因为祂很强,所以 0 ⩽ a i , j ⩽ 1 0 9 0\leqslant a_{i,j}\leqslant 10^9 0ai,j109 。因为祂尤其短,所以 k i ⩽ 5 k_i\leqslant 5 ki5“看开点,被击碎只会让数量变多!”

思路

反悔贪心

t t t 变大 1 1 1 时,只可能发生如下变化,进行一些 简单 的讨论即可。

  • + 1 +1 +1
  • + 2 − 1 +2-1 +21
  • + 3 − 2 +3-2 +32
  • + 3 − 1 − 1 +3-1-1 +311
  • + 2 + 2 − 3 +2+2-3 +2+23
  • + 4 − 3 +4-3 +43
  • + 4 − 2 − 1 +4-2-1 +421
  • + 4 − 1 − 1 − 1 +4-1-1-1 +4111

这里有 8 8 8 种情况,每次要在 8 8 8 个堆内重新更新,复杂度是 O ( K log ⁡ K ) \mathcal O(K\log K) O(KlogK),其中 K = 64 n k K=64nk K=64nk而且写起来应该非常简单

我们是冠军!

这个真的,太牛了。公屏们把兄弟打在 🐮🍺 上!

首先我们有一个 n a i v e \rm naive naive O ( n 2 k 2 ) \mathcal O(n^2k^2) O(n2k2) 背包做法。即 f ( j ) f(j) f(j) 表示,付出 j j j 的代价,最多能得到多少愉悦值。

shénde f ( j ) f(j) f(j) 按照模 M = 12 M=12 M=12 的余数分类,每组都是凸的!即 f ( j ) − f ( j − M ) ⩾ f ( j + M ) − f ( j ) f(j)-f(j{\rm-}M)\geqslant f(j{\rm +}M)-f(j) f(j)f(jM)f(j+M)f(j) 总成立!

为什么呢?考虑 f ( j − M ) f(j{\rm-}M) f(jM) f ( j + M ) f(j{\rm+}M) f(j+M) 的方案之间的差异。第 i i i 句假话可能由 j 0 j_0 j0 的代价变为 j 2 j_2 j2 的代价,有一个 λ i = j 2 − j 0 \lambda_i=j_2-j_0 λi=j2j0 的差。显然 ∑ λ i = 2 M \sum\lambda_i=2M λi=2M λ i ∈ [ − 4 , 4 ] \lambda_i\in[-4,4] λi[4,4]

为了避免负数,我们先将其做一个处理。对于 λ i < 0 \lambda_i<0 λi<0,任取一个 λ x ⩾ ∣ λ i ∣ \lambda_{x}\geqslant|\lambda_i| λxλi,将二者合并为 λ x + λ i \lambda_x+\lambda_i λx+λi 。如果不存在这样的 x x x,就找若干个 λ \lambda λ,使它们的和恰好大于 ∣ λ i ∣ |\lambda_i| λi,合并为它们的和。显然是可以做到的——毕竟总和 = 2 M > 0 =2M>0 =2M>0 。并且合并得到的结果仍然在 [ 0 , 4 ] [0,4] [0,4] 范围内。

经过这样的处理,仍然满足 λ \lambda λ 是独立的。也就是说,可以独立地将若干个 λ \lambda λ 应用到 f ( j − M ) f(j{\rm-}M) f(jM) 的方案上,使代价增加 ∑ i ∈ S λ i \sum_{i\in S}\lambda_i iSλi 。同时愉悦度的增加也是 ∑ i ∈ S ω i \sum_{i\in S}\omega_i iSωi 之类的。

打表暴搜 可以证明:对于任意整数集 S S S,如果 ∑ x ∈ S x = 2 M \sum_{x\in S}x=2M xSx=2M ∀ x ∈ S ,    x ∈ [ 0 , 4 ] \forall x\in S,\;x\in[0,4] xS,x[0,4],那么存在 T ⫅ S T\subseteqq S TS 使得 ∑ x ∈ T x = M \sum_{x\in T}x=M xTx=M 。不要问为什么,反正是对的(暴搜代码在文末)。

也就是说,我们可以选出一个 λ \lambda λ 的子集 T T T,将其应用到 f ( j − M ) f(j{\rm-}M) f(jM) 的方案上,便可得到 f ( j ) f(j) f(j) 。显然 T T T 的补集也可以做到这一点。只需要选择二者中,对愉悦度增加较大的一个,因为二者是独立地增加愉悦度。所以
f ( j ) ⩾ f ( j − M ) + f ( j + M ) 2 f(j)\geqslant\frac{f(j{\rm-}M)+f(j{\rm+}M)}{2} f(j)2f(jM)+f(j+M)
当然我们得到的只是一种 f ( j ) f(j) f(j) 的方案,实际上的 f ( j ) f(j) f(j) 可以更大,但是总是满足上面的不等式。而这个不等式就是凸性的证明。

证明了凸性,我们就会联想 闵科夫斯基和。两边都枚举模 M M M 的余数,然后做闵科夫斯基和。由于闵可夫斯基和是 O ( s i z e ) \mathcal O(size) O(size) 的,也就是子树大小,那肯定是要平均分配,也就是分治了。

时间复杂度 T ( N ) = 2 T ( N 2 ) + N k M ⋅ M 2 = N k M log ⁡ N T(N)=2T({N\over 2})+\frac{Nk}{M}\cdot M^2=NkM\log N T(N)=2T(2N)+MNkM2=NkMlogN

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int_ x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int_ infty = (1ll<<60)-1;
const int MaxN = 100000;
const int MaxK = 5; // size of unit
const int MaxLen = MaxN*MaxK;
const int Step = 12;
void minkowski(int_ *a,int n,int_ *b,int m,int_ *c){
	const int_ *end_a = a+n-1, *end_b = b+m-1;
	*c = max(*c,(*a)+(*b)); // first one
	for(++c; a!=end_a||b!=end_b; ++c)
		if(b == end_b || (a != end_a && *(a+1)-*a > *(b+1)-*b))
			++ a, *c = max(*c,(*a)+(*b));
		else ++ b, *c = max(*c,(*a)+(*b));
}

int_ tmp[Step*2-1][MaxLen/Step+2];
int_ *dp[MaxN][Step]; int k[MaxN];
/// @brief solve [l,r), save into dp[l]
void solve(int l,int r){
	if(r-l == 1) return ;
	int m = (l+r)>>1;
	solve(l,m), solve(m,r);
	const int new_len = k[l]+k[m];
	rep(i,0,(Step-1)<<1)
		rep(j,0,new_len/Step)
			tmp[i][j] = -infty;
	rep(i,0,Step-1) rep(j,0,Step-1)
		minkowski(dp[l][i],k[l]/Step+1,
			dp[m][j],k[m]/Step+1,tmp[i+j]);
	rep(i,Step,2*Step-2)
		rep(j,0,new_len/Step-1)
			tmp[i-Step][j+1] = max(
				tmp[i-Step][j+1],tmp[i][j]);
	int_ *_alloc = dp[l][0]; // re_alloc
	rep(i,0,Step-1){
		dp[l][i] = _alloc;
		memcpy(dp[l][i],tmp[i],(new_len/Step+1)<<3);
		_alloc += new_len/Step+1;
	}
	k[l] = new_len;
}

int_ memory_pool[MaxN*Step];
int main(){
	int n = readint();
	int_ *_alloc = memory_pool;
	rep(i,0,n-1){
		k[i] = readint()-1;
		rep(j,0,k[i]){
			dp[i][j] = _alloc ++;
			*dp[i][j] = readint();
		}
		rep(j,k[i]+1,Step-1){
			dp[i][j] = _alloc ++;
			*dp[i][j] = -infty;
		}
	}
	solve(0,n); // [0,n)
	rep(i,0,k[0]){
		writeint(dp[0][i%Step][i/Step]);
		putchar(' ');
	}
	putchar('\n');
	return 0;
}

后记

关于那个 s u m = 2 M sum=2M sum=2M 则存在子集 s u m = M sum=M sum=M 的东西,我挺好奇的,打了个表,也没看出任何规律。把暴搜验证的代码放在这里好了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int_ x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int N = 3;
const int MaxAns = 1000;
int cnt[N+1], WANT;
bool dp[MaxAns+1];
bool check(){
	memset(dp+1,0,WANT); dp[0] = true;
	rep(i,1,N) drep(j,WANT,i)
		for(int k=1; k<=cnt[i]&&!dp[j]; ++k)
			dp[j] = dp[j-k*i];
	return dp[WANT];
}
bool dfs(int t,int sum){
	if(t == N+1)
		return (sum != WANT*2) ? true : check();
	for(cnt[t]=0; cnt[t]<=(WANT*2-sum)/t; ++cnt[t])
		if(!dfs(t+1,sum+cnt[t]*t)) return false;
	return true; // no error reported
}

int main(){
	for(WANT=1; true; ++WANT)
		if(dfs(1,0)) break;
    printf("Step = %d\n",WANT);
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值