题解:CF1992F Valuable Cards

Part 1:前言

题目翻译

在他最喜欢的咖啡馆里,Kmes再次想尝尝皮草大衣下的鲱鱼。以前,这对他来说并不难,但咖啡馆最近推出了一项新的购买政策。

现在,为了进行购买,Kmes需要解决以下问题:在他面前摆放着 n n n 张不同价格的卡,第 i i i 张卡的价格为 a i a_i ai,在这些价格中没有整数 x x x

Kmes被要求将这些卡划分为最少数量的坏段(这样每张卡只属于一个段)。如果无法选择乘积等于 x x x 的卡子集,则认为该段是坏的。Kmes 划分卡片的所有段都必须是坏的。

从形式上讲,如果没有下标 i 1 < i 2 < … < i k i_1<i_2<\ldots<i_k i1<i2<<ik,使得 l ≤ i 1 , i k ≤ r l\le i_1,i_k\le r li1,ikr ∏ j = 1 k a i j = x \prod _ {j=1} ^ {k} a_{i_j} = x j=1kaij=x,则段 ( l , r ) (l,r) (l,r) 是坏的。

帮助Kmes确定坏段的最小数量,以便享受他最喜欢的菜肴。

输入格式

第一行包含一个整数 t ( 1 ≤ t ≤ 1 0 3 ) t(1\le t\le 10^3) t1t103——测试用例的数量。

每组输入数据的第一行分别给出整数 n n n x ( 1 ≤ n ≤ 1 0 5 , 2 ≤ x ≤ 1 0 5 ) x(1\le n\le 10^5,2\le x\le 10^ 5) x(1n1052x105)——卡片数量和整数。

每组输入数据的第二行包含 n n n 个整数 a i ( 1 ≤ a i ≤ 2 ⋅ 1 0 5 , a i ≠ x ) a_i(1\le a_i\le 2\cdot 10^5,a_i\neq x) ai(1ai2105ai=x)——卡片上的价格。

保证所有测试数据集的 n n n 总和不超过 1 0 5 10^5 105

输出格式

对于每组输入数据,输出坏段的最小数量。

Part 2:思路

这道题,咱就选择贪心吧(主要是我 dp 不大会)。

坏段 B B B 的定义: ∃ \exist 子序列 A ∈ B , ∏ A = x A \in B, \prod A=x AB,A=x

显然,一个坏段的长度是有单调性的:当 a 1 , a 2 , … , a k a_1,a_2,\dots,a_k a1,a2,,ak 是坏段时, a 1 , a 2 , … , a k + 1 a_1,a_2,\dots,a_{k+1} a1,a2,,ak+1 也必定是坏段。

所以,我们每次判断出一个段是坏段时,就把 ans 加一。

怎么判断呢?考虑用一个速度更快的 unordered_set,存储之前所有子序列的乘积。

每次循环遍历这个 unordered_set,令当前遍历到的为 k k k。当 a i × k = x a_i \times k=x ai×k=x 时,我们就知道,已经有一个坏段产生了。我们把 unordered_set 清空,将 ans 加一。

但是这样时间复杂度太高,极端情况下,复杂度为 O ( n x ) ≈ 1 0 10 O(nx) \approx 10^{10} O(nx)1010,即使 4s 时限,也救不了你。

所以我们要考虑优化。

  • x m o d    ( a i × k ) = 0 x\mod(a_i \times k)=0 xmod(ai×k)=0 时,将 a i × k a_i \times k ai×k 放入 unordered_set 里。

  • x m o d    ( a i × k ) ≠ 0 x\mod(a_i \times k)\neq0 xmod(ai×k)=0 时,由于无论之后的数怎么样,子序列的乘积已经 ≠ x \neq x =x 了,就舍去。

Part 3:代码

你们终于来到了这里

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
ll t,n,x,a[N],ans;
unordered_set<ll> st,us;
inline void FIO(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    #define endl '\n'
} 
inline void work(){
	st.clear();us.clear();ans=1;
	cin>>n>>x;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++){
		for(auto j:st){
			ll k=a[i]*j;
			if(x%k==0){
				if(k==x){
					ans++;st.clear();us.clear();
					break;
				}
				us.insert(k);
			}
		}
		for(auto j:us) st.insert(j);
		us.clear();
		if(x%a[i]==0) st.insert(a[i]);
	}
	cout<<ans<<endl;
}
signed main(){
    FIO();
    cin>>t;
    while(t--) work();
}

满江绿的感受,是那样的美好,那么的舒适!

  • 38
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值