1. 程式人生 > >【2018沈陽網絡賽】不太敢自稱官方的出題人題解

【2018沈陽網絡賽】不太敢自稱官方的出題人題解

typedef https 這樣的 ctr rpn tps 2.0 urn number

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\n", double(clock()-beg) / CLOCKS_PER_SEC);
    return 0;
}

B. Call of Accepted

鏈接

期望難度:Medium

表達式求值問題可以將中綴表達式轉換為後綴表達式,其中轉換步驟使用調度場算法 與後綴表達式的求值 均在維基百科中有詳細的介紹。

根據d運算的描述,\(x\ {\rm d}\ y\)本質上是一個定義在整數集的冪集上的運算,即\(對於任意 S_1, S_2 \in \mathbb{2^Z}且 \min(S_1) \geq 0 且 \min(S_2)\geq 1\),定義\(S_1\ {\rm d}\ S_2 = \{x\in \mathbb{Z} \mid \exists a\in S_1, \exists b\in S_2, a\leq x \leq ab\}\)

. 則對於任意一次有定義的\({\rm d}\)運算,都有$\min(S_1 {\rm d} S_2) = \min(S_1) $, \(\max(S_1\ {\rm 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\ {\rm d}\ S_2\ {\rm d}\ \cdots\ {\rm d}\ S_n),\ \max(S_1\ {\rm d}\ S_2\ {\rm d}\ \cdots\ {\rm d}\ S_n)>\ =\ <\min(S_1),\ \max(S_1) \max(S_2) \cdots \max(S_n)>\),是無需考慮\({\rm 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]\n", 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] != ‘\0‘; 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\n", 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\n", 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\)
所以,最終的系數為
\[ \begin{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 \]

\(\sum\limits_{i=1}^{n} gay(i) (n+1-i)\)中,\(i^2 \cdot (n+1-i)\)被計入答案當且僅當i不含有平方因子。不妨考慮對所有i的因子進行容斥,即
\[ \begin{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 \right) \mod p\\end{aligned} \]

其中,平方和與立方和為
\[ \begin{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}\right)^2 \end{aligned} \]

直接枚舉x後代入計算即可。

在計算\(x \times y \mod p\)時,由於p的最大值為\(10^{11}\),可能會超過long long的表示範圍,可以將x拆成\((a\cdot 2^{20} + b)\),將y拆成\((c\cdot 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\n", (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^2\log(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.\n");
            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\n", 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\)

則答案為
\[ \begin{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 + d\cdot \sum_{t=1}^{[\frac{n}{d}]} t \right) \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_j\cdot 2^j )+ 2^i \cdot \sum_{j=i+1}^{n-1} A_j\cdot 2^{j-i-1} \right]^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_j\cdot x^{j-L} )+ x^{i-L} \cdot \sum_{j=i+1}^{R-1} A_j\cdot x^{j-i-1} \right]^2 \]

其中有一項常數項,不方便合並,因此先將答案拆開:
\[ \begin{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_j\cdot x^{j-L} + x^{i-L} \cdot \sum_{j=i+1}^{R-1} A_j\cdot x^{j-i-1}) \\+ (A_i \cdot \sum_{j=L}^{i-1}A_j\cdot x^{j-L} + x^{i-L} \cdot \sum_{j=i+1}^{R-1} A_j\cdot x^{j-i-1})^2 \right] \end{aligned} \]
考慮到\(A_i\)的取值範圍為0或1,即\(A_i^2 = A_i\),可將答案轉化為
\[ \begin{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_j\cdot x^{j-L} + \sum_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} \right) + \sum_{i=L}^{R-1} \left(A_i\sum_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} \right)^2 \end{aligned} \]

\[ \begin{array}{ccl} &S(i)&=&\sum\limits_{j=i}^{n-1} A_i\&F_{[L,R)}(x)&=&\sum\limits_{i=L}^{R-1}A_i x^i\&Ans1_{[L,R)}(x)&=&\sum\limits_{i=L}^{R-1} A_i \cdot \left( \sum\limits_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum\limits_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} \right)\&Ans2_{[L,R)}(x)&= &\sum\limits_{i=L}^{R-1} \left(A_i\sum\limits_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum\limits_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} \right)^2 \end{array} \]

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

考慮如何合並兩個相鄰區間\([L, mid)\)\([mid,R)\)的答案。
\[ \begin{aligned} Ans1_{[L,R)}(x)=&\sum_{i=L}^{R-1} A_i \cdot \left( \sum_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} \right)\=&\sum_{i=L}^{mid-1} A_i \cdot \left( \sum_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{mid-1} A_j\cdot x^{j-L-1} + \sum_{j=mid}^{R-1} A_j \cdot x^{j-L-1} \right)\\ &+ \sum_{i=mid}^{R-1} A_i \cdot \left( \sum_{j=mid}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} + \sum_{j=L}^{mid-1} x^{j-L} \right)\=&Ans1_{[L,mid)}(x) + Ans1_{[mid,R)}(x)\cdot x^{mid-L}\\ &+ \left(\sum_{i=L}^{mid-1} A_i \right) \cdot \left( \sum_{j=mid}^{R-1} A_j \cdot x^{j-L-1} \right)\cdot + \left(\sum_{i=mid}^{R-1} A_i \right) \cdot \left( \sum_{j=L}^{mid-1} A_j \cdot x^{j-L} \right)\=&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} \]

\[ \begin{aligned} Ans2_{[L,R)}(x)=&\sum_{i=L}^{R-1} \left(A_i\sum_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} \right)^2\=&\sum_{i=L}^{mid-1} \left(A_i\sum_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{mid-1} A_j\cdot x^{j-L-1} + \sum_{j=mid}^{R-1}A_j\cdot x^{j-L-1} \right)^2 \\&+ \sum_{i=mid}^{R-1} \left(A_i\sum_{j=mid}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{R-1} A_j\cdot x^{j-L-1} + A_i\sum_{j=L}^{mid-1} A_j\cdot x^{j-L} \right)^2\=&Ans2_{[L,mid)}(x) + Ans2_{[mid, R)}(x) \cdot x^{2(mid-L)}\&+2\sum_{i=L}^{mid-1} \left(A_i\sum_{j=L}^{i-1}A_j\cdot x^{j-L} + \sum_{j=i+1}^{mid-1}A_j\cdot x^{j-L-1} \right)\cdot F_{[mid,R)}(x) x^{mid-L-1} \&+2\sum_{i=mid}^{R-1} A_i \left( \sum_{j=mid}^{i-1}A_j\cdot x^{j-mid} + \sum_{j=i+1}^{R-1}A_j\cdot x^{j-mid-1} \right) \cdot x^{mid-L} \cdot F_{[L,mid)}(x)\&+F_{[mid,R)}(x)^2\cdot (mid-L) \cdot x^{2(mid-L-1)}+F_{[L,mid)}(x)^2\cdot [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(n\log^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)\n", 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\n", 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(T\cdot \log N))\);
對於第二種情況,時間復雜度\(O(Q\cdot \frac{N}{T})\)

則總時間復雜度為\(O(Q \cdot (T \log N + \frac{N}{T}))\),取\(T=\sqrt{\frac{N}{\log N}}\) 最優。

時間復雜度\(O(Q\cdot\sqrt{N\log 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三者一定不會有兩者同時出現。因此滿足條件的整數不會超過四位,全部預處理出來即可。

【2018沈陽網絡賽】不太敢自稱官方的出題人題解