题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5643
这道题是一个类似于约瑟夫环的问题,但是跳动数每次都+1,小编先给出这道题的官方题解:
King's Game
约瑟夫问题的一个变种,然而题目全部是在唬人,就是一个简单的递推。虽然我知道有人会打表。。。
我们看看裸的约瑟夫是怎么玩的:n 个人,每隔 k 个删除。
由于我们只关心最后一个被删除的人,并不关心最后的过程,所以,我们没有必要也不能够模拟整个过程。我们用递推解决。假设有n个人围成环,标号为[0,n−1]从0开始的好处是取模方便),每数k个人杀一个的情况下,最后一个存活的人的编号是f[n]。
我们有f[1]=0,这不需要解释。
接着考虑一般情况f[n],第一个杀死的人的编号是k−1,杀死后只剩下n−1个人了,那么我们重新编号!
原来编号为k的现在是0号,也就是编号之间相差3我们只要知道现在n−1个人的情况最后是谁幸存也就知道n个人的情况是谁幸存。幸运的是f[n−1]已经算出来了那f[n]就是在f[n−1]的基础上加上一个k即可不要忘记总是要取模。
所以递推式子是: f[i]={ 0 i=1 (f[i - 1] + k) mod i other
此题只用在原版约瑟夫问题上加一维,由于依次隔 1,2,3...n−1 个人删除,所以用 f[i][j] 表示 i 个人,依次隔 j,j+1...j+i−1 个人的幸存者标号。
根据刚才的重标号法,第一次 j−1 号出局,从 j 开始新的一轮,从 j+1 开始清除,剩余 i−1 个人,也有递推式子:
f[i][j]={ 0 i=1 (f[i - 1][j+1] + j) mod i other
答案就是 f[n][1]+1(将标号转移到 [1,n]),问题轻松解决。
复杂度:预处理 O(n2),查询 O(1),总复杂度 O(n2)。由于可以滚动数组以及常数太小,所以 n 给了 5000(其实是出题人不会别的算法嘿嘿嘿)。
比赛的时候由于太多时间浪费在了手机那倒水题上(小编TT),这道题并没有怎么看,当时看着觉得是模拟可能会超时,但是又想不出什么方法就没怎么看,下来看到题解的时候也是惊呆了,果然DP是个玄学啊,这东西还能这样递推……(好吧,是小编太辣鸡了),由于题解写的很清楚了,小编这里就只做一点小小的讲解就好,因为n个人最后留下来的那个人是确定的不会改变的,所以我们并不需要每次都去算一次,预处理打表就可以解决。
#include <cstdio>
#include <cstring>
const int maxn = 5000+5;
int ans[maxn];
int main()
{
ans[1] = 1;
for(int i=2; i<=5000; i++)
{
int tmp = 0;
for(int j=2; j<=i; j++)
{
tmp = (tmp+i-j+1) % j;
}
ans[i] = tmp+1;
}
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
printf("%d\n",ans[n]);
}
return 0;
}