CodeForces 317D Game with Powers

本文解析了CodeForces317D题目,通过将游戏划分为多个子游戏并利用SG函数求解,实现了O(√N)的时间复杂度。介绍了如何通过统计不超过√N的数来简化问题。

CodeForces 317D Game with Powers

题目描述:

1 N N 个数,两个人轮流操作,每次取一个数,并且不能取以前取过的数的任意正整数次幂,第一个无法操作的人输,问谁能赢。

题解:

假设 x 不是任何其它数的正整数次幂,把 x 和它的正整数次幂分成一类,显然一个数不可能同时属于两类,于是整个游戏就被分成了若干个互不干扰的子游戏,且每个子游戏的规模不超过 log2N ,打表求出 SG 函数异或起来即可,但这样时间复杂度为 O(N)

然而实际上,对于超过 N x ,子游戏的规模一定为 1 ,所以我们只需要统计小于等于 N x 即可,时间复杂度 O(N)

题目链接: vjudge 原网站

代码:

#include <set>
#include <cstdio>
#include <bitset>
#include <cstdlib>
#include <algorithm>
using namespace std;

#define MAXN 1000000007
#define MAXM 43

const int SG[] = {0, 1, 2, 1, 4, 3, 2, 1, 5, 6, 2, 1, 8, 7, 5, 9, 8, 7, 3, 4, 7, 4, 2, 1, 10, 9, 3, 6, 11, 12, 14};

static set<int> st;
static int N, K = 0;
static bitset<MAXN> vis;

int main()
{
    scanf("%d", &N);
    for (int i = 2; i * i <= N; i++)
        if (!vis[i])
        {
            int num = 0;
            for (long long j = i; j <= N; j *= i, num++) st.insert(j), vis[j] = 1;
            K ^= SG[num];
        }
    printf("%s\n", (K ^ ((N - st.size()) & 1)) ? "Vasya" : "Petya");
    return 0;
}

提交记录(AC / Total = 1 / 5):

Run IDRemote Run IDTime(ms)Memory(kb)ResultSubmit Time
87629332633221360124156WA2017-04-13 14:55:26
87631032633234392124152WA2017-04-13 15:06:24
87632452633243792124168WA2017-04-13 15:16:04
876335526332524124124352RE2017-04-13 15:24:27
876340026332547124126344AC2017-04-13 15:27:14
<think> 题目:Codeforces 1679B - Stone Age Problem 题目大意:有一个数组,开始有n个元素,进行q次操作。操作有两种: 1. 将第i个元素的值改为x 2. 将数组中所有元素的值改为x 每次操作后,输出当前数组所有元素的和。 注意:操作1可能会在操作2之后发生,此时操作1会覆盖操作2对单个元素的赋值。 思路: 我们需要维护两个信息: 1. 当前数组每个元素的值(但注意,当有操作2时,我们并不真的立即更新所有元素,因为这样会超时) 2. 最后一次全局更新的时间戳和值 另外,我们还需要记录每个元素最后一次被单独更新的时间(时间戳),以判断该元素是否被之后的全局更新覆盖。 具体做法: 我们可以用一个数组arr来记录每个元素当前单独设置的值(即没有被全局覆盖的值),以及一个数组last_time记录每个元素最后一次单独更新的操作编号(或者时间戳,我们用操作序号作为时间戳)。 同时,我们维护一个全局的覆盖值base,以及最后一次全局更新的时间戳global_time(记录是第几次操作)。 还需要记录当前的和sum。 但是,注意操作2会重置整个数组,那么在此之后的单个更新会覆盖全局的值。因此,在执行操作1时,我们要检查这个元素上一次更新时间是否在最近一次全局更新之前,如果是,那么当前操作1时,这个元素之前的值实际上是全局覆盖的值,但我们在数组arr中保存的是单独设置的值,所以我们需要用全局覆盖的值来更新这个元素之前的值吗?其实不需要,因为我们只关心总和的变化。 具体操作: 我们设置: global_val: 最近一次全局赋值操作设置的值(如果没有,可以初始化为0?但是初始数组有初始值) last_global: 最近一次全局赋值操作的操作序号(从1开始) arr[i]: 记录第i个元素在单独赋值操作中设置的值(初始为初始数组的值) last[i]: 记录第i个元素最后一次单独赋值操作的操作序号(初始为0,表示没有单独赋值操作,或者如果一开始有初始值,我们可以认为是在第0次操作时由初始赋值赋予的,但注意初始赋值后没有全局操作,所以第一次操作前,每个元素的last[i]都是0,而last_global为0(表示没有全局操作)) 然后,在每次操作1(修改第i个元素为x)时: 首先,检查这个元素是否在最近一次全局操作之后被修改过?即比较last[i]和last_global: - 如果last[i] > last_global,说明该元素在最近一次全局操作之后被修改过,那么它当前的值就是arr[i]。 - 否则,该元素当前的值应该是全局值global_val。 然后,我们计算这个元素原来的值old_val,然后新的值变成x,那么总和的变化就是 (x - old_val) 更新总和:sum = sum + (x - old_val) 更新arr[i] = x,并且更新last[i] = current_op(当前操作序号) 在每次操作2(全局修改为x)时: 我们直接设置全局值:global_val = x 更新last_global = current_op(当前操作序号) 同时,总和变成 n * x 但是注意:我们不需要更新每个元素的arr和last,因为后续操作1会覆盖,而且我们通过时间戳来判断。 但是这里有一个问题:当前总和怎么得到?我们之前用sum记录,但是全局操作后,总和就是n*x。所以我们直接设置sum = n * x 然而,我们还需要注意:在全局操作之后,如果有操作1修改了某个元素,那么我们在操作1中已经更新了sum,并且更新了那个元素的值。但是全局操作时,我们设置sum为n*x,这相当于重置了整个数组。那么之后的操作1中,我们计算单个元素修改时,需要知道这个元素在修改前的值(可能是全局值,也可能是后来单独设置的值),这个我们已经通过last[i]和last_global的比较解决了。 但是,我们还需要维护sum。初始时,sum就是初始数组的和。 算法流程: 初始化: n, q 输入 arr: 初始数组,大小为n+1(1-indexed) last: 大小为n+1,初始全部为0 global_val = 0, last_global = 0 sum = 初始数组的和 然后进行q次操作,每次操作: 如果操作类型是1: 输入 i 和 x i = i(注意题目是1-indexed) 计算old_val: if last[i] > last_global: old_val = arr[i] else: old_val = global_val sum = sum - old_val + x arr[i] = x last[i] = current_op_index // 当前操作序号(从1到q) 输出 sum 如果操作类型是2: 输入 x global_val = x last_global = current_op_index sum = n * x 输出 sum 注意:这里我们不需要更新arr和last数组,因为后续操作1会更新,而操作2之后,所有元素的值都应该是x,但是我们通过时间戳机制在操作1中判断。 但是,这里有一个问题:在操作2之后,所有元素在逻辑上都被设置成了x。但是我们在操作1中只更新了被修改的元素,那么当我们再次遇到操作1时,对于没有被操作1修改的元素,我们仍然会通过last[i] <= last_global认为它是global_val(即最新的全局值),这样是正确的。 然而,我们还需要注意:全局操作后,我们重置了sum为n*x,这样在后续操作1中,我们计算单个元素变化时,这个元素在修改前的值我们通过全局值得到,这样是符合的。 但是,有一个问题:如果连续两次操作2,那么第一次操作2后,last_global被更新,sum被更新为n*x1。第二次操作2,我们更新last_global为当前操作序号,然后sum被更新为n*x2。这样也是正确的。 然而,在操作1中,我们更新了某个元素,然后后面又进行了一次操作2,那么该元素在操作2后就被覆盖了,所以它的值应该是操作2设置的值,直到下一次操作1修改它。 所以这个思路是可行的。 但是,我们如何维护每个元素的last[i]?我们只需要一个数组记录每个元素最后一次单独赋值的时间戳(操作序号)。另外,全局操作的时间戳用last_global记录。 注意:操作序号我们用当前操作的编号(从1到q)。 初始化: last_global = 0 // 表示还没有全局操作 global_val 初始值是多少?其实无所谓,因为一开始没有全局操作,每个元素的值由初始数组决定,所以一开始每个元素的值就是arr[i](初始值),并且last[i]=0(表示在第0次操作赋值,而last_global=0,所以比较时last[i](0)<= last_global(0),所以会取global_val?这不对!因为初始时没有全局操作,我们不应该取global_val。 因此,我们需要调整:初始时,我们设置last_global为0,global_val为0,但是初始数组的值是给定的,所以每个元素的值是arr[i]。那么,在第一次操作1之前,如果我们遇到操作1,我们希望取到初始值。那么我们可以这样:一开始,我们让last[i]都为0,而last_global为0,那么比较last[i] <= last_global?因为0<=0,所以会取global_val(0)?这显然不对。 所以我们需要修改:我们设定初始时,每个元素都有自己的值(初始值),并且它们都没有被全局操作覆盖?因此,我们可以设定last_global的初始值为-1(或者0,但是时间戳从1开始),然后初始时,每个元素的last[i]为0(表示在第0次操作时被赋值),而第0次操作(初始赋值)在全局操作之前(因为全局操作从第一次操作开始才可能有)。所以我们可以这样比较: if last[i] > last_global -> 使用arr[i] else -> 使用global_val 但是初始时,last_global为0(表示没有全局操作,或者全局操作在0次操作时?),而last[i]为0,那么0<=0,所以使用global_val(初始为0),但是初始值并不是0,而是arr[i](初始值)。 所以我们需要改变:在初始时,我们不应该使用global_val,而是使用初始数组的值。因此,我们可以认为初始数组的值是单独赋值的,并且时间戳为0。而全局操作的时间戳初始值设为-1(这样0>-1,所以会使用arr[i])。 因此,我们初始化: last_global = -1 // 表示还没有全局操作 global_val = 0 // 这个值无所谓,因为不会使用(除非有全局操作) last[i] = 0 // 表示在第0次操作(初始)被赋值 arr[i] = 初始数组的第i个值 sum = 初始数组的和 这样,在第一次操作1时(假设操作1是第一次操作): 对于元素i,last[i]=0,last_global=-1,0>-1为真,所以old_val=arr[i](初始值),然后更新arr[i]=x,last[i]=1(当前操作序号1),然后更新sum。 在第一次操作2(全局操作)时,我们设置last_global=1(当前操作序号),global_val=x,然后sum=n*x。 接下来,如果再来操作1(第二次操作1,即第三次操作): 对于元素i,last[i]可能是0(从未修改过)或者1(如果之前修改过),而last_global=1(第二次操作是操作2,操作序号2?不对,操作序号是顺序的,第一次操作是1,第二次操作是2,第三次操作是3)。 注意:操作序号我们按顺序从1到q。 假设是第一次操作1(操作序号1)修改了某个元素,那么last[i]=1,然后第二次操作是操作2(操作序号2),然后第三次操作是操作1(操作序号3)修改同一个元素。 那么,last[i]=1,last_global=2(因为第二次操作是操作2,我们设置last_global=2),因为1<2,所以old_val=global_val(即第二次操作2设置的x),然后更新为新的x3。 这样是正确的。 但是注意:在操作2之后,我们并没有更新每个元素的last[i](因为我们避免O(n)的更新),所以当操作1来的时候,对于那些没有被操作1修改的元素,它们的last[i]仍然小于last_global,所以会使用global_val。 因此,我们只需要维护: global_val, last_global arr: 每个元素单独赋值的值(初始为初始数组的值) last: 每个元素最后一次单独赋值的时间戳(初始为0) sum: 当前数组的和 注意:在操作2之后,我们更新sum为n*x,这可能会因为之前单独赋值操作而改变,所以操作2后我们就重置了整个数组。 但是,在操作2之后,我们并没有改变arr和last数组,那么当操作2之后,我们进行一个操作1修改一个元素,那么我们在操作1中更新了arr[i]和last[i],然后后续操作1再修改其他元素,这些元素还没有被单独修改过(即last[i]<=last_global),所以会使用global_val(即操作2设置的值)。 所以这个逻辑是自洽的。 但是,注意:我们如何维护sum?在操作1中,我们根据条件得到old_val,然后减去old_val,加上新值x,得到新的sum。在操作2中,我们直接将sum设为n*x。 但是,在多次操作2后,中间夹杂操作1,这样处理也是正确的吗? 例如: 操作1:修改某个元素,我们更新了arr[i]和last[i],并且更新了sum。 操作2:全局修改,设置global_val=x,last_global=当前操作序号,并且sum=n*x。 操作1:修改一个元素,此时这个元素在操作1修改前,因为last[i](上一次操作1的时间戳)小于操作2的时间戳,所以old_val=global_val(即操作2设置的值),然后更新为x,那么更新sum:sum = sum - global_val + x = n*x - global_val + x,但是global_val就是操作2设置的值,所以等于n*x - x + x = n*x + (x - global_val) ?不对,因为我们修改了一个元素,所以实际上应该是 (n-1)*x + x_new = n*x + (x_new - x) ?所以我们的计算:sum = sum - old_val + x = n*x - old_val + x,而old_val等于global_val(也就是x),所以等于 n*x - x + x = n*x,然后加上新值x再减去旧值x,实际上应该是n*x - old_val + x = n*x - x + x = n*x,这不对!因为我们修改了一个元素,新的和应该是 (n*x - x + x_new) = n*x - x + x_new。 所以,在操作1中,我们的计算应该是: old_val = (last[i] > last_global) ? arr[i] : global_val; sum = sum - old_val + x; 而操作2后,sum = n*x。 在操作2后的操作1:假设操作2设置x0,那么操作1修改第i个元素为x1,则: old_val = x0 sum = n*x0 - x0 + x1 = (n-1)*x0 + x1 而实际应该是:原来所有元素都是x0,现在修改一个为x1,所以总和确实是(n-1)*x0+x1。 因此,计算正确。 但是,在操作2后,我们修改了sum为n*x0,然后操作1:sum = n*x0 - x0 + x1 = (n-1)*x0+x1,正确。 所以我们可以这样实现。 注意:数据范围 n, q up to 200,000 每次操作1是O(1)的,操作2也是O(1)的,所以总复杂度O(q)。 代码实现: 步骤: #include <iostream> #include <vector> using namespace std; typedef long long ll; // 因为和可能很大 int main() { ll n, q; cin >> n >> q; vector<ll> arr(n+1); // 1-indexed vector<ll> last(n+1, 0); // 时间戳,初始为0 ll global_val = 0; ll last_global = -1; // 最后一次全局操作的操作序号,初始-1 ll sum = 0; for (ll i = 1; i <= n; i++) { cin >> arr[i]; sum += arr[i]; } for (ll op_index = 1; op_index <= q; op_index++) { ll type; cin >> type; if (type == 1) { ll i, x; cin >> i >> x; ll old_val; // 检查这个元素最后一次单独修改的时间戳和全局时间戳 if (last[i] > last_global) { old_val = arr[i]; } else { old_val = global_val; } // 更新sum sum = sum - old_val + x; // 更新arr[i]和last[i] arr[i] = x; last[i] = op_index; // 当前操作序号 cout << sum << endl; } else if (type == 2) { ll x; cin >> x; // 全局更新 global_val = x; last_global = op_index; sum = n * x; cout << sum << endl; } } return 0; } 但是,注意:在操作2之后,我们并没有修改arr和last数组。这样,后续操作1在访问arr[i]时,它保存的是上一次单独修改的值(可能是在全局操作之前修改的,也可能是全局操作之后修改的),但是我们并不需要这个值来代表当前值,因为我们通过时间戳机制已经得到了正确的old_val。而且,在操作1中,我们更新arr[i]为新的x,并更新last[i]为当前操作序号,这样这个元素就从全局操作中独立出来了。 所以这个代码是可行的。 但是,有一个边界:如果操作2之后,我们并没有对某个元素进行单独修改,那么它的last[i]就会一直小于last_global,所以每次操作1访问它时,都会使用global_val(即最近一次操作2的值)作为old_val。这样是正确的。 测试一下: 初始:n=3, arr=[1,2,3] -> sum=6 操作1:1 2 5 -> 修改第二个元素,原来第二个元素的值:因为last[2]=0,last_global=-1,0>-1为假?等等,0<=-1?不对,0>-1为真?所以old_val=arr[2]=2。然后更新为5,sum=6-2+5=9,输出9。然后arr[2]=5, last[2]=1。 操作2:2 1 -> 全局设置为1,last_global=2(当前操作序号2),global_val=1,sum=3*1=3,输出3。 操作1:1 2 3 -> 修改第二个元素,此时last[2]=1,last_global=2,1<2,所以old_val=global_val=1。然后更新:sum=3-1+3=5,arr[2]=3,last[2]=3,输出5。 操作1:1 1 1 -> 修改第一个元素,last[1]=0(初始),last_global=2,0<2,所以old_val=1。然后更新:sum=5-1+1=5,arr[1]=1,last[1]=4,输出5。 最后数组:第一个元素1(在操作4被修改),第二个元素3(在操作3被修改),第三个元素呢?没有修改,所以应该是全局值1?但是操作2后全局值为1,然后没有操作1修改第三个元素,所以第三个元素应该是1。所以总和=1+3+1=5,正确。 因此,代码按照上述编写。 注意:long long,因为n,q都是200000,元素值最大10^9,所以和最大200000*10^9=2e15,long long最大9e18,所以用long long足够。 提交到Codeforces,注意使用C++17,并且注意输入输出效率。 我们使用cin和cout,但是数据量20万,所以可以关闭同步: ios::sync_with_stdio(false); cin.tie(0); 完整代码: </think> 以下是解决 Codeforces 1679B 问题的 C++ 实现。该问题要求在两种操作下动态维护数组的和: 1. **单点修改**:将第 `i` 个元素改为 `x` 2. **全局修改**:将所有元素改为 `x` ### 解决方案 我们使用以下策略高效处理操作: - **时间戳机制**:记录每个元素**最后一次单独修改**的操作编号(`last[i]`) - **全局状态**:记录**最后一次全局修改**的操作编号(`last_global`)和值(`global_val`) - **动态和**:维护当前数组的和(`sum`),在每次操作后更新 ```cpp #include <iostream> #include <vector> using namespace std; typedef long long ll; int main() { ios::sync_with_stdio(false); cin.tie(0); ll n, q; cin >> n >> q; vector<ll> arr(n + 1); vector<ll> last(n + 1, 0); // 初始时间戳为0 ll global_val = 0; ll last_global = -1; // 初始无全局操作 ll sum = 0; // 读入初始数组 for (ll i = 1; i <= n; i++) { cin >> arr[i]; sum += arr[i]; } // 处理q次操作 for (ll op_index = 1; op_index <= q; op_index++) { ll type; cin >> type; if (type == 1) { ll i, x; cin >> i >> x; // 判断当前元素的旧值 ll old_val; if (last[i] > last_global) { old_val = arr[i]; // 最近有单独修改 } else { old_val = global_val; // 最近被全局修改覆盖 } // 更新和、数组值及时间戳 sum = sum - old_val + x; arr[i] = x; last[i] = op_index; cout << sum << '\n'; } else if (type == 2) { ll x; cin >> x; // 更新全局状态并重置和 global_val = x; last_global = op_index; sum = n * x; cout << sum << '\n'; } } return 0; } ``` ### 代码解释 1. **初始化**: - `arr` 存储元素初始值,`last` 记录每个元素最后修改时间戳(初始为0) - `global_val` 和 `last_global` 分别存储全局修改的值和时间戳(初始为-1) - `sum` 初始化为数组元素总和 2. **操作处理**: - **单点修改(类型1)**: - 检查元素当前值:若单独修改时间晚于全局修改(`last[i] > last_global`),则取 `arr[i]`;否则取全局值 `global_val` - 更新 `sum = sum - old_val + x` - 更新 `arr[i] = x` 和 `last[i] = 当前操作号` - **全局修改(类型2)**: - 更新 `global_val = x` 和 `last_global = 当前操作号` - 直接计算 `sum = n * x` 3. **时间复杂度**: - 每次操作 $O(1)$,总复杂度 $O(n + q)$,满足题目要求 ### 示例测试 **输入**: ``` 5 5 1 2 3 4 5 1 1 5 2 10 1 5 20 2 1 1 3 100 ``` **输出**: ``` 19 // (5+2+3+4+5) 50 // 5*10 70 // 50 -10 +20 5 // 5*1 103 // 5 -1 +100 = 104? 注意:第三个元素旧值为1,改为100后:5-1+100=104 ``` 修正:最后一步详细计算: - 全局修改后所有元素为1(`sum=5`) - 修改元素3:旧值=1,新值=100 - `sum = 5 - 1 + 100 = 104`(代码中输出104)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值