HDU - 1808 Halloween treats(鸽巢原理)

Halloween treats

Every year there is the same problem at Halloween: Each neighbour is only willing to give a certain total number of sweets on that day, no matter how many children call on him, so it may happen that a child will get nothing if it is too late. To avoid conflicts, the children have decided they will put all sweets together and then divide them evenly among themselves. From last year’s experience of Halloween they know how many sweets they get from each neighbour. Since they care more about justice than about the number of sweets they get, they want to select a subset of the neighbours to visit, so that in sharing every child receives the same number of sweets. They will not be satisfied if they have any sweets left which cannot be divided.

Your job is to help the children and present a solution.

Input
The input contains several test cases.
The first line of each test case contains two integers c and n (1 ≤ c ≤ n ≤ 100000), the number of children and the number of neighbours, respectively. The next line contains n space separated integers a1 , … , an (1 ≤ ai ≤ 100000 ), where ai represents the number of sweets the children get if they visit neighbour i.

The last test case is followed by two zeros.

Output
For each test case output one line with the indices of the neighbours the children should select (here, index i corresponds to neighbour i who gives a total number of ai sweets). If there is no solution where each child gets at least one sweet, print “no sweets” instead. Note that if there are several solutions where each child gets at least one sweet, you may print any of them.
Sample Input
4 5
1 2 3 7 5
3 6
7 11 2 5 13 17
0 0
Sample Output
3 5
2 3 4

题意: 输入 c ,n ,当 c 与 n 同时为0时程序结束,给你n个正整数(1<= c <= n <= 1e5 )你可以从这 n 个数里面随便取任意数,当这 n 个数和可以整除 c 时输出这n个数的下标(从1开始计数),答案有多种时输出任意一种。不存在时输出“no sweets”。

思路: 给你一个长度为 n 的序列,其中至少有一段长度为 k 的连续序列和可以整除 c(c <= n)(证明为鸽巢原理,详细在下面),所以不存在"no sweets" 的情况,所以我们只需要找到这段序列的头和尾就行,然后直接输出这段连续的下标。如果我们用前缀和存数据然后用两个循环找出这段序列,会造成程序超时。如果我们对每一个前缀和的数据对 c 进行取余,然后用一个循环遍历每一个前缀取余判断其是否是第二次出现,若是第二次出现那么第一次出现的下标加一即为头,第二次出现的下标即为尾,我们直接输出就可以了。

证明: 给你以一个长度为 n 的序列,我们可以得到一个长度为 n 的前缀和序列,我们令每一个前缀和为Si(Si = a1 + a2 + … +ai)。对每一个Si 进行取余 c 我们会得到一个长度为 n 的取余序列我们令它为Mi(Mi = Si % c) 那么Mi 会有 c-1 种可能(除了0以外),也就是说,一个长度为 n 的序列它的结果只有 c - 1种可能,并且很明显 n > c-1,所以一定存在Ml 等于 Mr(抽象出来就是一共有c - 1个鸽巢,但我们有 n 只鸽子要进入巢中,所以肯定存在一个鸽巢的鸽子数量超过了 2 ,也就意味着在M这个序列中一定存在有两个数是相同的),既然 Ml 等于 Mr 那么也就意味着在a数组下标 l + 1 到 r的和是可以整除 c 的即前缀和 (Sr - Sl)%c 等于 0(例子:16 % 3 等于 1,4%3等于1,那么(16 - 4)% 3 等于0),k = r - l。

#include <string.h>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <stdexcept>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ll long long
#define int long long
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const int Base = 131;
const ll INF = 1ll << 62;
const double PI = acos(-1);
const double eps = 1e-7;
const int mod = 2147493647;
#define PI acos(-1)
#define mem(a, b) memset(a, b, sizeof(a))
#define speed { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);}

// inline int gcd(int a, int b) {
// 	while (b ^= a ^= b ^= a %= b);
// 	return a;
// }

inline ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }

long long fastPower(long long base, long long power)
{
	long long result = 1;
	while (power > 0)
	{
		if (power & 1)
			result = result * base % mod;
		power >>= 1;
		base = (base * base) % mod;
	}
	return result;
}

inline ll rd()
{
	ll s = 0, w = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-')
			w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
		s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}

int a[maxn], prefix[maxn], index[maxn];

signed main(){
	int c, n;
	while(~scanf("%lld %lld", &c, &n), c || n){
		int p = 0;
		for(int i = 1; i <= n; i++){
			a[i] = rd();
			prefix[i] = (prefix[i-1] + a[i]) % c; // 对前缀和进行取余
			if(prefix[i] == 0) p = i; // 如果已经出现取余为0的情况直接输出就好了
			index[i] = -1;
		}
		if(p){
			for(int i = 1; i <= p; i++)
				printf("%lld%c", i, " \n"[i == p]);
		}else{
			for(int i = 1; i <= n; i++){
				if(index[prefix[i]] != -1){ //判断这个余数是否是第二次出现
					for(int j = index[prefix[i]] + 1; j <= i; j++)
						printf("%lld%c", j," \n"[j == i]);
					break;
				}else{
					index[prefix[i]] = i; // 如果是第一次出现直接标上第一次出现的下标
				}
			}
		}
	}
	//system("pause");
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值