• 洛谷 P4588 [TJOI2018]数学计算


    传送门

    Description

    现在有一个数 \(x\),初始值为 \(1\)。有 \(Q\) 次操作,操作有两种:

    \(1\) \(m\):将 \(x\) 变为 \(x \times m\),并输出 \(x \bmod M\)

    \(2\) \(pos\):将 \(x\) 变为 \(x\) 除以第 \(pos\) 次操作所乘的数(保证第 \(pos\) 次操作一定为类型 \(1\),对于每一个类型 \(1\) 的操作至多会被除一次),并输出 \(x \bmod M\)

    每个测试点共有 \(T\) 组输入。

    Constraints

    对于 \(20\%\) 的数据,\(1 \le Q \le 500\)

    对于 \(100\%\) 的数据,\(1 \le Q \le 10^5\)\(T \le 5, M \le 10^9\)\(0 < m \leq 10^9\)

    Solution 0 \(TLE\) or \(WA\)

    扫一眼题目,这题似乎能用模拟解决。

    然而我们发现,若使用高精度乘法,时间必然会不够,直接吃一个 TLE。

    同时,由于出现了除法,所以在取模时应当想到逆元,但是 \(M\) 并不一定为质数,逆元也不可做。

    Solution 1

    注意看题目里我加粗的那行字,题中保证了每次乘操作在后面的除操作中至多只用一次。

    想想后面的除操作,是不是就相当于将前面的乘操作抵消掉呢?(抵消掉,意思就是把乘操作的值变回 \(1\)

    把“时间”(操作次数) \(i\) 抽象成一个个初始为 \(1\) 点,乘操作就是把该“时间” \(i\) 的值修改为乘上的值,除操作就是把这个位置上的值变为 \(1\),则每次询问的答案就是从 \(1\) 位置到 \(i\) 所有数之积对 \(M\) 取模。

    可以看出,这就是单点修改和区间查询,自然能够想到使用树状数组或线段树维护。

    Code

    最近在学线段树,就拿线段树来写了~

    注意一开始整棵线段树要全赋为 \(1\),注意开 long long。

    // by youyou2007 in 2022.
    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <queue>
    #include <stack>
    #include <map>
    #define int long long
    #define REP(i, x, y) for(int i = x; i < y; i++)
    #define rep(i, x, y) for(int i = x; i <= y; i++)
    #define PER(i, x, y) for(int i = x; i > y; i--)
    #define per(i, x, y) for(int i = x; i >= y; i--)
    #define lc (k << 1)
    #define rc (k << 1 | 1)
    using namespace std;
    const int N = 1E5 + 5;
    int MOD;
    int T;
    int q;
    int m[N];
    int f[N * 4]; 
    void pushup(int k)
    {
    	f[k] = (f[lc] * f[rc]) % MOD; 
    }
    void modify(int l, int r, int q, int k, int d, int opt)//单点修改
    {
    	if(l == r && l == q)
    	{
    		if(opt == 1)
    		{
    			f[k] = d;
    		}
    		else
    		{
    			f[k] = 1;
    		}
    		return;
    	}
    	int mid = (l + r) / 2;
    	if(q <= mid)
    	{
    		modify(l, mid, q, lc, d, opt);
    	}
    	else
    	{
    		modify(mid + 1, r, q, rc, d, opt);
    	}
    	pushup(k);
    }
    signed main()
    {
    	scanf("%lld", &T);
    	while(T--)
    	{
    		scanf("%lld%lld", &q, &MOD);
    		rep(i, 1, q * 4 + 5)//整棵线段树都要赋 1
    		{
    			f[i] = 1; 
    		}
    		rep(i, 1, q)
    		{
    			int opt, m;
    			scanf("%lld%lld", &opt, &m);
    			if(opt == 1)
    			{
    				modify(1, q, i, 1, m, 1);//如果是乘操作,就在位置 i 上面修改成 m
    				printf("%lld\n", f[1] % MOD);//这里进行了一个简化,因为 i 位置之后都是 1,对答案无影响,所以可以直接输出整课线段树的积,即 f[1] 的值	
    			}
    			else
    			{
    
    				modify(1, q, m, 1, 1, 2);//如果是除操作,就在位置 m(pos)上面修改成 1
    				printf("%lld\n", f[1] % MOD);
    			}
    		}
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    UVA 11991 Easy Problem from Rujia Liu(map,vector的使用)
    UVA 11995 I Can Guess the Data Structure! (STL应用)
    HDU 2795 Billboard(线段树,单点更新)
    HDU 1394 Minimum Inversion Number (线段树,单点更新)
    UVA 11827 Maximum GCD(读入技巧,stringstream的使用)
    contest 2 总结
    Const 1 总结
    开始进行大量题目练习
    函数式线段树的个人理解
    poj 2318 TOYS
  • 原文地址:https://www.cnblogs.com/pjxpjx/p/16414016.html
Copyright © 2020-2023  润新知