1 条题解

  • 0
    @ 2025-6-16 21:30:38
    #include<iostream>
    #include<string>
    #include<vector>
    #include<cstdio>
    using namespace std;
    int main()
    {
    	int n, w, s;
    	cin >> n;
    	string name;
    	vector<string>v;
    	int a[70];
    	for (int i = 0; i < n; i++)
    	{
    		cin >> name;
    		v.push_back(name);
    		a[i] = i;
    	}
    	scanf("%d,%d", &w, &s);
    	w = (w + n - 1) % n;
    	do
    	{
    		w = (w + s - 1) % n;
    		cout << v[a[w]] << endl;
    		for (int j = w; j < n - 1; j++)
    		{
    			a[j] = a[j + 1];
    		}
    	} while (--n);
    1,循环链表,直接模拟。O(n*s)
    
    
    
    2,线段树,O(nlg(n))
    
    例子如下:n = 5, w = 1, s = 3  (括号内为原始编号)
    
    1(1),2(2),3(3),4(4),5(5)     第3个数3出列
    
    1(1),2(2),3(4),4(5)                第1个数1出列
    
    1(2),2(4),3(5)                           第3个数5出列
    
    1(2),2(4)                                      第1个数2出列
    
    1(4)                                                 第1个数4出列
    
    
    
    先引入Joseph递推公式,设有n个人(0,...,n-1),数到m退出,则第i轮出局的人为f(i)=(f(i-1)+m-1)%(n-i+1),f(0)=0; f(i) 表示当前子序列中要退出的那个人(当前序列编号为0~(n-i));
    
    f(0) = 0;
    
    第1轮:f(1) = (0 + 3 - 1) % 5 = 2 (下标为2即为第3个数)
    
    第2轮:f(2) = (2 + 3 - 1) % 4 = 0 
    
    第3轮:f(3) = (0 + 3 - 1) % 3 = 2 
    
    第4轮:f(4) = (2 + 3 - 1) % 2 = 0 
    
    第5轮:f(5) = (0 + 3 - 1) % 1 = 0
    
    所以使用这个公式每一轮中每个人的编号的,接着我们需要快速地找到这一轮中要退出的人的编号对应的原始编号。一个人在当前剩余队列中编号为i,则说明他是从左到右数第i个人,这启发我们可以用线段树来解决问题。用线段树维护原编号i..j内还有多少人没 有被淘汰,这样每次选出被淘汰者后,在当前线段树中查找位置就可以了。
    
    例如我们有5个原编号,当前淘汰者在剩余队列中编号为3,先看左子树,即原编号1..3区间内,如果剩下的人不足3个,则说明当前剩余编号为3的 这个人原编号只能是在4..5区间内,继续在4..5上搜索;如果1..3内剩下的人大于等于3个,则说明就在1..3内,也继续缩小范围查找,这样既可 在logn时间内完成对应。问题得到圆满的解决。
    
    }
    
    • 1

    信息

    ID
    2750
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者