• 【ACM-ICPC 2018 沈阳赛区网络预赛】不太敢自称官方的出题人题解


    A. Gudako and Ritsuka

    链接

    by Yuki & Asm.Def

    期望难度:Hard-

    考虑从后往前进行博弈动态规划,在这一过程中维护所有的先手必胜区间。区间不妨采用左开右闭,方便转移。

    考虑一次转移,如果当前Servant的后一个位置属于对手,则当前Servant的必胜区间可以通过将后一个Servant的每个必败区间的左端点+1、右端点+x得到;如果后一个位置属于自己,则可以通过将后一个Servant的必胜区间做同样的操作得到。不妨分别对必胜区间左右端点维护一个偏移量,需要从对手进行转移时只需修改偏移量后交换左右端点的集合,然后再在左端点的集合里插入一个0即可。

    需要注意的是,这样得到的必胜区间会有重叠,可能导致对下一个对手的必胜区间的统计出错。考虑到每次转移时所有的同类区间的长度的变化量都相同,可以分别用两个优先队列维护这两类区间,每次转移后暴力合并重叠的区间即可。

    复杂度(O((A+B) log(A+B)))

    //Asm.Def
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int maxn = 100005;
    int A, B, M;
    LL N, x;
    bool p[maxn], beg_A;
    
    void init()
    {
    	scanf("%lld%lld%d%d", &N, &x, &A, &B);
    	assert(A+B <= 100000);
    	assert(A+B >= N);
    	assert(A >= 1 && B >= 1);
    	M = A+B;
    	for(int i = 1;i <= M;++i) p[i] = false;
    	int a;
    	beg_A = false;
    	for(int i = 0;i < A;++i)
    	{
    		scanf("%d", &a);
    		p[a] = 1;
    		if(a == 1) beg_A = true;
    	}
    }
    
    int a[maxn], len;
    
    struct Seg
    {
    	int l, r;
    	LL len;
    };
    bool operator < (const Seg &a, const Seg &b)
    {
    	return a.len > b.len;
    }
    
    void work()
    {
    	len = 0;
    	int cnt = 0;
    	for(int i = 1;i <= M;++i)
    	{
    		++cnt;
    		if(i == M || p[i+1] != p[i])
    		{
    			a[len++] = cnt;
    			cnt = 0;
    		}
    	}
    	bool ans = false;
    
    	static LL loc[maxn];
    	static int nxt[maxn], lst[maxn];
    	static bool sel[maxn];
    	priority_queue<Seg> Q[2];
    
    	bool L = 0, R = 1;
    	LL offs[2] = {0};
    
    	loc[len] = 0;
    	nxt[len] = lst[len] = len;//用链表记录当前区间端点
    
    	sel[len] = false;
    	for(int i = len-1;i >= 0;--i)
    	{
    		//区间整体右移
    		offs[L] += a[i] * x;
    		offs[R] += a[i];
    		L ^= 1, R ^= 1;
    		
    		//插入新增的左端点
    		loc[i] = -offs[L];
    		nxt[i] = nxt[len];
    		lst[nxt[i]] = i;
    		lst[i] = len;
    		nxt[len] = i;
    		sel[i] = true;
    		Q[L].push( (Seg){i, nxt[i], loc[nxt[i]]-loc[i]} );
    
    		//合并区间
    		LL t = offs[R] - offs[L];//R(k)+offs[R] < L(k+1)+offs[L]
    		Seg tmp;
    		while(!Q[R].empty() && ((tmp = Q[R].top()).len <= t || !sel[tmp.l] || !sel[tmp.r]) )
    		{
    			Q[R].pop();
    			if(sel[tmp.l] && sel[tmp.r])//合并一对跨立的"(](]",
    			{
    				sel[tmp.l] = sel[tmp.r] = false;
    				nxt[lst[tmp.l]] = nxt[tmp.r];
    				lst[nxt[tmp.r]] = lst[tmp.l];
    				Q[L].push( (Seg){lst[tmp.l], nxt[tmp.r], loc[nxt[tmp.r]]-loc[lst[tmp.l]]} );
    			}
    			//否则为已被合并的区间,无需处理
    		}
    
    	}
    
    	int it = nxt[len];
    	ans = false;
    	while(it != len && loc[it]+offs[L] < N)
    	{
    		if(nxt[it] == N || loc[nxt[it]]+offs[R] >= N)
    		{
    			ans = true;
    			break;
    		}
    		it = nxt[it];
    		if(it != len) it = nxt[it];
    		if(it == 0) break;
    	}
    	puts((ans ^ beg_A) ? "Ritsuka" : "Gudako");
    }
    
    int main()
    {
    	clock_t beg = clock();
    	int T;
    	scanf("%d", &T);
    	while(T--)
    	{
    		init();
    		work();
    	}
    	//printf("%.3f sec
    ", double(clock()-beg) / CLOCKS_PER_SEC);
    	return 0;
    }
    

    B. Call of Accepted

    链接

    期望难度:Medium

    表达式求值问题可以将中缀表达式转换为后缀表达式,其中转换步骤使用调度场算法后缀表达式的求值 均在维基百科中有详细的介绍。

    根据d运算的描述,(x { m d} y)本质上是一个定义在整数集的幂集上的运算,即(对于任意 S_1, S_2 in mathbb{2^Z}且 min(S_1) geq 0 且 min(S_2)geq 1),定义(S_1 { m d} S_2 = {xin mathbb{Z} mid exists ain S_1, exists bin S_2, aleq x leq ab}). 则对于任意一次有定义的({ m d})运算,都有$min(S_1 { m d} S_2) = min(S_1) $, (max(S_1 { m d} S_2)=max(S_1)cdot max(S_2)). 其中(min(S))(max(S))分别表示集合(S)中的最小值和最大值。

    再将其余三种运算扩展到(mathbb{2^Z})上,可得

    (min(S_1 + S_2)=min(S_1)+min(S_2), max(S_1+S_2)=max(S_1)+max(S_2))

    (min(S_1-S_2) = min(S_1)-max(S_2), max(S_1-S_2) = max(S_1) - min(S_2))

    (min(S_1*S_2)=min{min(S_1)*min(S_2), min(S_1)*max(S_2), max(S_1) * min(S_2), max(S_1)*max(S_2)})

    (max(S_1*S_2)=max{min(S_1)*min(S_2), min(S_1)*max(S_2), max(S_1) * min(S_2), max(S_1)*max(S_2)})

    由于我们只关注最大和最小的结果,所以可以直接用二元组(<min(S), max(S)>)来表示一个子表达式的运算结果,按照上述扩展定义进行运算即可。

    p.s.虽然从实际意义来看d运算不满足结合律,但如果只考虑二元组(<min(S), max(S)>)的话,有(<min(S_1 { m d} S_2 { m d} cdots { m d} S_n), max(S_1 { m d} S_2 { m d} cdots { m d} S_n)> = <min(S_1), max(S_1) max(S_2) cdots max(S_n)>),是无需考虑({ m d})运算的结合顺序的。

    #include <bits/stdc++.h>
    using namespace std;
    
    struct Interval
    {
    	int L, R;
    	Interval(){}
    	Interval(int a, int b) : L(a), R(b) {}
    	void Print(){printf("[%d,%d]
    ", L, R);}
    };
    Interval operator + (const Interval &a, const Interval &b)
    {
    	return Interval(a.L + b.L, a.R + b.R);
    }
    Interval operator - (const Interval &a, const Interval &b)
    {
    	return Interval(a.L - b.R, a.R - b.L);
    }
    Interval operator * (const Interval &a, const Interval &b)
    {
    	int mn = min(min(a.L * b.L, a.L * b.R), min(a.R * b.L, a.R * b.R));
    	int mx = max(max(a.L * b.L, a.L * b.R), max(a.R * b.L, a.R * b.R));
    	return Interval(mn, mx);
    }
    Interval f(const Interval &a, const Interval &b)
    {
    	assert(a.L >= 0 && b.L >= 1);
    	return Interval(a.L, a.R * b.R);
    }
    int RPNLen, RPNNumLen;
    Interval RPNNum[105];
    char s[105], RPN[105];
    
    inline int precedence(char ope) {
    	if (ope == '+') return 1;
    	if (ope == '-') return 1;
    	if (ope == '*') return 2;
    	if (ope == '/') return 2;
    	if (ope == 'd') return 3;
    	return 0;
    }
    
    void expressionToRPN() {
    	int stackLen = 0, x = 0;
    	char stack[105];
    	RPNLen = 0;
    	RPNNumLen = 0;
    	for (int i = 0; s[i] != ''; i++) {
    		if (s[i] >= '0' && s[i] <= '9') {
    			if (i == 0 || s[i-1] < '0' || s[i-1] > '9') {
    				RPNLen++;
    				RPN[RPNLen] = 'N';
    				x = s[i] - '0';
    			} else {
    				x = x * 10 - '0' + s[i];
    			}
    			if(s[i+1] < '0' || s[i+1] > '9')
    				RPNNum[++RPNNumLen] = Interval(x, x);
    		} else if (s[i] == '(') {
    			stackLen++;
    			stack[stackLen] = s[i];
    		} else if (s[i] == ')') {
    			while (stack[stackLen] != '(') {
    				RPNLen++;
    				RPN[RPNLen] = stack[stackLen];
    				stackLen--;
    			}
    			stackLen--;
    		} else {
    			while (stackLen > 0 && precedence(s[i]) <= precedence(stack[stackLen])) {
    				RPNLen++;
    				RPN[RPNLen] = stack[stackLen];
    				stackLen--;
    			}
    			stackLen++;
    			stack[stackLen] = s[i];
    		}
    	}
    	while (stackLen > 0) {
    		RPNLen++;
    		RPN[RPNLen] = stack[stackLen];
    		stackLen--;
    	}
    }
    
    Interval CalcRPN() {
    	int RPNNumCnt = 0, stackLen = 0;
    	Interval stack[105];
    	for (int i = 1; i <= RPNLen; i++) {
    		if (RPN[i] == 'N') {
    			RPNNumCnt++;
    			stackLen++;
    			stack[stackLen] = RPNNum[RPNNumCnt];
    		} else {
    			Interval b = stack[stackLen];
    			stackLen--;
    			Interval a = stack[stackLen];
    			stackLen--;
    			Interval result;
    			//printf("%c
    ", RPN[i]);
    			//a.Print(), b.Print();
    			if(RPN[i] == '+') result = a + b;
    			if(RPN[i] == '-') result = a - b;
    			if(RPN[i] == '*') result = a * b;
    			if(RPN[i] == 'd') result = f(a, b);
    			//result.Print();
    			stackLen++;
    			stack[stackLen] = result;
    		}
    	}
    	return stack[stackLen];
    }
    
    int main() {
    	while (scanf("%s", s) == 1) {
    		expressionToRPN();
    		Interval ans = CalcRPN();
    		printf("%d %d
    ", ans.L, ans.R);
    	}
    	return 0;
    }
    

    附对拍用的std.py

    # Haizs
    import re
    class Interval:
        def __init__(self, l, r):
            self.l = l
            self.r = r
    
        def __str__(self):
            return "%d %d" % (self.l, self.r)
    
        def __add__(self, b):
            return Interval(self.l + b.l, self.r + b.r)
    
        def __sub__(self, b):
            return Interval(self.l - b.r, self.r - b.l)
    
        def __mul__(self, b):
            mn = min(min(self.l*b.l, self.r*b.r), min(self.l*b.r, self.r*b.l))
            mx = max(max(self.l*b.l, self.r*b.r), max(self.l*b.r, self.r*b.l))
            return Interval(mn, mx)
    
        def __pow__(self, b):
            return Interval(self.l, self.r * b.r)
    
    a = input()
    while a:
        a = a.replace("d", "**")
        b = re.sub(r"(d+)", r"Interval(1,1)", a)
        # print(b)
        print(eval(b))
        try:
            a = input()
        except:
            break
    

    by catsworld & Asm.Def

    C. Convex Hull

    链接

    期望难度:Medium

    Solution 1

    不考虑外层循环的情况,那么答案显然是:

    [ans = sum_{i=1}^{sqrt{x}} mu(i) * frac{1}{6}(frac{x}{i^2}+1) * (frac{x}{i^2}) * (2(frac{x}{i^2}))+1) * i^4 ]

    在加了外层循环的情况下,考虑计算(mu(i) * i^4)的系数
    (sum(i)=sum_{j=1}^i j^2)
    对于每一个(mu(i) * i^4),它在全部的答案中出现次数为(n-i^2+1)次,可以推出系数为(sum_{j=i^2}^n sum(frac{j}{i^2}))
    (Max=frac{n}{i^2})
    考虑sum括号中的取值,可以发现,一定有(i*i个1,i*i个2...i*i个Max-1,(n-i*i+11-(Max-1)*i*i)个Max)
    所以,最终的系数为

    [egin{aligned} &i*i*(sum_{j=1}^{Max-1}sum(j)) + (n-i*i+1-(Max-1)*i*i)*sum(Max)\ = &Max*Max*(Max+1)*(Max-1)/12+(n-i*i+1-(Max-1)*i*i)*sum(Max) end{aligned} ]

    考虑到模数很大,计算过程中需要用类似于分治乘法的思路或int128。

    By WYJ2015

    Solution2

    答案可转化为

    [sum_{i=1}^n gay(i) cdot (n+1-i) mod p ]

    (sumlimits_{i=1}^{n} gay(i) (n+1-i))中,(i^2 cdot (n+1-i))被计入答案当且仅当i不含有平方因子。不妨考虑对所有i的因子进行容斥,即

    [egin{aligned} Ans &= sum_{x=1}^{[sqrt{n}]} mu(x) sum_{k=1}^{[frac{n}{x^2}]} (k x^2)^2 * (n+1-k x^2) mod p\ &= sum_{x=1}^{[sqrt{n}]} mu(x) left( (n+1)x^4 sum_{k=1}^{[frac{n}{x^2}]} k^2 - x^6 sum_{k=1}^{[frac{n}{x^2}]} k^3 ight) mod p\ end{aligned} ]

    其中,平方和与立方和为

    [egin{aligned} sum_{i=1}^n i^2 &= frac{n(n+1)(2n+1)}{6}\ sum_{i=1}^n i^3 &= left(frac{n(n+1)}{2} ight)^2 end{aligned} ]

    直接枚举x后代入计算即可。

    在计算(x imes y mod p)时,由于p的最大值为(10^{11}),可能会超过long long的表示范围,可以将x拆成((acdot 2^{20} + b)),将y拆成((ccdot 2^{20} + d)),将每次乘法的运算数范围限制在(2^{20})以内,可以直接用((((((a * c) << 20) + (a * d + b * c)) % mod) << 20) + b * d) % mod计算出结果。

    时间复杂度(O(sqrt{N}))

    by Asm.Def

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 100005;
    typedef long long LL;
    
    int mu[maxn], P[maxn], pcnt;
    bool not_p[maxn];
    LL N, mod;
    
    const LL lb = (1LL << 20) - 1;
    
    inline LL mult(LL x, LL y, LL mod)
    {
    	LL a = x >> 20, b = x & lb;
    	LL c = y >> 20, d = y & lb;
    	return ( ( ( ( ( (a * c) << 20) + (a * d + b * c) ) % mod) << 20) + b * d) % mod;
    }
    
    inline LL S2(LL N)
    {
    	return mult(mult(N, N+1, 6*mod), (2*N+1), 6*mod) / 6;
    	//return (__int128(N) * (N+1) * (2*N+1) / 6) % mod;
    }
    
    inline LL S3(LL N)
    {
    	LL t = mult(N, N+1, mod<<1) >> 1;
    	//LL t = (__int128(N) * (N+1) / 2) % mod;
    	return mult(t, t, mod);
    }
    
    void init()
    {
    	mu[1] = 1;
    	for(int i = 2;i < maxn;++i)
    	{
    		if(!not_p[i]) P[pcnt++] = i, mu[i] = -1;
    		for(int j = 0;j < pcnt;++j)
    		{
    			if(i * P[j] >= maxn) break;
    			not_p[i * P[j]] = true;
    			if(i % P[j] == 0)
    			{
    				mu[i * P[j]] = 0;
    				break;
    			}
    			mu[i * P[j]] = -mu[i];
    		}
    	}
    }
    
    void work()
    {
    	LL ans = 0;
    	for(int i = 1;LL(i) * i <= N;++i) if(mu[i])
    	{
    		LL t = LL(i) * i, t2 = mult(t, t, mod);
    		ans = (ans + mu[i] * mult( mult(t2,N+1,mod), S2(N/t), mod) ) % mod;
    		ans = (ans - mu[i] * mult(mult(t2,t,mod), S3(N/t), mod)) % mod;
    	}
    	printf("%lld
    ", (ans + mod) % mod);
    }
    
    int main()
    {
    	init();
    
    	while(~scanf("%lld%lld", &N, &mod))
    	{
    		work();
    	}
    	return 0;
    }
    

    D. Made In Heaven

    链接

    by XLC

    期望难度:Easy

    K短路模板题。由于数据均为随机生成,直接预处理出每个点到终点的最短路后A*搜索即可。

    E. The Cake Is A Lie

    链接

    by Haizs

    期望难度:Medium

    二分答案,那么每次check相当于是给定一个半径的圆,然后问这个圆最多覆盖多少个点,我们可以枚举一个点,然后再枚举每个与他距离<=2r的点,就可以求出所有的相交弧,离散化之后,求出覆盖最多次的弧,就是答案了。复杂度(O(n^2log(n)cdot log(30000)))

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 605;
    const double eps = 1e-6;
    int i, j, t, n, m, k, z, y, x;
    double l, r, mid;
    int R;
    int cmp(double x)
    {
        if (fabs(x) < eps) return 0;
        if (x > 0) return 1;
        return -1;
    }
    struct point
    {
        double x, y;
        point() {}
        point(double _x, double _y)
            : x(_x), y(_y) {}
        void input()
        {
            scanf("%lf%lf", &x, &y);
        }
        friend point operator+(const point &a, const point &b)
        {
            return point(a.x + b.x, a.y + b.y);
        }
        friend point operator-(const point &a, const point &b)
        {
            return point(a.x - b.x, a.y - b.y);
        }
        double norm()
        {
            return sqrt(x * x + y * y);
        }
        double angl()
        {
            return atan2(y, x);
        }
    } poi[maxn];
    double dis[maxn][maxn], ang[maxn][maxn];
    pair<double, int> pdi[maxn];
    #define mp(a, b) make_pair(a, b)
    bool check(double r)
    {
        int i, j, t, ans = 1, k;
        double d;
        for (i = 1; i <= n; i++)
        {
            t = 0;
            for (j = 1; j <= n; j++)
                if (i != j)
                {
                    if (cmp(dis[i][j] - 2.0 * r) > 0) continue;
                    d = acos(dis[i][j] / (2.0 * r));
                    pdi[++t] = mp(ang[i][j] - d, 1);
                    pdi[++t] = mp(ang[i][j] + d, -1);
                }
            sort(pdi + 1, pdi + t + 1);
            k = 1;
            for (j = 1; j <= t; j++)
            {
                k += pdi[j].second;
                ans = max(ans, k);
            }
        }
        return ans >= m;
    }
    int main()
    {
        // cout << time(NULL) << endl;
        // freopen("tcial.in", "r", stdin);
        // freopen("tcial.out", "w", stdout);
        int T, I;
        scanf("%d", &T);
        for (I = 1; I <= T; I++)
        {
            scanf("%d%d", &n, &m);
            for (i = 1; i <= n; i++) poi[i].input();
            scanf("%d", &R);
            if (m > n)
            {
                printf("The cake is a lie.
    ");
                continue;
            }
            for (i = 1; i <= n; i++)
                for (j = 1; j <= n; j++)
                    dis[i][j] = (poi[j] - poi[i]).norm(), ang[i][j] = (poi[j] - poi[i]).angl();
            l = R;
            r = 30000;
            while (r - l > eps)
            {
                mid = (l + r) / 2;
                if (check(mid - R))
                    r = mid;
                else
                    l = mid;
            }
            printf("%.6f
    ", r);
        }
        // cout << time(NULL) << endl;
        return 0;
    }
    

    F. Fantastic Graph

    https://nanti.jisuanke.com/t/31446](https://nanti.jisuanke.com/t/31446)

    by Infi

    期望难度:Easy

    添加源点s,汇点t。
    对于原图的边,定义流量为[0,1],s对于N个点都连边,流量为[L,R],M个点对t都连边,流量为[L,R]。那么就变成了有源汇上下界可行流问题。根据相关方法建图即可。

    G. Spare Tire

    链接

    by ZGH

    期望难度:Easy+

    观察递推方程,不难看出通项公式的形式:

    [a_n = k p^n + an^2 + bn + c ]

    代入后解得(p=1, a=1, b=1, c=-k)
    (a_n = n^2 + n)

    则答案为

    [egin{aligned} &sum_{i=1}^{n} [gcd(i, m)=1] (i^2 + i)\ =&sum_{d|m land d leq n} mu(d) cdot sum_{t=1}^{[frac{n}{d}]} ((td)^2 + td)\ =&sum_{d|m land d leq n} mu(d) cdot left( d^2 cdot sum_{t=1}^{[frac{n}{d}]} t^2 + dcdot sum_{t=1}^{[frac{n}{d}]} t ight) end{aligned} ]

    对m分解质因数后dfs枚举所有满足条件且(mu(d))不为0的d,然后用求和公式计算后半部分的贡献。

    H. Hamming Weight

    链接

    by Asm.Def

    期望难度:Hard

    将N表示为

    [N = sum_{i=0}^{n-1} A_i 2^i, A_i in {0,1} ]

    由于位与运算每一位是独立的,不妨对每一位单独考虑它对答案的贡献:

    [Ans(N) = sum_{i=0}^{n-1}left[A_i cdot (1 +sum_{j=0}^{i-1}A_jcdot 2^j )+ 2^i cdot sum_{j=i+1}^{n-1} A_jcdot 2^{j-i-1} ight]^2 ]

    如果直接用FFT计算每次平方,时间复杂度为(O(n^2 log n)) ,难以接受。

    考虑在N的某个区间([L, R))上定义答案,并将答案写成多项式的形式,即

    [Ans_{[L,R)}(x)=sum_{i=L}^{R-1} left[A_i cdot (1 +sum_{j=L}^{i-1}A_jcdot x^{j-L} )+ x^{i-L} cdot sum_{j=i+1}^{R-1} A_jcdot x^{j-i-1} ight]^2 ]

    其中有一项常数项,不方便合并,因此先将答案拆开:

    [egin{aligned} &Ans_{[L,R)}(x)\ =&sum_{i=L}^{R-1} left[A_i^2 + 2A_i(A_i cdot sum_{j=L}^{i-1}A_jcdot x^{j-L} + x^{i-L} cdot sum_{j=i+1}^{R-1} A_jcdot x^{j-i-1}) \+ (A_i cdot sum_{j=L}^{i-1}A_jcdot x^{j-L} + x^{i-L} cdot sum_{j=i+1}^{R-1} A_jcdot x^{j-i-1})^2 ight] end{aligned} ]

    考虑到(A_i)的取值范围为0或1,即(A_i^2 = A_i),可将答案转化为

    [egin{aligned} &Ans_{[L,R)}(x)\ =&sum_{i=L}^{R-1} A_i + 2 sum_{i=L}^{R-1} A_i cdot left( sum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{R-1} A_jcdot x^{j-L-1} ight) + sum_{i=L}^{R-1} left(A_isum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{R-1} A_jcdot x^{j-L-1} ight)^2 end{aligned} ]

    [egin{array}{ccl} &S(i)&=&sumlimits_{j=i}^{n-1} A_i\ &F_{[L,R)}(x)&=&sumlimits_{i=L}^{R-1}A_i x^i\ &Ans1_{[L,R)}(x)&=&sumlimits_{i=L}^{R-1} A_i cdot left( sumlimits_{j=L}^{i-1}A_jcdot x^{j-L} + sumlimits_{j=i+1}^{R-1} A_jcdot x^{j-L-1} ight)\ &Ans2_{[L,R)}(x)&= &sumlimits_{i=L}^{R-1} left(A_isumlimits_{j=L}^{i-1}A_jcdot x^{j-L} + sumlimits_{j=i+1}^{R-1} A_jcdot x^{j-L-1} ight)^2 end{array} ]

    (Ans_{[L,R)}(x) = S(L)-S(R) + 2Ans1_{[L,R)}(x) + Ans2_{[L,R)}(x))

    考虑如何合并两个相邻区间([L, mid))([mid,R))的答案。

    [egin{aligned} Ans1_{[L,R)}(x)=&sum_{i=L}^{R-1} A_i cdot left( sum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{R-1} A_jcdot x^{j-L-1} ight)\ =&sum_{i=L}^{mid-1} A_i cdot left( sum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{mid-1} A_jcdot x^{j-L-1} + sum_{j=mid}^{R-1} A_j cdot x^{j-L-1} ight)\ &+ sum_{i=mid}^{R-1} A_i cdot left( sum_{j=mid}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{R-1} A_jcdot x^{j-L-1} + sum_{j=L}^{mid-1} x^{j-L} ight)\ =&Ans1_{[L,mid)}(x) + Ans1_{[mid,R)}(x)cdot x^{mid-L}\ &+ left(sum_{i=L}^{mid-1} A_i ight) cdot left( sum_{j=mid}^{R-1} A_j cdot x^{j-L-1} ight)cdot + left(sum_{i=mid}^{R-1} A_i ight) cdot left( sum_{j=L}^{mid-1} A_j cdot x^{j-L} ight)\ =&Ans1_{[L,mid)}(x) + Ans1_{[mid,R)}(x)cdot x^{mid-L} \&+sum_{j=mid}^{R-1}(S(L)-S(mid))cdot A_j x^{j-L-1}+sum_{j=L}^{mid-1}(S(mid)-S(R))cdot A_j x^{j-L} end{aligned} ]

    [egin{aligned} Ans2_{[L,R)}(x)=&sum_{i=L}^{R-1} left(A_isum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{R-1} A_jcdot x^{j-L-1} ight)^2\ =&sum_{i=L}^{mid-1} left(A_isum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{mid-1} A_jcdot x^{j-L-1} + sum_{j=mid}^{R-1}A_jcdot x^{j-L-1} ight)^2 \&+ sum_{i=mid}^{R-1} left(A_isum_{j=mid}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{R-1} A_jcdot x^{j-L-1} + A_isum_{j=L}^{mid-1} A_jcdot x^{j-L} ight)^2\ =&Ans2_{[L,mid)}(x) + Ans2_{[mid, R)}(x) cdot x^{2(mid-L)}\ &+2sum_{i=L}^{mid-1} left(A_isum_{j=L}^{i-1}A_jcdot x^{j-L} + sum_{j=i+1}^{mid-1}A_jcdot x^{j-L-1} ight)cdot F_{[mid,R)}(x) x^{mid-L-1} \ &+2sum_{i=mid}^{R-1} A_i left( sum_{j=mid}^{i-1}A_jcdot x^{j-mid} + sum_{j=i+1}^{R-1}A_jcdot x^{j-mid-1} ight) cdot x^{mid-L} cdot F_{[L,mid)}(x)\ &+F_{[mid,R)}(x)^2cdot (mid-L) cdot x^{2(mid-L-1)}+F_{[L,mid)}(x)^2cdot [S(mid) - S(R)] end{aligned} ]

    化简到这里,就可以通过两次长度为((mid-L))((R-mid))的FFT运算和若干次多项式加法、数乘和移位,由([L,mid))([mid,R))(O((R-L)log(R-L)))的时间内求出(Ans1_{[L,R)}(x), Ans2_{[L,R)}(x))。由于待求的相当于x=2时的点值,所以每次用FFT进行乘法后都可以对结果进行一次进位,从而确保在相乘的过程中系数不会溢出。

    (Ans_{[0,n)}(2)​)分治求解,总复杂度(O(nlog^2 n)​).

    //Asm.Def
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 200005, mod = 998244353, g = 3, maxk = 1 << 19;
    typedef long long LL;
    typedef vector<int> Poly;
    int A[maxn], N, Sum[maxn];
    
    void Print(const Poly &ans)
    {
    	for(int i = ans.size()-1;i >= 0;--i) printf("%d ", ans[i]);
    	puts("");
    }
    
    int powmod(int a, int n)
    {
    	int ans = 1;
    	while(n)
    	{
    		if(n & 1) ans = (LL) ans * a % mod;
    		a = (LL) a * a % mod;
    		n >>= 1;
    	}
    	return ans;
    }
    
    void Carry(Poly &x)
    {
    	int C = 0, c;
    	while(x.size() > 1)
    	{
    		if(x.back() == 0) x.pop_back();
    		else break;
    	}
    
    	for(int i = 0;i < x.size() || C;++i)
    	{
    		if(i < x.size())
    		{
    			x[i] += C;
    			C = x[i] >> 1;
    			x[i] = x[i] & 1;
    		}
    		else
    		{
    			x.push_back(C & 1);
    			C >>= 1;
    		}
    	}
    }
    
    //Poly operator * (const Poly &a, const Poly &b)
    //{
    //	Poly ans(a.size() + b.size() - 1);
    //	for(int i = 0;i < (int) a.size();++i)
    //		for(int j = 0;j < (int) b.size();++j)
    //			ans[i+j] += a[i] * b[j];
    //	Carry(ans);
    //	return ans;
    //}
    
    void Shuff(int A[], int n)//n为位数
    {
    	static int B[maxk];
    	int N = 1 << n, j, mx = 1 << (n-1);
    	for(int i = 0, it = 0;i < N;++i)
    	{
    		B[i] = A[it];
    		j = mx;
    		while(it & j)
    		{
    			it ^= j;
    			j >>= 1;
    		}
    		it ^= j;
    	}
    	for(int i = 0;i < N;++i) A[i] = B[i];
    }
    
    void DFT(Poly &A, int n, int a)//n为位数
    {
    	static int B[maxk], pw[20];
    	int N = 1 << n;
    	pw[n-1] = a;
    	for(int i = n-1;i;--i) pw[i-1] = (LL) pw[i] * pw[i] % mod;
    	A.resize(N);
    	for(int i = 0;i < N;++i) B[i] = A[i];
    	Shuff(B, n);
    	for(int i = 0;i < n;++i)
    	{
    		int d = (1 << i), x0 = pw[i];
    		for(int j = 0;j < N;j += (d<<1))
    		{
    			for(int k = j, x = 1;k < j+d;++k)
    			{
    				int t = (LL) x * B[k+d] % mod;
    				B[k+d] = (B[k] + mod - t) % mod;
    				B[k] = (B[k] + t) % mod;
    				x = (LL) x * x0 % mod;
    			}
    		}
    	}
    	for(int i = 0;i < N;++i) A[i] = B[i];
    	//Print(A);
    }
    
    Poly operator * (const Poly &x, const Poly &y)
    {
    	static Poly A, B;
    	A = x, B = y;
    	while(A.size() > 1)
    	{
    		if(A.back() == 0) A.pop_back();
    		else break;
    	}
    	while(B.size() > 1)
    	{
    		if(B.back() == 0) B.pop_back();
    		else break;
    	}
    	int n = 0;
    	while((1<<n) < A.size() + B.size()) ++n;
    
    	int a = powmod(g, (mod-1) >> n);
    	DFT(A, n, a);
    	DFT(B, n, a);
    
    	for(int i = 0;i < (1 << n);++i) A[i] = (LL) A[i] * B[i] % mod;
    	DFT(A, n, powmod(a, mod-2));
    	int t = powmod((1 << n), mod-2);
    	for(int i = 0;i < (1 << n);++i) A[i] = (LL) A[i] * t % mod;
    
    	//Print(A);
    	Carry(A);
    	return A;
    }
    
    Poly operator * (const Poly &a, int x)
    {
    	Poly ans(a.size());
    	for(int i = 0;i < (int) a.size();++i)
    		ans[i] = a[i] * x;
    	Carry(ans);
    	return ans;
    }
    
    Poly operator + (const Poly &a, const Poly &b)
    {
    	Poly ans(max(a.size(), b.size()));
    	for(int i = 0;i < (int) ans.size();++i)
    	{
    		ans[i] = 0;
    		if(i < (int) a.size()) ans[i] += a[i];
    		if(i < (int) b.size()) ans[i] += b[i];
    	}
    	return ans;
    }
    
    Poly operator << (const Poly &x, int n)
    {
    	Poly ans(x.size() + n, 0);
    	for(int i = 0;i < x.size();++i) ans[n+i] = x[i];
    	return ans;
    }
    
    //void Multi(const Poly &a, const Poly &b, Poly &ans)
    //{
    //	ans.resize(a.size() + b.size() - 1);
    //	for(int i = 0;i < (int) a.size();++i)
    //		for(int j = 0;j < (int) b.size();++j)
    //			ans[i+j] += a[i] * b[j];
    //}
    
    void init()
    {
    	for(int i = 1;i <= N;++i) scanf("%1d", &A[N-i]);
    	Sum[N] = 0;
    	for(int i = N-1;i >= 0;--i)
    		Sum[i] = Sum[i+1] + A[i];
    }
    
    
    void Solve(int L, int R, Poly &Ans1, Poly &Ans2)
    {
    	static Poly SL, SR, AL, AR;
    	if(R - L == 1)
    	{
    		Ans1.resize(1);Ans1[0] = 0;
    		Ans2.resize(1);Ans2[0] = 0;
    		return;
    	}
    	int mid = (L + R) >> 1;
    	Poly Ans1L, Ans1R, Ans2L, Ans2R;
    	Solve(L, mid, Ans1L, Ans2L);
    	Solve(mid, R, Ans1R, Ans2R);
    	//printf("Solve (%d,%d)
    ", L, R);
    	//
    	//Print(Ans1L);
    	//Print(Ans1R);
    	//Print(Ans2L);
    	//Print(Ans2R);
    	//puts("");
    
    	AL.clear();
    	AL.resize(mid-L);
    	for(int i = L;i < mid;++i) AL[i-L] = A[i];
    
    	AR.clear();
    	AR.resize(R-mid);
    	for(int i = mid;i < R;++i) AR[i-mid] = A[i];
    
    	SL.clear();
    	SL.resize(mid-L);
    	for(int i = L;i < mid-1;++i)
    		SL[i-L] = A[i] * (Sum[i+1]-Sum[mid]) + A[i+1] * (i+1-L);
    
    	SR.clear();
    	SR.resize(R-mid);
    	for(int i = mid;i < R-1;++i)
    		SR[i-mid] = A[i] * (Sum[i+1]-Sum[R]) + A[i+1] * (Sum[mid]-Sum[i+1]);
    
    	//puts("");
    	//Print(AL);
    	//Print(AR);
    	//Print(SL);
    	//Print(SR);
    	
    	Ans2 = Ans2L + ((AR * SL) << (mid-L)) + ((AR * AR * (mid - L)) << (2 * (mid-L-1))) + (Ans2R << (2 * (mid-L))) + ((AL * SR) << (mid-L+1)) + AL * AL * (Sum[mid]-Sum[R]);
    	Carry(Ans2);
    
    	Ans1 = Ans1L + (Ans1R << (mid-L));
    	if((int) Ans1.size() < (R-L-1))
    		Ans1.resize(R-L-1);
    	for(int i = L;i < mid;++i)
    		Ans1[i-L] += A[i] * (Sum[mid] - Sum[R]);
    	for(int i = mid;i < R;++i)
    		Ans1[i-L-1] += A[i] * (Sum[L] - Sum[mid]);
    	Carry(Ans1);
    	//Print(Ans1);
    	//Print(Ans2);
    }
    
    void work()
    {
    	Poly ans1, ans2;
    	Solve(0, N, ans1, ans2);
    	//Print(ans1), Print(ans2);
    	ans2 = ans2 + (ans1 << 1);
    	ans2[0] += (Sum[0] - Sum[N]);
    	Carry(ans2);
    	int sum = 0;
    	for(int i = ans2.size()-1;i >= 0;--i) printf("%d", ans2[i]);
    	puts("");
    	//for(int i = 0;i < (int) ans.size();++i)
    	//	sum += ans[i];
    	//printf("%d
    ", sum % 1000000007);
    }
    
    int main()
    {
    	time_t beg = clock();
    	while(~scanf("%d", &N))
    	{
    		init();
    		work();
    	}
    	//printf("%.2f sec", double(clock() - beg) / CLOCKS_PER_SEC);
    	return 0;
    }
    

    I. Lattice's basics in digital electronics

    链接

    by Joker

    期望难度:Easy

    签到题。直接根据题意模拟即可,可以采用map来减少编码难度。

    J. Ka Chang

    链接

    期望难度:Medium-

    按每一层的结点个数分类讨论,设阈值为(T)

    当第(L)层的结点个数(Size_L <T)时,每次(1 L X)操作只需枚举这一层的所有结点,维护它们对每个结点的答案产生的贡献即可。

    当第(L)层的结点个数(Size_L >= T)时,这样的层不超过(frac{N}{T})个,对于操作1可以直接对每个这样的层维护增加了多少point,对于每次询问直接枚举一遍即可。

    对于第一种情况,可以用树状数组维护dfs序列上的区间和,时间复杂度(O(Q(Tcdot log N)));
    对于第二种情况,时间复杂度(O(Qcdot frac{N}{T}))

    则总时间复杂度为(O(Q cdot (T log N + frac{N}{T}))),取(T=sqrt{frac{N}{log N}}) 最优。

    时间复杂度(O(Qcdotsqrt{Nlog N}))

    by bird_14

    K. Supreme Number

    链接

    by morejarphone

    期望难度:Easy-

    考虑到答案中任意一位都必须是1或质数,可知答案只可能由1、2、3、5、7构成。由于任意两个不为1的数字构成的两位数一定可以被11整除,所以答案中除1外的数字只能出现一次;1最多出现2次,因为111可以被3整除;而2、5、7三者一定不会有两者同时出现。因此满足条件的整数不会超过四位,全部预处理出来即可。

  • 相关阅读:
    Oracle课堂实验一“表的使用”代码。
    Oracle安装时忘记解锁scott用户的解决方案
    PHP中CURL技术模拟登陆抓取网站信息,用与微信公众平台成绩查询
    Fckeditor漏洞利用总结
    js 倒计时 button不可用
    .net 下载图片
    截取字符串
    js 后台弹窗
    账号注册,密码安全级别提示(弱、中、强)代码
    jquery 评论等级(很差,差,一般,好,很好)代码
  • 原文地址:https://www.cnblogs.com/Asm-Definer/p/9610262.html
Copyright © 2020-2023  润新知