poj 3372

直接推还真没推出来。。。看了别人讲的好多也没明白,最后看这个讲的还是不错的。

题意:N个小孩围成一个圈,老师 顺时针隔 0, 1, 2, 3,。。。个小孩发糖,问每个小孩是否都能领到糖。

解法很简单 当N是2^K时输出YES,否则输出NO。printf((N & N - 1) ? "NO\n" : "YES\n");

关键这个怎么证明呢。看到Discuss里说和2次剩余有关,但是没有看出来。。。下面给出自己的简易证明。

当N是奇数时,拿 7 为例。当增量大于N时,就mod N,对增量没有影响。

增量 0 1 2 3 4 5 6 0 1 2 3 4 5 6
编号 0 1 3 6 3 1 0 0 1 3 6 3 1 0

你会看到,这个数列是重复的,重复区间大小是7,再观察着[0, 6]你会发现 这个重复区间是对称的,为什么会这样?

因为 3 + 4 = 7,0 +1 + 2 + 3 + 4的本质就是 0 +1 + 2 所以N为奇数时,他的重复区间都是对称的,所以可知,他最多只能发给 (N + 1) / 2个小孩糖吃,7时为 0 1 3 6 这4个小孩。

所以N为奇数时,一定为NO。

当N是偶数时,又有什么样的景象呢?以 10 为例:

增量 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
编号 0 1 3 6 0 5 1 8 6 5 5 6 8 1 5 0 6 3 1 0

你会发现他是以 2 * N的重复区间出现的。为什么会这样,因为 1 + 9 = 10,2 + 8 = 10。。。

那么 0 + 1 + ....9 % 10 = 5。即 0 + 1 + 。。。N - 1 % N = N / 2。两个区间合起来则又回到了0。

而再次向后加的时候 0 + ... (N - 1) + 0 + 1 = 0 + ...(N - 2) ,和奇数同样的道理,重复区间的内部是对称的。

怎么再次利用这样对称呢,我们发现 出现的编号其实是 从 0 为起点向前走 N / 2步,以及以 N / 2为起点向前走 N / 2步。

在根据编号0,和编号N / 2 在环上的对称性,来解决。

先看 0 的 N / 2步产生的路径L1,是 0 1 3 6 0 ,这里面出来一个大于N / 2的元素 6 。

再看 N / 2 的路径L2, 是 5 6 8 1 5,这里面出现了一个小于N / 2的元素 1。

而6 % 5 = 1。这是由于0和N / 2对称性的关系。

当 0 的 路径L1 中的 位置i的编号j 大于 N / 2,那么 L2中位置i上的编号一定是 j % (N / 2)。

那么我们就可以修改两条路径使其互不侵犯,让L1上所有值都mod (N / 2) ,如果L1能覆盖0 到 N / 2 - 1,则L2即可覆盖 N / 2 到 N - 1。

那么L1本质上就转化成了更小的问题 即 F(N / 2);

写出递归式

           | Yes(N == 1);

F(N) = | F(N / 2) (N & 1 == 0)

           | NO (N & 1 == 1)

所以可知 N 只有为2^K输出YES。

//============================================================================
// Name        : 3372.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

long long n, k;

int main() {
	while(scanf("%lld", &n)!=EOF){
		k = n;
		while(k%2 == 0){
			k/=2;
		}
		if(k == 1){
			printf("YES\n");
		}
		else{
			printf("NO\n");
		}
	}
	return 0;
}

  

posted @ 2011-07-27 09:10  KOKO's  阅读(234)  评论(0编辑  收藏  举报