2020 HDU多校 第三场 04-Tokitsukaze and Multiple(贪心 + 前缀和)

题目链接: 04-Tokitsukaze and Multiple

Description

题意:给出n个数和p,求最多可以有多少个不相交的连续子序列和可以整除p

Tokitsukaze has a sequence of length n, denoted by a.

Tokitsukaze can merge two consecutive elements of a as many times as she wants. After each operation, a new element that equals to the sum of the two old elements will replace them, and thus the length of a will be reduced by 1.

Tokitsukaze wants to know the maximum possible number of elements that are multiples of p she can get after doing some operations (or doing nothing) on the sequence a.

Input

  • There are several test cases.
  • The first line contains an integer T (1 ≤ T ≤ 20), denoting the number of test cases. Then follow all the test cases.
  • For each test case, the first line contains two integers n and p (1 ≤ n,p ≤ 105), denoting the length of the sequence and the special number, respectively.
  • The second line contains n integers, where the i-th integer ai (1 ≤ ai ≤105) is the i-th element of a.
  • It is guaranteed that the sum of n in all test cases is no larger than 106.

Output

For each test case, output in one line the maximum possible number of elements that are multiples of p after doing some operations.

Sample Input

2
5 3
2 1 3 2 1
3 1
123 456 789

Sample Output

3
3

Method

  • 其实就是贪心的思想,把n个数看成x根线段,从左至右找线段右端点,符合条件就选该条线段,最后输出选择了的线段的总和;
  • 正确也是快速的解法是,用map维护线段的和,当前缀和模p的值在map中已被标记,说明该线段内存在能整除p的子线段,因为是从左至右选,所以这也是最优的子线段;
  • 我AC的代码是通过遍历右端点,从右端点开始往左寻找能够整除p的线段,找到的话更新左区间上限,同时ans++,最后也是正解。
  • 据说是因为机器的缘故,我AC的代码,赛后提交TLE了,我人傻了,但是从600ms变成>1000ms属实离谱,蒟蒻再努力吧

Code

详见注释

正解: 用map维护前缀和

#include <bits/stdc++.h>

using namespace std;
#pragma GCC optimize(2)
#define ll long long
const int Max = 1e6+3;
const int mod = 1e9+7;
template<typename T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }

int T, n, p, tmp, ans, cnt;
map<int, int> mp;

int main()
{
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &p);
		mp[0] = 1;
		cnt = ans = 0;
		for(int i=0; i<n; i++)
		{
			scanf("%d", &tmp);
			cnt = (cnt+tmp)%p;
			if(mp[cnt]){		//如果存在能整除p的线段,则选它
				ans++;
				cnt = 0;
				mp.clear();
				mp[0] = 1;
			}
			else mp[cnt] = 1;	//否则更新map
		}
		printf("%d\n", ans);
	}
	return 0;
}

蒟蒻AC的代码

#include <iostream>
#include <cstdio>

using namespace std;
#pragma GCC optimize(2)
#define Max 100005
#define ll long long
template<typename T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }

int a[Max], T, n, p;
ll dp[Max];								//前缀和数组 

int main()
{
	scanf("%d", &T);
	while(T--)
	{
		int cnt=0, ans = 0;
		scanf("%d%d", &n, &p);
		for(int i=1; i<=n; i++)
		{
			scanf("%d", &a[i]);
			a[i] %= p;					//缩小a[i] 
			if(a[i]) dp[i] = dp[i-1] + a[i];	//计算前缀和 
			else dp[i] = 0;
		}
		for(int i=1; i<=n; i++)			//从左至右找线段右端点 
		{
			if(!dp[i]) {				//如果该点是p的倍数,更新左区间 
				ans++; cnt = i;
			}
			else {						//从线段右端点向左寻找左端点,当线段和满足条件时停止,同时更新左区间 
				for(int j=i-2; j>=cnt; j--)
				{
					if((dp[i]-dp[j])%p == 0) {
						ans++; cnt=i; break;
					}
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}

蒟蒻一只,欢迎指正

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值