[NOI2019]序列

89 篇文章 0 订阅
27 篇文章 0 订阅

题目

题目背景
你知道 OUYE \textsf{OUYE} OUYE Dd(XyX) \textsf{Dd(XyX)} Dd(XyX),三百年前叱咤于大海上的海盗吗?传说他们的宝物正埋在某座海岛下的洞穴中,未曾有人发掘。

没人知道他们留下了什么,唯一可以确定的是:为了找到这些宝物,战火从大陆之东烧到大洋之北,未曾停歇。

寻宝者 OneInDark \textsf{OneInDark} OneInDark 听信群友的指引,来到了一座海岛。海浪不断地翻滚,卷出千层雪。

绕着海岛走了一圈,便看到一个洞口,青草茂盛。走进去,内部空间竟十分宽敞, O n e I n D a r k \sf OneInDark OneInDark 孤零零地站在里面,很显孤独。

走到底,有一石门。上面的凹槽,恰好是一根标杆和一根标枪的形状。下面写着:

神之居所,凡人当止之处。

O n e I n D a r k \sf OneInDark OneInDark 轻推门,门便开了。走进去,只看到一个空空如也的箱子。这箱子,应当原本是装着吸附有 O U Y E \sf OUYE OUYE 神力的坐垫。

旁边是兔子的脚印,和一个蛋的影子,漂浮在空中。

题目描述
传送门 to luogu

思路

一眼线性规划,然后我人傻了 😭

再一搜提交记录,发现 Rainybunny \textsf{Rainybunny} Rainybunny crashed \textsf{crashed} crashed 早就 A A A 了 💢

n a i v e l y \rm naively naively x i , y i x_i,y_i xi,yi 表示是否选择该元素,然后 ∑ min ⁡ { a i , b i } ⩾ L \sum\min\{a_i,b_i\}\geqslant L min{ai,bi}L 是无法写成网络流的!淦!

必须换一种想法。其实可以先从 反悔贪心 开始想(依据之,很可能建立网络流)。

考虑怎么让 “两边都选” 的多 1 1 1 个。比如直接拼出一个 ( a , b ) (a,b) (a,b),那么 a , b a,b a,b 可能没 “动” 也可能动了:

  • ( a ) , ( b ) → ( a , b ) (a),(b)\to(a,b) (a),(b)(a,b):代价是 Δ a \Delta a Δa Δ b \Delta b Δb Δ a + Δ b \Delta a+\Delta b Δa+Δb

或者拆掉一个原有的 ( a , b ) (a,b) (a,b) 再拼出来两个,即

  • ( a , b ) , ( a x ) , ( b y ) → ( a x , b ) , ( a , b y ) (a,b),(a_x),(b_y)\to(a_x,b),(a,b_y) (a,b),(ax),(by)(ax,b),(a,by):代价是 b x + a y − a − b b_x+a_y-a-b bx+ayab

注意此时 a x , b y a_x,b_y ax,by 是没有 “动” 的——如果 “动” 了,即在原本 a , b a,b a,b 都没选的位置 ( ∅ ) (\varnothing) () 完成组合。但这种方案等价于先让 a x , b y a_x,b_y ax,by 在另一处(该调整所得到的另一个匹配位点)完成匹配,再将这个 ( a , b ) (a,b) (a,b) 移动到 ( ∅ ) (\varnothing) () 位置。由于当前方案是最优,移动 ( a , b ) (a,b) (a,b) ( ∅ ) (\varnothing) () 必然是非正贡献,应当移除。

显然 ( a , b ) (a,b) (a,b) 被拆掉的时候, a a a b b b 都需要重新进入一个匹配。因此拆掉两个 ( a , b ) (a,b) (a,b) 是无需考虑的。

于是维护 ( a ) (a) (a) min ⁡ a ,    max ⁡ b \min a,\;\max b mina,maxb ( b ) (b) (b) min ⁡ b ,    max ⁡ a \min b,\;\max a minb,maxa ( a , b ) (a,b) (a,b) min ⁡ ( a + b ) \min (a{+}b) min(a+b) ( ∅ ) (\varnothing) () max ⁡ ( a + b ) \max(a{+}b) max(a+b) 就行。时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

代码

#include <cstdio>
#include <algorithm> // Almighty XJX yyds!!!
#include <cstring>  // oracle: ZXY yydBUS!!!
#include <cctype> // Huge Egg Dog eats me!!!
#include <queue>
#include <functional>
using llong = long long;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
	return a*f;
}

const int MAXN = 200000;
using PII = std::pair<int,int>;
std::priority_queue<PII> pq[6];

bool usea[MAXN], useb[MAXN];
int a[MAXN], b[MAXN], both;

int ida[MAXN], idb[MAXN];
bool cmpa(const int &x, const int &y){ return a[x] > a[y]; }
bool cmpb(const int &x, const int &y){ return b[x] > b[y]; }
const llong INF = 1ll<<60;
# define pq5push(x) pq[5].push(PII{a[x]+b[x],x})
# define pq4push(x) pq[4].push(PII{-a[x]-b[x],x})
std::function<bool(int)> pred[6];
int main(){
	pred[0] = pred[1] = [](const int &x){ return usea[x] && !useb[x]; };
	pred[2] = pred[3] = [](const int &x){ return !usea[x] && useb[x]; };
	pred[4] = [](const int &x){ return usea[x] && useb[x]; };
	pred[5] = [](const int &x){ return !usea[x] && !useb[x]; };
	for(int T=readint(); T; --T){
		int n = readint(), k = readint(), L = readint();
		memset(usea, false, n), memset(useb, false, n);
		llong ans = 0; both = 0; // clear
		rep0(i,0,n) a[ida[i] = i] = readint();
		std::sort(ida, ida+n, cmpa);
		rep0(i,0,k) usea[ida[i]] = true, ans += a[ida[i]];
		rep0(i,0,n) b[idb[i] = i] = readint();
		std::sort(idb, idb+n, cmpb);
		rep0(i,0,k) useb[idb[i]] = true, ans += b[idb[i]];
		rep(i,0,5) while(!pq[i].empty()) pq[i].pop();
		rep0(i,0,n) // sample for classifing
			if(usea[i] && useb[i]) pq4push(i), ++ both;
			else if(!usea[i] && !useb[i]) pq5push(i);
			else if(usea[i]){ // && !useb
				pq[0].push(PII{-a[i],i});
				pq[1].push(PII{b[i],i});
			}
			else{ // useb && !usea
				pq[2].push(PII{a[i],i});
				pq[3].push(PII{-b[i],i});
			}
		for(; both<L; ++both){
			rep(i,0,5) while(!pq[i].empty()){
				int x = pq[i].top().second;
				if(!pred[i](x)) pq[i].pop(); else break;
			}
			llong ca = (pq[0].empty() || pq[2].empty()) ?
				-INF : pq[0].top().first+pq[2].top().first;
			llong cb = (pq[1].empty() || pq[3].empty()) ?
				-INF : pq[1].top().first+pq[3].top().first;
			llong mer = (pq[0].empty() || pq[3].empty() || pq[5].empty()) ?
				-INF : pq[0].top().first+pq[3].top().first+pq[5].top().first;
			llong sub = (pq[4].empty() || pq[1].empty() || pq[2].empty()) ?
				-INF : pq[4].top().first+pq[1].top().first+pq[2].top().first;
			if(ca >= cb && ca >= mer && ca >= sub){
				int fr = pq[0].top().second, to = pq[2].top().second;
				usea[fr] = false, usea[to] = true; ans += ca;
				pq5push(fr), pq4push(to);
			}
			else if(cb >= ca && cb >= mer && cb >= sub){
				int fr = pq[3].top().second, to = pq[1].top().second;
				useb[fr] = false, useb[to] = true; ans += cb;
				pq5push(fr), pq4push(to);
			}
			else if(mer >= ca && mer >= cb && mer >= sub){
				int fra = pq[0].top().second, frb = pq[3].top().second;
				int to = pq[5].top().second; ans += mer;
				usea[fra] = useb[frb] = false, usea[to] = useb[to] = true;
				pq5push(fra), pq5push(frb), pq4push(to);
			}
			else{ // substitute
				int fr = pq[4].top().second; ans += sub;
				int toa = pq[2].top().second, tob = pq[1].top().second;
				usea[fr] = useb[fr] = false, usea[toa] = useb[tob] = true;
				pq5push(fr), pq4push(toa), pq4push(tob);
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

后记

那么,能否建出网络流,以完全地佐证该做法的正确性呢?

其实 ( a ) , ( b ) → ( a , b ) (a),(b)\to(a,b) (a),(b)(a,b) 这种 “给 a a a 换一个 b b b 来配对” 已经提示我们:将 a , b a,b a,b 建成两层(分层图)。

那么这实际上类似于二分图匹配。先用费用 0 0 0 的边满足题目的限制条件: a i → x a_i\to x aix 容量 + ∞ +\infty + x → y x\to y xy 容量 ( k − L ) (k{-}L) (kL) y → b j y\to b_j ybj 容量 + ∞ +\infty +

补全整张图: a i → b i a_i\to b_i aibi 容量 1 1 1 费用 0 0 0,然后 S → a i S\to a_i Sai 容量 1 1 1 费用 a i a_i ai,最后 b i → T b_i\to T biT 容量 1 1 1 费用 b i b_i bi

可以发现反悔贪心实际上是对 x → y x\to y xy 退流。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 子序列是指从一个序列中取出任意数量的元素,而不改变它们在原序列中的相对顺序所得到的新序列。换句话说,子序列是原序列的一个部分,可以是连续或非连续的。 对于给定的一个序列,请你求出它的子序列的个数。 解题思路: 我们可以使用动态规划的思想来解决这个问题。假设原序列的长度为n。首先,我们定义一个长度为n的数组dp,其中dp[i]表示以第i个元素结尾的子序列的个数。 初始化时,dp数组的所有元素都为1,因为每个元素本身也是一个子序列。 然后,我们从第二个元素开始遍历原序列。对于当前遍历到的元素,我们需要计算以它结尾的子序列的个数。遍历到第i个元素时,我们需要向前遍历前面的元素,若前面的某个元素小于第i个元素,则第i个元素可以接在这个元素的后面,形成一个新的子序列。此时,我们可以利用dp数组来直接求出以前面的这个元素结尾的子序列的个数,并将它们累加到dp[i]中。 最后,我们将dp数组中所有元素的值相加,即可得到原序列的子序列的个数。 例如,对于序列1 2 3 4,其子序列的个数为15,具体的子序列是(1)、(2)、(3)、(4)、(1 2)、(1 3)、(1 4)、(2 3)、(2 4)、(3 4)、(1 2 3)、(1 2 4)、(1 3 4)、(2 3 4)、(1 2 3 4)。 这就是使用动态规划求解子序列个数的方法。 希望对你有帮助! ### 回答2: 子序列是指从给定序列中删除若干个元素后所得到的序列,而被删除的元素的顺序保持不变。例如,对于序列[1, 2, 3, 4, 5],它的子序列可以为[1, 2, 3]、[2, 4, 5]等。那么现在我们来解答关于子序列的NOI教师培训试题。 试题:给定一个长度为n的正整数序列a,若存在一个长度为m的序列b(b中元素值可以不连续)是a的子序列,并且b满足b中各个元素之和可以整除k,输出序列b的最大长度m。 解答: 首先,我们可以使用动态规划的思想来解决这个问题。定义一个dp数组,dp[i]表示以第i个元素结尾的子序列的最大长度。初始化dp数组的所有元素为1。 然后,我们遍历序列a,对于每个元素a[i],再遍历它之前的元素a[j](0 <= j < i),如果a[i]可以整除k,说明可以将a[j]添加到以a[i]结尾的子序列中,此时更新dp[i] = max(dp[i], dp[j] + 1)。最后,找出dp数组中的最大值,即为题目所求的结果。 例如,对于序列a = [1, 2, 3, 4, 5],k = 3,执行上述算法得到dp数组为[1, 1, 1, 1, 2],最大值为2,因此输出结果为2。 该算法的时间复杂度为O(n^2),在n较小的情况下可以接受。如果希望进一步优化时间复杂度,可以考虑使用动态规划+哈希表的方法,将时间复杂度降低到O(n)。 以上就是关于NOI教师培训试题子序列的解答,希望能对您有所帮助! ### 回答3: 子序列是指从一个给定的序列中选择出若干个元素,这些元素在原序列中保持相对顺序不变,但不一定连续。例如,对于序列1 2 4 3,它的子序列可以是1 4、2 3、1 2 3、4等。 求一个序列的最长递增子序列是一个经典问题。给定一个整数序列,我们要找出一个最长的递增子序列,其中递增表示:对于任意的i和j,如果i < j,则ai < aj。例如,对于序列2 1 4 3,它的最长递增子序列是1 3,长度为2。 解决这个问题的动态规划算法可以描述如下: 1. 创建一个辅助数组dp,dp[i]表示以第i个元素结尾的最长递增子序列的长度。 2. 初始化dp数组,将dp的所有元素都初始化为1,表示每个元素本身就是一个递增子序列,长度为1。 3. 从第2个元素开始遍历原序列,依次计算每个元素结尾的最长递增子序列的长度。 4. 对于每个元素,从它之前的元素中找到比它小的元素,如果找到,就更新dp[i]为dp[j]+1,表示以当前元素结尾的最长递增子序列长度增加1。 5. 遍历完整个序列后,dp数组中的最大值即为原序列的最长递增子序列的长度。 上述算法的时间复杂度是O(n^2),其中n是序列的长度。还有其他更优化的算法,可以将时间复杂度降到O(nlogn),例如使用二分查找或贪心算法。 对于NOI教师培训试题,子序列问题是一个较为常见的题型,可以使用上述动态规划算法进行求解。在解题过程中,需要注意理解子序列的含义,以及动态规划算法的思想。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值