• 12月训练


    300iq contest 3

    F

    题意:给定$n$个怪的血量,先手攻击力$a$,后手攻击力$b$,先手每次任选一个攻击,后手只选编号最小的,求先手最多杀多少只

    先求出$L_i$表示杀第$i$只怪时可以节省的攻击次数,$R_i$表示不杀时可以节省的攻击次数

    初始先手节省次数为$1$,那么问题转化为每个位置选择$L_i$或$R_i$,使得每个前缀和均非负,那么可以先初始化每个位置都选$L$,然后用堆贪心撤销

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 3e5+10;
    int n, a, b, h[N], L[N], R[N];
    int main() {
        scanf("%d%d%d", &n, &a, &b);
        for (int i=1; i<=n; ++i) scanf("%d", &h[i]);
        for (int i=1; i<=n; ++i) {
            R[i] = (h[i]+b-1)/b;
            int t = h[i]-(R[i]-1)*b;
            L[i] = R[i]-1-(t+a-1)/a;
        }
        int ans = n;
        int64_t s = 1;
        priority_queue<int64_t> q;
        for (int i=1; i<=n; ++i) {
            s += L[i];
            q.push(R[i]-L[i]);
            while (s<0) { 
                s += q.top(); q.pop();
                --ans;
            }
        }
        printf("%d
    ", ans);
    }
    View Code

    J

    题意:给定图,每条边赋一个$[0,4]$的权值,要求每个点邻接边权值和是$5$的倍数,求方案数

    对于一个连通块,答案就是$5^{m-basis}$,如果是树的话,方案数只能为$1$,$basis=n-1$

    假设有环的话,如果是二分图的话,每个环交替填$-1,1$,一定合法,不影响$basis$

    如果是树上连一个奇环的话,方案数是$1$,$basis=n$

    由于$basis$一定是不超过$n$的,所以二分图答案$5^{m-n+1}$,否则$5^{m-n}$

    E

    题意:$n$堆石子,第$i$堆$a_i$个,两人轮流取,每次至多取$x$个,不能取则输,对于每个$x$输出谁赢

    bash博弈,等价于求$a_i space mod (x+1)$的异或和

    枚举每个二进制位,转化为求$sumlimits_{i=1}^n lfloorfrac{a_i-lfloorfrac{a_i}{x} floor x}{2^d} floor$的奇偶性

    枚举$a_i$值很小,可以枚举$t=lfloorfrac{a_i}{x} floor$,那么等价于求$sumlimits_{tle i<i+t}lfloorfrac{i-t}{2^d} floor$

    $lfloorfrac{i-t}{2^d} floor=lfloorfrac{i}{2^d} floor - lfloorfrac{t}{2^d} floor - [ispace mod 2^d<tspace mod 2^d]$

    第三项是个二维偏序,用树状数组的话总复杂度是$O(nlog^3 n)$过不去

    可以发现$i space mod 2^d$是循环的,预处理每块的前缀和就可以$O(nlog^2 n)$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 5e5+10, M = 19;
    bool cnt[N], sg[N], f[N], sum[N], g[N], h[N];
    int main() {
        ios::sync_with_stdio(0);
        cin.tie(0);
        int n;
        cin>>n;
        for (int i=1; i<=n; ++i) { 
            int t;
            cin>>t;
            cnt[t] ^= 1;
        }
        for (int i=1; i<=n; ++i) sum[i] = sum[i-1]^cnt[i];
        for (int d=0,num=1; num<=n; num<<=1,++d) {
            for (int i=1; i<=n; ++i) f[i] = g[i] = h[i] = 0;
            for (int i=1; i<=n; ++i) { 
                f[i] = f[i-1]^i>>d&cnt[i];
                g[i] = cnt[i];
                if (i%num) g[i] ^= g[i-1];
                h[i] = g[i];
                if (i>num) h[i] ^= h[i-num];
            }
            auto gao = [&](int i, int val) {
                if (i<=val) return g[i];
                bool ans = h[val+(i-val>>d<<d)];
                if (i%num<val) ans ^= g[i];
                return ans;
            };
            for (int x=d+1; x<=n+1; ++x) if (!sg[x]) {
                int tot = 0;
                for (int t=0; t<=n; t+=x) {
                    int lx = max(t,1), rx = min(n,t+x-1);
                    if (lx<=rx) { 
                        tot ^= f[rx]^f[lx-1];
                        tot ^= (sum[rx]^sum[lx-1])&(t>>d);
                        int val = t%num-1;
                        if (val>=0) {
                            tot ^= gao(rx,val);
                            if (lx>1) tot ^= gao(lx-1,val);
                        }
                    }
                }
                if (tot&1) sg[x] = 1;
            }
        }
        for (int x=2; x<=n+1; ++x) cout<<(sg[x]?"Alice":"Bob")<<" 
    "[x==n+1];
    }
    View Code

    gym102798J

    题意:$n$堆石子,两人轮流取,每次要么选一个最小的黑堆取任意正数颗,要么选一个白堆取任意正数颗,不能取则输,求多少种染色方案使得后手必胜

    白堆就是nim游戏,对于黑堆,打个表可以发现,去除掉最大值后,如果最小值$x$个数为奇$sg=x-1$,否则$sg=x$

    枚举最小值以及最小值的出现次数,那么转化为求一个后缀的子集异或和为$s$的方案,倒序添点用线性基判断即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10, P = 1e9+7;
    int n, pw[N], fac[N], ifac[N];
    int64_t sg[N], a[N], suf[N], b[100];
    int qpow(int a, int n) {
        int ans = 1;
        for (; n; n>>=1,a=a*(int64_t)a%P) if (n&1) ans=ans*(int64_t)a%P;
        return ans;
    }
    int C(int n, int m) {
        return fac[n]*(int64_t)ifac[m]%P*ifac[n-m]%P;
    }
    int main() {
        scanf("%d", &n);
        pw[0] = fac[0] = 1;
        for (int i=1; i<=n; ++i) { 
            pw[i] = pw[i-1]*2%P;
            fac[i] = fac[i-1]*(int64_t)i%P;
        }
        ifac[n] = qpow(fac[n],P-2);
        for (int i=n-1; i>=0; --i) ifac[i] = ifac[i+1]*(i+1ll)%P;
        for (int i=1; i<=n; ++i) scanf("%lld", &a[i]);
        sort(a+1,a+1+n);
        for (int i=1; i<=n; ++i) sg[i] = sg[i-1]^a[i];
        for (int i=n; i; --i) suf[i] = suf[i+1]^a[i];
        int ans = !sg[n];
        auto calc = [&](int64_t s, int pos) {
            static int now = n;
            while (now>=pos) {
                int64_t t = a[now];
                for (int i=1; i<=*b; ++i) t = min(t, t^b[i]);
                if (t) b[++*b] = t;
                --now;
            }
            int ans = suf[pos]==s;
            for (int i=1; i<=*b; ++i) s = min(s, s^b[i]);
            if (s) return 0;
            return pw[n-pos+1-*b]-ans;
        };
        for (int i=n; i; --i) {
            int j = i;
            while (j-1>=1&&a[j-1]==a[i]) --j;
            for (int k=1; k<=i-j+1; ++k) {
                int64_t w = k&1?a[i]-1:a[i], d = (i-j+1-k)&1?a[i]:0;
                ans = (ans+C(i-j+1,k)*(int64_t)calc(sg[j-1]^d^w,i+1))%P;
                w = k&1?a[i]:a[i]-1;
                if (w==(sg[j-1]^d^suf[i+1])) ans = (ans+C(i-j+1,k))%P;
            }
            i = j;
        }
        printf("%d
    ", ans);
    }
    View Code

    XXI Open Cup. Grand Prix of Korea

    J

    题意:给定操作序列,点(0,0)有个障碍,往障碍上走的操作无效,$q$次询问,给定起点,求操作后终点坐标

    假设不会碰到障碍的话,横纵坐标求个和即可,如果会碰到的话,预处理出每个后缀碰到障碍时的终点即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 3e5+10;
    int n, q;
    pair<int,int> a[N];
    char s[N];
    pair<int,int> get(char c) {
        if (c=='L') return {1,0};
        if (c=='R') return {-1,0};
        if (c=='U') return {0,-1};
        return {0,1};
    }
    void Move(int &x, int &y, char c) {
        if (c=='L') --x;
        if (c=='R') ++x;
        if (c=='U') ++y;
        if (c=='D') --y;
    }
    int main() {
        scanf("%d%s%d", &n, s+1, &q);
        map<pair<int,int>,int> vis;
        int x = 0, y = 0;
        vis[{0,0}] = n+1;
        a[n] = get(s[n]);
        Move(x,y,s[n]);
        vis[{x,y}] = n;
        for (int i=n-1; i; --i) {
            if (s[i]==s[i+1]) a[i] = a[i+1];
            else {
                auto u = get(s[i]);
                pair<int,int> v{x+u.first,y+u.second};
                if (vis.count(v)) a[i] = a[vis[v]-1];
                else a[i] = v;
            }
            Move(x,y,s[i]);
            vis[{x,y}] = i;
        }
        vis.clear();
        x = y = 0;
        for (int i=1; i<=n; ++i) {
            Move(x,y,s[i]);
            if (!vis.count({x,y})) vis[{x,y}] = i;
        }
        while (q--) {
            int X, Y;
            scanf("%d%d", &X, &Y);
            if (!vis.count({-X,-Y})) printf("%d %d
    ", X+x, Y+y);
            else {
                int t = vis[{-X,-Y}];
                printf("%d %d
    ", a[t].first, a[t].second);
            }
        }
    }
    View Code

    H

    题意:$n$种数,给定每种数个数,每次选一个子集替换为$mex$,求最后剩一个数时的最大值

    特判只有一个数的情况,否则至少要有一次取mex操作,所以如果想要得到$x$,那么就要构造出$0,1,...,x-1$,可以二分答案判断

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 4e6+10;
    int n, c[N];
    int64_t d[N];
    
    int chk(int x) {
        for (int i=0; i<x; ++i) d[i] = c[i];
        for (int i=N-1; i>=x; --i) d[0] += c[i];
        int64_t sum = 0;
        for (int i=x-1; i>0; --i) {
            if (d[i]+sum>1) d[0] += d[i]+sum-1;
            else if (d[i]+sum<=0) sum += d[i]+sum-1;
            if (sum<-1e15) return 0;
        }
        return d[0]+sum>=1;
    }
    int main() {
        scanf("%d", &n);
        for (int i=0; i<n; ++i) scanf("%d", &c[i]);
        int64_t sum = 0;
        for (int i=0; i<n; ++i) sum += c[i];
        if (sum==1) {
            int ans = 1;
            for (int i=2; i<n; ++i) if (c[i]) ans = i;
            printf("%d
    ", ans);
            return 0;
        }
        int l = 2, r = N-1, ans = 1;
        while (l<=r) {
            int mid = (l+r)/2;
            if (chk(mid)) ans=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d
    ", ans);
    }
    View Code

    D

    可以发现一条边(u,v)的权值一定要不小于u到v所有路径的最小值,可以用并查集求

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 6e5+10;
    int n, m, fa[N], sz[N], val[N];
    struct _ {int u,v,w;} e[N];
    int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;}
    int main() {
        scanf("%d%d", &n, &m);
        for (int i=1; i<=n; ++i) sz[i] = 1;
        for (int i=1; i<=m; ++i) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
        sort(e+1,e+1+m,[](_ a,_ b){return a.w>b.w;});
        int64_t ans = 0;
        int tot = n;
        for (int i=1,j; i<=m; i=j) {
            j = i;
            while (j<=m&&e[j].w==e[i].w) {
                int u = Find(e[j].u), v = Find(e[j].v);
                if (u==v&&e[j].w!=val[u]) return puts("-1"),0;
                ++j;
            }
            for (int k=i; k<j; ++k) {
                int u = Find(e[k].u), v = Find(e[k].v);
                if (u!=v) {
                    fa[u] = fa[v] = ++tot;
                    val[tot] = e[i].w;
                    sz[tot] = sz[u]+sz[v];
                    ans += sz[u]*(int64_t)sz[v]*e[k].w;
                }
            }
        }
        int now = 0;
        for (int i=1; i<=2*n; ++i) if (Find(i)==i) {
            ans += now*(int64_t)sz[Find(i)];
            now += sz[Find(i)];
        }
        printf("%lld
    ", ans);
    }
    View Code

    G

    题意:给定长$N$的串$S$,求长$N$的$T$,使得$S$和$T$最长公共子序列长度为$N-K$

    gym102769H

    题意:一个合法序列要求每个数范围$[1,n]$,假设前缀最大值$p_i$,要求$p_i-p_{i-1}le 1$。求所有合法序列中每种元素出现次数平方和

    枚举$k$的第一次出现位置,那么前缀方案是一个是一个斯特林数,后缀贡献是一个合法序列中选两个$k$的方案

    dp即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 3e3+10;
    int pre[N][N], suf[N][N], ans[N];
    int main() {
        int T;
        scanf("%d", &T);
        for (int clk=1; clk<=T; ++clk) {
            int n, m;
            scanf("%d%d", &n, &m);
            pre[0][0] = 1;
            for (int i=1; i<=n; ++i) 
                for (int j=1; j<=i; ++j) 
                    pre[i][j] = (pre[i-1][j]*(int64_t)j+pre[i-1][j-1])%m;
            for (int i=1; i<=n; ++i) ans[i] = 0, suf[0][i] = 1;
            for (int i=1; i<=n; ++i) 
                for (int j=1; j<=n; ++j) 
                    suf[i][j] = (suf[i-1][j]*(j-1ll)+suf[i-1][min(n,j+1)])%m;
            //pre[i][j] = 前i个数,最大值为j的方案
            //suf[i][j] = 长i的序列,第一个数可以填>=j的方案
            for (int i=1; i<=n; ++i) ans[i] = 0;
            for (int i=1; i<=n; ++i) {
                for (int j=1; j<=n; ++j) {
                    //枚举数j的第一次出现位置i
                    //两个k都选位置i时,贡献suf[n-i][j+1]
                    //两个k都选除i以外的相同位置,贡献(n-i)suf[n-i-1][j+1]
                    //一个选i一个不选i,贡献2(n-i)suf[n-i-1][j+1]
                    //两个都不选i且不相同,贡献(n-i)(n-i-1)suf[n-i-2][j+1]
                    int w = suf[n-i][min(n,j+1)];
                    if (n-i>=1) w = (w+(n-i)*3ll*suf[n-i-1][min(n,j+1)])%m;
                    if (n-i>=2) w = (w+(n-i)*(n-i-1ll)*suf[n-i-2][min(n,j+1)])%m;
                    ans[j] = (ans[j]+pre[i-1][j-1]*(int64_t)w)%m;
                }
            }
            printf("Case #%d: 
    ", clk);
            for (int i=1; i<=n; ++i) printf("%d%c", ans[i], " 
    "[i==n]);
        }
    }
    View Code

    gym101991K

    题意:给定$n,k,l,r$,求有多少个序列,满足每个元素范围$[l,r]$,且和为$3$的倍数的区间恰好$k$个

    考虑前缀和序列$s$,一个合法区间$[l,r]$等价于$s_{l-1}=s_{r}$,假设$s$中有$x$个0,$y$个$1$,$z$个$2$,那么合法区间数就是$x(x-1)/2+y(y-1)/2+z(z-1)/2$

    所以如果$n>sqrt{6k}$的话直接无解,否则$O(n^3)dp$一下即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 255, P = 1e9+7;
    int n, K, l, r, cnt[3];
    int dp[N][N][N][3];
    void add(int &a, int64_t b) {a=(a+b)%P;}
    int main() {
        freopen("khoshaf.in","r",stdin);
        int T;
        scanf("%d", &T);
        while (T--) {
            scanf("%d%d%d%d", &n, &K, &l, &r);
            if (((n/3)*(n/3)*3-n)/2>K) {puts("0");continue;}
            auto calc = [](int l, int r, int x) {
                int ans = 0;
                while (l%3!=x%3) ++l;
                while (r>=0&&r%3!=x%3) --r;
                if (l>r) return 0;
                return (r-l)/3+1;
            };
            for (int i=0; i<3; ++i) cnt[i] = calc(l,r,i);
            for (int i=0; i<=n+1; ++i)
                for (int j=0; j<=n+1; ++j) 
                    for (int k=0; k<=n+1; ++k)
                        for (int z=0; z<3; ++z)
                            dp[i][j][k][z] = 0;
            dp[0][1][0][0] = 1;
            int ans = 0;
            for (int i=0; i<=n; ++i) {
                for (int j=0; j<=i+1; ++j) {
                    for (int k=0; j+k<=i+1; ++k) {
                        int t = i+1-j-k;
                        for (int z=0; z<3; ++z) {
                            int ret = dp[i][j][k][z];
                            if (!ret) continue;
                            if (i==n) {
                                if (j*(j-1)/2+k*(k-1)/2+t*(t-1)/2==K) add(ans,ret);
                                continue;
                            }
                            for (int num=0; num<3; ++num) {
                                int u = (z+num)%3;
                                add(dp[i+1][j+(u==0)][k+(u==1)][u],ret*(int64_t)cnt[num]);
                            }
                        }
                    }
                }
            }
            printf("%d
    ", ans);
        }
    }
    View Code

    2020 ccpc mianyang

    B

    题意:给定左前视图和$k$个位置的高度,求有多少种摆法

    左前视图相当于限制了斜着一排的最大值,可以枚举斜着的每一排dp

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5+10, P = 1e9+7;
    int n, m, k, a[N], R[N], dp[N][2], cnt[N], fac[N], ifac[N];
    int qpow(int a, int n) {
        int ans = 1;
        for (; n; n>>=1,a=a*(int64_t)a%P) if (n&1) ans=ans*(int64_t)a%P;
        return ans;
    }
    int C(int n, int m) {
        return fac[n]*(int64_t)ifac[m]%P*ifac[n-m]%P;
    }
    void add(int &a, int64_t b) {a=(a+b)%P;}
    int main() {
        fac[0] = 1;
        for (int i=1; i<N; ++i) fac[i] = fac[i-1]*(int64_t)i%P;
        ifac[N-1] = qpow(fac[N-1], P-2);
        for (int i=N-2; i>=0; --i) ifac[i] = ifac[i+1]*(i+1ll)%P;
        int T;
        scanf("%d", &T);
        for (int clk=1; clk<=T; ++clk) {
            scanf("%d%d%d", &n, &m, &k);
             for (int i=1; i<=n+m; ++i) { 
                scanf("%d", &a[i]), R[i] = 0;
                cnt[i] = min(i-1,n)-max(i-m,1)+1;
                dp[i][0] = dp[i][1] = 0;
            }
            for (int i=1; i<=k; ++i) {
                int x, y, h;
                scanf("%d%d%d", &x, &y, &h);
                R[x+y] = max(R[x+y], h);
                R[x+y-1] = max(R[x+y-1], h);
                --cnt[x+y];
            }
            int ok = 1;
            for (int i=1; i<=n+m; ++i) if (R[i]>a[i]) ok = 0;
            if (!ok) {
                printf("Case #%d: 0
    ", clk);
                continue;
            }
            dp[1][R[1]==a[1]] = 1;
            for (int i=2; i<=n+m; ++i) {
                if (a[i]==a[i-1]) {
                    if (cnt[i]) add(dp[i][1],(dp[i-1][0]+dp[i-1][1])*(int64_t)(qpow(a[i],cnt[i])-qpow(a[i]-1,cnt[i])));
                    add(dp[i][0],dp[i-1][1]*(int64_t)qpow(a[i]-1,cnt[i]));
                }
                else if (a[i]<a[i-1]) {
                    if (cnt[i]) add(dp[i][1],dp[i-1][1]*(int64_t)(qpow(a[i],cnt[i])-qpow(a[i]-1,cnt[i])));
                    add(dp[i][0],dp[i-1][1]*(int64_t)qpow(a[i]-1,cnt[i]));
                }
                else {
                    if (cnt[i]) add(dp[i][0],(dp[i-1][0]+dp[i-1][1])*(int64_t)(qpow(a[i-1],cnt[i])-qpow(a[i-1]-1,cnt[i])));
                    add(dp[i][0],dp[i-1][1]*(int64_t)qpow(a[i-1]-1,cnt[i]));
                }
                if (R[i]==a[i]) add(dp[i][1],dp[i][0]), dp[i][0] = 0;
            }
            if (dp[n+m][1]<0) dp[n+m][1] += P;
            printf("Case #%d: %d
    ", clk, dp[n+m][1]);
        }
    }
    View Code

    C

    题意:定义了字典树插入询问操作,给定一些询问的结果,求字典树最少多少个节点

    分类讨论题,值相同的询问求一下lca,然后每个点贪心往上缩

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    char s[N];
    int n, tot, ch[N][26], val[N], fa[N], dep[N], vis[N], c[N], sz[N];
    pair<int,int> a[N];
    int lca(int x, int y) {
        if (!x||!y) return x+y;
        while (dep[x]!=dep[y]) {
            if (dep[x]<dep[y]) y=fa[y];
            else x=fa[x];
        }
        while (x!=y) x=fa[x],y=fa[y];
        return x;
    }
    int ans;
    int chk(int x) {
        int w = vis[x];
        for (int i=0; i<26; ++i) if (ch[x][i]) w += chk(ch[x][i]);
        return w;
    }
    void dfs1(int x) {
        sz[x] = vis[x];
        for (int i=0; i<26; ++i) if (ch[x][i]) {
            dfs1(ch[x][i]);
            sz[x] += sz[ch[x][i]];
        }
    }
    void dfs(int x) {
        int ban = 0;
        for (int i=0; i<26; ++i) if (ch[x][i]) {
            if (sz[ch[x][i]]>1) ban = 1;
            if (vis[x]&&sz[ch[x][i]]) ban = 1;
        }
        if (!ban) {
            if (vis[x]) {
                ++ans;
                return;
            }
            int cnt = 0;
            for (int i=0; i<26; ++i) cnt += sz[ch[x][i]];
            ans += cnt;
            return;
        }
        ++ans;
        int f = !vis[x];
        for (int i=0; i<26; ++i) if (ch[x][i]) { 
            if (!sz[ch[x][i]]) continue;
            if (sz[ch[x][i]]==1) {
                if (f) f = 0;
                else ++ans;
                continue;
            }
            dfs(ch[x][i]);
        }
    }
    int main() {
        int T;
        scanf("%d", &T);
        for (int clk=1; clk<=T; ++clk) {
            scanf("%d", &n);
            for (int i=1; i<=tot; ++i) {
                fa[i] = dep[i] = vis[i] = c[i] = val[i] = sz[i] = 0;
                memset(ch[i],0,sizeof ch[i]);
            }
            tot = 1;
            int ok = 1;
            for (int i=1; i<=n; ++i) {
                int x;
                scanf("%s%d", s+1, &x);
                int m = strlen(s+1), now = 1;
                for (int j=1; j<=m; ++j) {
                    if (!ch[now][s[j]-'a']) { 
                        ++tot;
                        fa[tot] = now;
                        dep[tot] = dep[now]+1;
                        ch[now][s[j]-'a'] = tot;
                    }
                    now = ch[now][s[j]-'a'];
                }
                if (val[now]&&val[now]!=x) ok = 0;
                else val[now] = x;
                a[i] = {x,now};
            }
            if (!ok) {
                printf("Case #%d: -1
    ", clk);
                continue;
            }
            sort(a+1,a+1+n);
            for (int i=1; i<=n; ++i) {
                int j = i;
                while (j+1<=n&&a[j+1].first==a[j].first) ++j;
                int l = 0;
                for (int k=i; k<=j; ++k) l = lca(l,a[k].second);
                for (int k=i; k<=j; ++k) {
                    int t = a[k].second;
                    if (t==l) continue;
                    while (fa[t]!=l) t = fa[t];
                    c[t] = a[i].first;
                }
                if (vis[l]) ok = 0;
                vis[l] = 1;
                i = j;
            }
            for (int i=1; i<=n; ++i) {
                int t = a[i].second;
                while (t!=1) {
                    if (c[t]&&c[t]!=a[i].first) ok = 0;
                    t = fa[t];
                }
            }
            if (!ok) {
                printf("Case #%d: -1
    ", clk);
                continue;
            }
            ans = 0;
            dfs1(1);
            dfs(1);
            printf("Case #%d: %d
    ", clk, ans);
        }
    }
    View Code

    E

    分层图bfs

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10, INF = 0x3f3f3f3f;
    int n, m, k, deg[N], d[N][52];
    vector<int> g[N], f[N];
    queue<pair<int,int>> q;
    int main() {
        int T;
        scanf("%d", &T);
        for (int clk=1; clk<=T; ++clk) { 
            scanf("%d%d%d", &n, &m, &k);
            for (int i=1; i<=n; ++i) { 
                g[i].clear(), f[i].clear();
                deg[i] = 0;
                memset(d[i],0x3f,sizeof d[i]);
            }
            for (int i=1; i<=m; ++i) {
                int u, v;
                scanf("%d%d", &u, &v);
                g[v].push_back(u);
                f[u].push_back(v);
                ++deg[u];
            }
            auto gao = [&](int id, int l, int w) {
                for (int t=l; t>=0; --t) { 
                    if (d[id][t]==INF) d[id][t]=w,q.push({id,t});
                    else break;
                }
            };
            gao(n,k,0);
            auto expand = [&](int id, int l) {
                int w = d[id][l]+1;
                if (l) {
                    for (int t:g[id]) gao(t,l-1,w);
                    for (int t:f[id]) gao(t,l-1,w);
                }
                else {
                    if (!deg[id]&&d[id][k]>w) gao(id,k,w);
                    for (int t:g[id]) if (!--deg[t]&&d[t][k]>w) gao(t,k,w);
                }
            };
            while (q.size()) {
                auto u = q.front(); q.pop();
                expand(u.first,u.second);
            }
            printf("Case #%d:
    ", clk);
            for (int i=1; i<=n; ++i) printf("%d
    ", d[i][0]==INF?-1:d[i][0]);
        }
    }
    View Code

    H

    答案就等于$sumlimits_{substack{|x|+|y|= d_{01} \ |xx|+|yy|=d_{02}}} [|x-xx|+|y-yy|=d_{12}]$

    然后暴力分类讨论去掉绝对值即可,可以利用对称性简化一些讨论

    #include <bits/stdc++.h>
    using namespace std;
    int a,b,c;
    int64_t s0(int l, int r) {
        if (l>r) return 0;
        return r-l+1;
    }
    int64_t s1(int l, int r) {
        if (l>r) return 0;
        return (l+r)*(r-l+1ll)/2;
    }
    int gao(int64_t x1, int64_t x2) {
        int ans = 0;
        int64_t y1 = a-abs(x1), y2 = b-abs(x2), w = c-abs(x2-x1);
        if (y1==0&&y2==0) { 
            if (w==0) ++ans;
        }
        else if (y1==0) { 
            if (y2==w) ans += 2;
        }
        else if (y2==0) { 
            if (y1==w) ans += 2;
        }
        else { 
            if (abs(y2-y1)==w) ans += 2;
            if (abs(y2+y1)==w) ans += 2;
        }
        return ans;
    }
    int64_t calc0(int n, int m) {
        int64_t ans = 0;
        if ((b+a-c)%2==0) {
            int t = (b+a-c)/2;
            if (1<=t&&t<=m) ans += s0(max(1,t),n);
            if (1<=t&&t<=n) ans += s0(t+1,m);
        }
        if (-b+a==c) {
            ans += s0(m,min(n,a-b))*m;
            ans += s1(1,min({n,a-b,m-1}));
            ans += s0(max({1,a-b+1,m}),n)*(m-b+a);
            ans -= s1(max({1,a-b+1,m}),n);
            ans += s0(max(1,a-b+1),min(n,m-1))*(a-b);
        }
        if (b-a==c) {
            ans += s0(max(1,m+a-b),n)*m;
            ans -= s1(max(1,m+a-b),n);
            ans += s0(1,min(m+a-b-1,n))*(b-a);
        }
        if ((b-a-c)%2==0) {
            int t = (b-a-c)/2;
            ans += s0(max({1-t,m,m-b+a}),min(n,m-t));
            if (b-a>=0&&t<=0) ans += s0(max(1,1-t),min(m-1,n));
            if (t<=b-a&&b-a<0) ans += s0(max(1,1-t),min(n,m-b+a-1));
        }
        if ((c+b-a)%2==0) {
            int t = (c+b-a)/2;
            if (b-a>=0&&t>=1+b-a) ans += s0(1,min(n,m-t));
            if (b-a<0&&t>=1) ans += s0(1,min(n,m-t));
        }
        return ans;
    }
    int64_t calc3(int n, int m) {
        int64_t ans = 0;
        if (a+b==c) ans += n*(b-1ll);
        if ((c-b+a)%2==0) {
            int t = (c-b+a)/2;
            if (t>=a-1&&t>=1&&t<=n) ans += s0(-b+1,-1);
            if (t<a-1&&t>=1&&t<=n) ans += s0(a-t-b,-1);
        }
        if ((a-c-b)%2==0) {
            int t = (a-b-c)/2;
            if (-b+1<=t&&t<0) ans += s0(1,min(n,a-b));
            if (-b+1<=t) ans += s0(max(1,a-b+1),min(n,a-b-1-t));
        }
        return ans;
    }
    int64_t calc1() {
        if (a<=1||b<=1) return 0;
        return 4*calc0(a-1,b-1);
    }
    int64_t calc2() {
        if (a<=1||b<=1) return 0;
        return 4*calc3(a-1,-b+1);
    }
    int64_t calc5(int x) {
        int64_t ans = gao(x,-b)+gao(x,b)+gao(x,0);
        if (x==0) {
            if (a==0) {
                if (b==c) ans += 4ll*(b-1);
            }
            else {
                if (a+b==c) ans += 4ll*(b-1);
                if ((a-b-c)%2==0) {
                    int t = (a-b-c)/2;
                    if (t>=-b+1&&t<0&&t<=a-b) ans += 2;
                }
                if (b-a==c) { 
                    ans += 2*s0(max(-b+1,a-b+1),-1);
                    ans += 2*s0(1,min(b-1,b-a));
                }
                if ((c-a+b)%2==0) {
                    int t = (c-a+b)/2;
                    if (b-t<a&&t>=1&&t<b) ans += 2;
                }
            }
            return ans;
        }
        if (x==a) {
            if (b==c-a) ans += 2*(b-1);
            if ((b-c+a)%2==0) {
                int t = (b-c+a)/2;
                if (t>=1&&t<b&&t<=a) ans += 2;
            }
            if (b==c+a) ans += 2*s0(max(1,a+1),b-1);
            return ans;
        }
        if ((c-a-b)%2==0) {
            int t = (c-a-b)/2;
            if (t>=-a&&t>=-b+1&&t<0) ans += 2;
        }
        if (b==c+a) ans += 2*s0(-b+1,min(-a-1,-1));
        if (b==c-a) ans += 2*(b-1);
        return ans;
    }
    int64_t calc4() { 
        if (a==0&&b==0) return gao(0,0);
        int64_t ans = calc5(0);
        if (a==0) return ans;
        ans += calc5(-a);
        ans += calc5(a);
        swap(a,b);
        ans += calc5(-a);
        ans += calc5(a);
        ans += calc5(0);
        swap(a,b);
        for (int x:{-a,0,a}) for (int y:{-b,0,b}) ans -= gao(x,y);
        return ans;
    }
    int main() {
        int T;
        scanf("%d", &T);
        for (int clk=1; clk<=T; ++clk) {
            scanf("%d%d%d", &a, &b, &c);
            if (a>b) swap(a,b);
            int64_t ans=calc1()+calc2()+calc4();
            printf("Case #%d: %lld
    ", clk, ans);
        }
    }
    View Code

    L

    题意:给定$n$种数的大小和个数,每种数都是$2$的幂,求能得到多少种和

    先排序,构成和序列连续的连续段可以合并,然后答案就是每段的乘积

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10, P = 1e9+7;
    int n;
    pair<int,int> a[N];
    int qpow(int a, int n) {
        int ans = 1;
        for (; n; n>>=1,a=a*(int64_t)a%P) if (n&1) ans=ans*(int64_t)a%P;
        return ans;
    }
    int main() {
        int T;
        scanf("%d", &T);
        for (int clk=1; clk<=T; ++clk) {
            scanf("%d", &n);
            for (int i=0; i<n; ++i) scanf("%d%d", &a[i].first, &a[i].second);
            sort(a, a+n);
            int ans = 1;
            for (int i=0; i<n; ++i) {
                int j = i;
                int64_t sum = a[i].second+1, ret = a[i].second*(int64_t)qpow(2,a[i].first)%P;
                int pre = a[i].first;
                while (j+1<n&&a[j+1].first<=pre+59) {
                    if (sum>=(1ll<<(a[j+1].first-pre))) {
                        ++j;
                        sum >>= a[j].first-pre;
                        sum += a[j].second;
                        pre = a[j].first;
                        ret = (ret+a[j].second*(int64_t)qpow(2,a[j].first))%P;
                    }
                    else break;
                }
                ans = ans*(ret*qpow(qpow(2,a[i].first),P-2)%P+1)%P;
                i = j;
            }
            printf("Case #%d: %d
    ", clk, ans);
        }
    }
    View Code

    2014 icpc mudanjiang

    B

    题意:给定树,求两个点,使得每个点到这两点距离最小值的最大值最小

    由于距每个点距离最远的点一定是两直径端点之一,考虑二分答案$x$,把两个点放在距直径$x$位置检验即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5+10, INF = 1e9;
    int n, fa[N], vis[N];
    vector<int> g[N];
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            scanf("%d", &n);
            for (int i=1; i<=n; ++i) g[i].clear();
            for (int i=1; i<n; ++i) {
                int u, v;
                scanf("%d%d", &u, &v);
                g[u].push_back(v);
                g[v].push_back(u);
            }
            auto bfs = [&](int x) {
                for (int i=1; i<=n; ++i) vis[i] = 0;
                queue<int> q;
                vis[x] = 1, q.push(x);
                while (q.size()) {
                    x = q.front(); q.pop();
                    for (int y:g[x]) if (!vis[y]) { 
                        vis[y] = 1, fa[y] = x;
                        q.push(y);
                    }
                }
                return x;
            };
            int u = bfs(1), v = bfs(u);
            vector<int> a;
            while (v!=u) a.push_back(v), v = fa[v];
            a.push_back(u);
            int l = 0, r = a.size()-1, ans, ans_x, ans_y;
            auto chk = [&](int x) {
                ans_x = a[x], ans_y = a[a.size()-1-x];
                if (ans_y==ans_x) ans_y = ans_x==1?2:1;
                queue<int> q;
                for (int i=1; i<=n; ++i) vis[i] = INF;
                q.push(ans_x), q.push(ans_y);
                vis[ans_x] = vis[ans_y] = 0;
                int d = x;
                while (q.size()) {
                    x = q.front(); q.pop();
                    for (int y:g[x]) if (vis[y]==INF) vis[y] = vis[x]+1, q.push(y);
                }
                return vis[x]<=d;
            };
            while (l<=r) {
                int mid = (l+r)/2;
                if (chk(mid)) ans=mid,r=mid-1;
                else l=mid+1;
            }
            chk(ans);
            printf("%d %d %d
    ", ans, ans_x, ans_y);
        }
    }
    View Code

    C

    E

    题意:$n imes n$格子,求构造路径使得至少转弯$n(n-1)-1$次

    暴力跑一下,如果发现一定可以构造出起点左上终点右上的情况,当$n+2$时可以在外围蛇形绕一圈,这样就能构造出所有奇数情况

    当$n$为偶数时,不断上下蛇形交替走可以构造出$n(n-1)$个转弯

    #include <bits/stdc++.h>
    using namespace std;
    int n, a[555][555], b[555][555];
    void dfs(int x1, int y1, int x2, int y2, int d, int s) {
        if (d==1) {
            a[x1][y1] = ++s;
            return;
        }
        for (int i=x1; i+1<x1+d; i+=2) {
            a[i][y1] = ++s;
            a[i][y1+1] = ++s;
            a[i+1][y1+1] = ++s;
            a[i+1][y1] = ++s;
        }
        a[x1+d-1][y1] = ++s;
        a[x1+d-1][y1+1] = ++s;
        for (int i=y1+2; i+1<y1+d; i+=2) {
            a[x1+d-1][i] = ++s;
            a[x1+d-2][i] = ++s;
            a[x1+d-2][i+1] = ++s;
            a[x1+d-1][i+1] = ++s;
        }
        a[x1+d-1][y1+d-1] = ++s;
        a[x1+d-2][y1+d-1] = ++s;
        dfs(x1,y1+2,x1+d-3,y1+d-1,d-2,s);
        for (int i=x1; i<=x1+d-3; ++i)
            for (int j=y1+2; j<=y1+d-1; ++j)
                b[i][j] = a[i][j];
        for (int i=x1; i<=x1+d-3; ++i) 
            for (int j=y1+2; j<=y1+d-1; ++j) 
                a[x1+j-y1-2][y1+d-i+x1-1] = 2*s+(d-2)*(d-2)-b[i][j]+1;
    }
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int n;
            scanf("%d", &n);
            if (n&1) dfs(1,1,n,n,n,0);
            else {
                int now = 0;
                for (int j=1; j+1<=n; j+=4) {
                    int i = 1;
                    for (; i+3<=n; i+=2) { 
                        a[i][j] = ++now;
                        a[i][j+1] = ++now;
                        a[i+1][j+1] = ++now;
                        a[i+1][j] = ++now;
                    }
                    a[i][j] = ++now;
                    a[i+1][j] = ++now;
                    a[i+1][j+1] = ++now;
                    a[i][j+1] = ++now;
                    a[i][j+2] = ++now;
                    a[i+1][j+2] = ++now;
                    a[i+1][j+3] = ++now;
                    a[i][j+3] = ++now;
                    for (--i; i>=2; i-=2) {
                        a[i][j+3] = ++now;
                        a[i][j+2] = ++now;
                        a[i-1][j+2] = ++now;
                        a[i-1][j+3] = ++now;
                    }
                }
            }
            for (int i=1; i<=n; ++i) {
                for (int j=1; j<=n; ++j) printf("%d ", a[i][j]);
                puts("");
            }
        }
    }
    View Code

    F

    题意:给定树,给定每个点取值范围,求多少种方案使得相邻点权值互质

    考虑树形$dp$,${dp}_{x,i}$表示点$x$取值$i$时,子树$x$的分配方案

    容易得到转移${dp}_{x,i}=prodsum {dp}_{y,j}[gcd(i,j)=1]$,其中$y$是$x$的儿子

    反演一下就有${dp}_{x,i}=prod sumlimits_{d|i}mu(d)sumlimits_{d|j} {dp}_{y,j}$

    这样就可以$O(nMlog M)$求出每个点子树的$dp$值,然后再换根$dp$即可

    G

    题意:给定圆和两个点$P_0,P_1$,求构造圆内一个点$P_2$满足坐标为整数且三角形$P_0P_1P_2$面积$2$倍是$S$

    H

    题意:给定一个串,求每个$key$对应的$value$是什么

    把每个$key$的路径hash一下即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10;
    const int P = 876756319, B = 9999991;
    int tot,nxt[N];
    char s[N],t[N];
    map<string,int> mp;
    map<int,pair<int,int>> ans;
    int ID(string s) {
        if (mp.count(s)) return mp[s];
        int t = mp.size()+1;
        return mp[s] = t;
    }
    void build(int l, int r, int v) { 
        if (r-l+1<=5) return;
        int pos = l+1;
        while (s[pos]!='"') ++pos;
        int posl = pos+2, posr = posl+1;
        if (s[posl]=='{') posr = nxt[posl];
        else {
            while (s[posr]!='"') ++posr;
        }
        if (s[posr+1]==',') build(posr+2,r,v);
        v = ((int64_t)v*B+ID(string(s+l,s+pos+1)))%P;
        ans[v] = {posl,posr};
        if (s[posl]=='{') build(posl+1,posr-1,v);
    }
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int q;
            scanf("%s%d", s+1, &q);
            int n = strlen(s+1);
            mp.clear(), ans.clear();
            vector<int> v;
            for (int i=1; i<=n; ++i) {
                if (s[i]=='{') v.push_back(i);
                else if (s[i]=='}') nxt[v.back()] = i, v.pop_back();
            }
            build(2,n-1,{});
            while (q--) {
                scanf("%s", t+1);
                int m = strlen(t+1), ok = 1, now = 0;
                for (int i=1; i<=m; ++i) if (t[i]=='"') {
                    int j = i+1;
                    while (t[j]!='"') ++j;
                    auto u = string(t+i,t+j+1);
                    if (!mp.count(u)) {
                        ok = 0;
                        break;
                    }
                    now = (now*(int64_t)B+ID(u))%P;
                    i = j;
                }
                if (!ok||!ans.count(now)) puts("Error!");
                else {
                    int l, r;
                    tie(l,r) = ans[now];
                    for (int i=l; i<=r; ++i) putchar(s[i]);
                    puts("");
                }
            }
        }
    }
    View Code

    J

    题意:给定串,对于每种区间长度,求多少个区间满足前一半和后一半循环同构

    暴力枚举区间,考虑如何检验两个串$A,B$循环同构,可以发现,要么$A=B$,要么是$B$的一个后缀等于$A$的前缀,然后其余部分相等,暴力kmp+hash可过,复杂度不知道咋证

    正解是用manacher,考虑每个位置为中心的所有区间,依次把头尾插到一个新串中,那么原串循环同构就等价于新串中一个后缀是双回文串,可以用manacher检验 

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 5e3+10;
    const int P1 = 876756319, B1 = 999991;
    const int P2 = 799898821, B2 = 233333;
    int a[N], f[N], fac[N], fac1[N], fac2[N], f1[N], f2[N];
    vector<int> ans[N];
    pair<int,int> Hash(int l, int r) {
        int x = (f1[r]-f1[l-1]*(int64_t)fac1[r-l+1])%P1;
        int y = (f2[r]-f2[l-1]*(int64_t)fac2[r-l+1])%P2;
        if (x<0) x += P1; if (y<0) y += P2;
        return pair<int,int>(x,y);
    }
    void getfail(int *s, int *f, int n) {
        f[0] = f[1] = 0;
        for (int i=1,j=0; i<n; ++i) {
            while (j&&s[i]!=s[j]) j=f[j];
            if (s[i]==s[j]) ++j;
            f[i+1] = j;
        }
    }
    int main() {
        fac1[0] = fac2[0] = 1;
        for (int i=1; i<N; ++i) {
            fac1[i] = fac1[i-1]*(int64_t)B1%P1;
            fac2[i] = fac2[i-1]*(int64_t)B2%P2;
        }
        int T;
        scanf("%d", &T);
        while (T--) {
            int n, m;
            scanf("%d%d", &n, &m);
            for (int i=1; i<=n; ++i) ans[i].clear();
            for (int i=1; i<=n; ++i) { 
                scanf("%d", &a[i]);
                f1[i] = (f1[i-1]*(int64_t)B1+a[i])%P1;
                f2[i] = (f2[i-1]*(int64_t)B2+a[i])%P2;
            }
            for (int i=1; i<=n; ++i) {
                getfail(a+i,f,n-i+1);
                for (int j=i+1; j<=n; j+=2) {
                    int len = (j-i+1)/2, mid = j-len;
                    auto chk = [&]() {
                        if (Hash(i,mid)==Hash(mid+1,j)) return true;
                        int t = f[j-i+1];
                        while (t>=len) t = f[t];
                        while (t) {
                            if (Hash(2*mid+1-j+t,mid)==Hash(mid+1,j-t)) return true;
                            t = f[t];
                        }
                        return false;
                    };
                    if (chk()) ans[len*2].push_back(i);
                }
            }
            int cnt = 0, tot = 0;
            for (int i=1; i<=n; ++i) if (ans[i].size()) ++cnt, tot += ans[i].size();
            printf("%d %d
    ", tot, cnt);
            for (int i=1; i<=n; ++i) if (ans[i].size()) {
                printf("%d %d", i, (int)ans[i].size());
                for (int t:ans[i]) printf(" %d", t);
                puts("");
            }
        }
    }
    View Code

    K

    题意:给定串,每次操作交换任意两字符或者填一个任意字符,求最少多少次操作能得到合法逆波兰表达式

    $*$看做$-1$,数字看做$1$,那么合法逆波兰等价于每个前缀和为正,$n$很小直接暴力枚举多少个$*$移到前面即可

    CF587D

    题意:给定图,要求删除一些边,使得删的边没有公共顶点,并且剩余边中不存在颜色相同且有公共顶点的边,并且删的边边权最大值最小

    考虑二分答案,第一个限制就是要求一个点的邻接边至多删一条,第二个限制就是每个点同色邻接边至多留一条不删,这是一个2sat关系,前缀优化建图即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 5e4+10;
    int n, m;
    struct _ {
        int to,c,t,id;
        bool operator < (const _ &rhs) const {
            return c<rhs.c;
        }
    };
    vector<_> g[N];
    struct twosat {
        int tot,n,dfn[N*10],low[N*10],sccno[N*10],clk,scnt;
        int s[N*10],s_top;
        vector<int> g[N*10];
        void dfs(int x) {
            dfn[s[++s_top]=x]=low[x]=++clk;
            for (int y:g[x]) {
                if (!dfn[y]) dfs(y),low[x]=min(low[x],low[y]);
                else if (!sccno[y]) low[x]=min(low[x],dfn[y]);
            }
            if (low[x]==dfn[x]) for (int u=++scnt; sccno[u=s[s_top--]]=scnt,u!=x;) ;
        }
        void init(int _n) {
            for (int i=1; i<=tot; ++i) {
                g[i].clear();
                dfn[i] = sccno[i] = 0;
            }
            tot = 2*_n, n = _n;
            n = _n, clk = scnt = 0;
        }
        bool solve() {
            for (int i=1; i<=tot; ++i) if (!dfn[i]) dfs(i);
            for (int i=1; i<=n; ++i) if (sccno[i]==sccno[i+n]) return false;
            return true;
        }
    } sat;
    bool chk(int lim) {
        sat.init(m);
        for (int i=1; i<=n; ++i) {
            for (int j=0; j<g[i].size(); ++j) {
                int id = ++sat.tot;
                sat.g[id].push_back(g[i][j].id+m);
                if (j) {
                    sat.g[id].push_back(id-1);
                    if (g[i][j].t<=lim) sat.g[g[i][j].id].push_back(id-1);
                }
            }
            for (int j=(int)g[i].size()-1; j>=0; --j) {
                int id = ++sat.tot;
                sat.g[id].push_back(g[i][j].id+m);
                if (j+1!=g[i].size()) {
                    sat.g[id].push_back(id-1);
                    if (g[i][j].t<=lim) sat.g[g[i][j].id].push_back(id-1);
                }
            }
            for (int j=0; j<g[i].size(); ++j) {
                if (g[i][j].t>lim) sat.g[g[i][j].id].push_back(g[i][j].id+m);
            }
            for (int j=0,k; j<g[i].size(); j=k) {
                k = j;
                while (k<g[i].size()&&g[i][k].c==g[i][j].c) ++k;
                for (int u=j; u<k; ++u) {
                    int id = ++sat.tot;
                    sat.g[id].push_back(g[i][u].id);
                    if (u!=j) {
                        sat.g[id].push_back(id-1);
                        sat.g[g[i][u].id+m].push_back(id-1);
                    }
                }
                for (int u=k-1; u>=j; --u) {
                    int id = ++sat.tot;
                    sat.g[id].push_back(g[i][u].id);
                    if (u+1!=k) {
                        sat.g[id].push_back(id-1);
                        sat.g[g[i][u].id+m].push_back(id-1);
                    }
                }
            }
        }
        return sat.solve();
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i=1; i<=m; ++i) {
            int u, v, c, t;
            scanf("%d%d%d%d", &u, &v, &c, &t);
            g[u].push_back({v,c,t,i});
            g[v].push_back({u,c,t,i});
        }
        for (int i=1; i<=n; ++i) sort(g[i].begin(),g[i].end());
        int l=0,r=1e9+10,ans=-1;
        while (l<=r) {
            int mid = (l+r)/2;
            if (chk(mid)) ans=mid,r=mid-1;
            else l=mid+1;
        }
        if (ans==-1) puts("No");
        else {
            puts("Yes");
            chk(ans);
            int cnt = 0;
            for (int i=1; i<=m; ++i) if (sat.sccno[i]<sat.sccno[i+m]) ++cnt;
            printf("%d %d
    ", ans, cnt);
            for (int i=1; i<=m; ++i) if (sat.sccno[i]<sat.sccno[i+m]) printf("%d ", i);
            puts("");
        }
    }
    View Code

    CF1007D

    题意:给定树上$m$个路径对,要求从每个对中选一个路径,使得$m$条路径不相交

    CF935F

    题意:定义$f(A)=sumlimits_{1le i<n} |a_i-a_{i+1}|$,两种操作$(1,l,r,x)$表示$[l,r]$内选一个位置+x,输出最大f(A),$(2,l,r,x)$表示区间加,操作1不改变序列

    如果区间长度$>1$,可以发现+x后答案一定增大,讨论一下每个位置的贡献

    若$a_{i}ge a_{i-1}wedge a_{i}ge a_{i+1}$,那么贡献$2x$

    若$a_{i}ge a_{i-1}wedge a_{i}le a_{i+1}$,那么贡献$2(x-a_{i+1}+a_{i})$

    若$a_{i}le a_{i-1}wedge a_{i}ge a_{i+1}$,那么贡献$2(x-a_{i-1}+a_{i})$

    若$a_{i}le a_{i-1}wedge a_{i}le a_{i+1}$,那么贡献$2(x-a_{i-1}-a_{i+1}+2a_{i})$

    可以发现答案均为$2x-max(0,a_{i+1}-a_{i})-max(0,a_{i-1}-a_{i})$

    用线段树维护即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int n, a[N];
    struct bit {
        int64_t c[N];
        void add(int l, int r, int x) {
            for (; l<=n; l+=l&-l) c[l] += x;
            for (++r; r<=n; r+=r&-r) c[r] -= x;
        }
        int64_t query(int x) {
            int64_t ans = a[x];
            for (; x; x^=x&-x) ans += c[x];
            return ans;
        }
    } tr;
    struct seg {
        int64_t ma[N<<2];
        void pu(int o) {
            ma[o] = max(ma[o*2], ma[o*2+1]);
        }
        void build(int o, int l, int r) {
            if (l==r) ma[o] = -max(0,a[l-1]-a[l])-max(0,a[l+1]-a[l]);
            else {
                int mid = (l+r)/2;
                build(o<<1,l,mid),build(o<<1|1,mid+1,r);
                pu(o);
            }
        }
        int64_t query(int o, int l, int r, int ql, int qr) {
            if (ql<=l&&r<=qr) return ma[o];
            int mid = (l+r)/2;
            if (mid>=qr) return query(o<<1,l,mid,ql,qr);
            if (mid<ql) return query(o<<1|1,mid+1,r,ql,qr);
            return max(query(o<<1,l,mid,ql,qr),query(o<<1|1,mid+1,r,ql,qr));
        }
        void update(int o, int l, int r, int x, int64_t v) {
            if (l==r) {
                ma[o] = v;
                return;
            }
            int mid = (l+r)/2;
            if (mid>=x) update(o<<1,l,mid,x,v);
            else update(o<<1|1,mid+1,r,x,v);
            pu(o);
        }
    } T;
    int main() {
        scanf("%d", &n);
        int64_t ans = 0;
        for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
        for (int i=1; i<n; ++i) ans += abs(a[i]-a[i+1]);
        auto gao = [&](int x) {
            if (x==0||x==n) return 0ll;
            return abs(tr.query(x)-tr.query(x+1));
        };
        auto calc = [&](int p, int x) {
            int64_t w = -gao(p-1)-gao(p);
            a[p] += x;
            w += gao(p-1)+gao(p);
            a[p] -= x;
            return w;
        };
        if (n>2) T.build(1,2,n-1);
        int q;
        scanf("%d", &q);
        while (q--) {
            int op,l,r,x;
            scanf("%d%d%d%d", &op, &l, &r, &x);
            if (op==1) {
                int64_t ma = max(calc(l,x),calc(r,x));
                if (r-l<=1) {
                    printf("%lld
    ", ans+ma);
                    continue;
                }
                if (n>2) ma = max(ma, 2ll*x+2ll*T.query(1,2,n-1,l+1,r-1));
                printf("%lld
    ", ans+ma);
            }
            else {
                ans -= gao(l-1)+gao(r);
                tr.add(l,r,x);
                ans += gao(l-1)+gao(r);
                auto upd = [&](int p) {
                    if (p<2||p>=n) return;
                    T.update(1,2,n-1,p,-max(0ll,tr.query(p-1)-tr.query(p))-max(0ll,tr.query(p+1)-tr.query(p)));
                };
                if (l!=1) upd(l-1),upd(l);
                if (r!=n) upd(r),upd(r+1);
            }
        }
    }
    View Code

    CF1109E

    题意:给定序列,区间乘,单点除,区间求和取模

    核心观察是$a space modspace m = frac{a}{gcd(a,m)}space mod space frac{m}{gcd(a,m)} cdot gcd(a,m)$

    把$m$分解一下,线段树维护三个值:每个数对于每个$m$的素因子的次幂,每个数除去$m$的素因子后模$m$的值,模$m$的区间和

    这样复杂度就是$O(10 nlog n)$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int n,m,q,phi,a[N];
    vector<pair<int,int>> v;
    int qpow(int a, int n, int m) {
        int ans = 1;
        for (; n; n>>=1,a=a*(int64_t)a%m) if (n&1) ans=ans*(int64_t)a%m;
        return ans;
    }
    int qpow(int a, int n) {
        int ans = 1;
        for (int k=0; k<n; ++k) ans *= a;
        return ans;
    }
    struct bit {
        int b[N],c[N];
        void add(int l, int r, int v) {
            for (int x=l; x<=n; x+=x&-x) c[x] += v;
            for (int x=r+1; x<=n; x+=x&-x) c[x] -= v;
        }
        int query(int x) {
            int ans = b[x];
            for (; x; x^=x&-x) ans += c[x];
            return ans;
        }
    } tr[10];
    struct seg {
        int sum[N<<2],tag[N<<2];
        void build(int o, int l, int r) {
            if (l==r) { 
                sum[o] = a[l]%m;
                return;
            }
            tag[o] = 1;
            int mid = (l+r)/2;
            build(o<<1,l,mid), build(o<<1|1,mid+1,r);
            pu(o);
        }
        void pu(int o) {
            sum[o] = (sum[o<<1]+sum[o<<1|1])%m;
        }
        void pd(int o) {
            if (tag[o]!=1) {
                tag[o<<1] = tag[o<<1]*(int64_t)tag[o]%m;
                tag[o<<1|1] = tag[o<<1|1]*(int64_t)tag[o]%m;
                sum[o<<1] = sum[o<<1]*(int64_t)tag[o]%m;
                sum[o<<1|1] = sum[o<<1|1]*(int64_t)tag[o]%m;
                tag[o] = 1;
            }
        }
        void mul(int o, int l, int r, int ql, int qr, int x) {
            if (ql<=l&&r<=qr) {
                sum[o] = sum[o]*(int64_t)x%m;
                tag[o] = tag[o]*(int64_t)x%m;
                return;
            }
            pd(o);
            int mid = (l+r)/2;
            if (mid>=ql) mul(o<<1,l,mid,ql,qr,x);
            if (mid<qr) mul(o<<1|1,mid+1,r,ql,qr,x);
            pu(o);
        }
        void upd(int o, int l, int r, int x, int v) {
            if (l==r) {
                sum[o] = v;
                return;
            }
            pd(o);
            int mid = (l+r)/2;
            if (mid>=x) upd(o<<1,l,mid,x,v);
            else upd(o<<1|1,mid+1,r,x,v);
            pu(o);
        }
        int query(int o, int l, int r, int ql, int qr) {
            if (ql<=l&&r<=qr) return sum[o];
            pd(o);
            int mid = (l+r)/2, ans = 0;
            if (mid>=ql) ans = query(o<<1,l,mid,ql,qr);
            if (mid<qr) ans = (ans+query(o<<1|1,mid+1,r,ql,qr))%m;
            return ans;
        }
    } T,Y;
    int main() {
        scanf("%d%d", &n, &m);
        int mm = m;
        phi = 1;
        for (int i=2; i*i<=mm; ++i) if (mm%i==0) {
            int cnt = 0;
            while (mm%i==0) mm /= i, ++cnt;
            v.push_back({i,cnt});
            phi *= (i-1)*qpow(i,cnt-1);
        }
        if (mm>1) v.push_back({mm,1}), phi *= mm-1;
        for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
        T.build(1,1,n);
        for (int i=1; i<=n; ++i) {
            for (int k=0; k<v.size(); ++k) {
                while (a[i]%v[k].first==0) a[i] /= v[k].first, ++tr[k].b[i];
            }
        }
        Y.build(1,1,n);
        scanf("%d", &q);
        while (q--) {
            int op, l, r, x, p;
            scanf("%d", &op);
            if (op==1) {
                scanf("%d%d%d", &l, &r, &x);
                T.mul(1,1,n,l,r,x);
                for (int k=0; k<v.size(); ++k) {
                    int cnt = 0;
                    while (x%v[k].first==0) x /= v[k].first, ++cnt;
                    if (cnt) tr[k].add(l,r,cnt);
                }
                Y.mul(1,1,n,l,r,x);
            }
            else if (op==2) {
                scanf("%d%d", &p, &x);
                int g = 1, tmp[10];
                for (int k=0; k<v.size(); ++k) {
                    int cnt = 0;
                    while (x%v[k].first==0) x /= v[k].first, ++cnt;
                    tr[k].b[p] -= cnt;
                    tmp[k] = tr[k].query(p);
                    g = g*qpow(v[k].first,min(tmp[k],v[k].second));
                }
                int val = 1;
                for (int k=0; k<v.size(); ++k) {
                    val = val*(int64_t)qpow(v[k].first,tmp[k]-min(tmp[k],v[k].second),m/g)%(m/g);
                }
                int t = Y.query(1,1,n,p,p)*(int64_t)qpow(x,phi-1,m)%m;
                Y.upd(1,1,n,p,t);
                val = val*(int64_t)g%m*t%m;
                T.upd(1,1,n,p,val);
            }
            else {
                scanf("%d%d", &l, &r);
                printf("%d
    ", T.query(1,1,n,l,r));
            }
        }
    }
    View Code

    CF1175F

    题意:给定序列,求多少个区间$[l,r]$为1到r-l+1的排列

    合法区间要恰好包含1个1,讨论最大值在1的左侧还是右侧,hash统计

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 3e5+10;
    mt19937_64 rd(time(0));
    int n, ans, a[N];
    uint64_t ID[N],w[N],s[N];
    void solve() {
        for (int i=1,mx=0; i<=n; ++i) {
            s[i] = s[i-1]^ID[a[i]];
            mx = max(mx, a[i]);
            if (a[i]==1) mx = 1;
            else if (i>=mx&&(s[i]^s[i-mx])==w[mx]) ++ans;
        }
    }
    int main() {
        scanf("%d", &n);
        for (int i=1; i<=n; ++i) {
            scanf("%d", &a[i]);
            ID[i] = rd();
            w[i] = w[i-1]^ID[i];
            if (a[i]==1) ++ans;
        }
        solve();
        reverse(a+1,a+1+n);
        solve();
        printf("%d
    ", ans);
    }
    View Code

    icpc 2017 xi'an

    A

    题意:给定序列$A$和常数$k$,每次询问给出区间$[l,r]$,求$[l,r]$中选出一些数异或后与$k$按位或的最大值

    $k$为$1$的为相当于一定为$1$,那么构建线性基时直接除去,对也就是说A[i]&~k建线性基即可

    就转化为经典问题,给定序列,求区间异或最大值

    直接线段树暴力维护线性基,复杂度是3个log,数据范围很小,可以过

    一个稍微聪明点的办法是,贪心从高位到低位维护线性基每个数最后出现位置,询问只考虑处理到位置$r$时最后出现位置$ge l$的基

    时空复杂度都是$O(nlog A)$,如果把询问离线,空间复杂度也可以优化到$O(n)$

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e4+10;
    int val[N][27],pos[N][27];
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int n, q, k;
            scanf("%d%d%d", &n, &q, &k);
            for (int i=1; i<=n; ++i) { 
                int t;
                scanf("%d", &t);
                t &= ~k;
                memcpy(val[i],val[i-1],sizeof val[i]);
                memcpy(pos[i],pos[i-1],sizeof pos[i]);
                int p = i;
                for (int j=26; j>=0; --j) if (t>>j&1) {
                    if (pos[i][j]<p) { 
                        swap(val[i][j],t);
                        swap(pos[i][j],p);
                    }
                    t ^= val[i][j];
                    if (!t) break;
                }
            }
            while (q--) {
                int l, r;
                scanf("%d%d", &l, &r);
                int ans = k;
                for (int j=26; j>=0; --j) if (pos[r][j]>=l) ans=max(ans,ans^val[r][j]);
                printf("%d
    ", ans);
            }
        }
    }
    View Code 

    B 排序双指针

    C

    D

    E

    题意:给定无向图和序列$A$,求添一些边,${dist}_i$为$i$到$1$最短距离,使得$sum (A_i-{dist}_i)^2$最小

    添边后dist序列每个值不变或减小,并且对于一条边$(u,v)$,要满足$|{dist}_u-{dist}_v|le 1$,按照切糕构造最小割即可

    #include <bits/stdc++.h>
    using namespace std;
    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10, S = N-2, T = N-1, INF = 0x3f3f3f3f;
    int n, m, tot;
    struct edge {
        int to,w,next;
        edge(int to=0,int w=0,int next=0):to(to),w(w),next(next) {}
    } e[N];
    int head[N], dep[N], cur[N], cnt=1;
    queue<int> Q;
    int bfs() {
        for (int i=1; i<=tot; ++i) dep[i]=INF,cur[i]=head[i];
        dep[S]=INF,cur[S]=head[S];
        dep[T]=INF,cur[T]=head[T];
        dep[S]=0,Q.push(S);
        while (Q.size()) {
            int u = Q.front(); Q.pop();
            for (int i=head[u]; i; i=e[i].next) {
                if (dep[e[i].to]>dep[u]+1&&e[i].w) {
                    dep[e[i].to]=dep[u]+1;
                    Q.push(e[i].to);
                }
            }
        }
        return dep[T]!=INF;
    }
    int dfs(int x, int w) {
        if (x==T) return w;
        int used = 0;
        for (int i=cur[x]; i; i=e[i].next) {
            cur[x] = i;
            if (dep[e[i].to]==dep[x]+1&&e[i].w) {
                int f = dfs(e[i].to,min(w-used,e[i].w));
                if (f) used+=f,e[i].w-=f,e[i^1].w+=f;
                if (used==w) break;
            }
        }
        return used;
    }
    int dinic() {
        int ans = 0;
        while (bfs()) ans+=dfs(S,INF);
        return ans;
    }
    void add(int u, int v, int w) {
        e[++cnt] = edge(v,w,head[u]);
        head[u] = cnt;
        e[++cnt] = edge(u,0,head[v]);
        head[v] = cnt;
    }
    int g[50][50],dist[50],a[50],mp[50][50];
    int main() {
        while (~scanf("%d%d", &n, &m)) {
            memset(g,0,sizeof g);
            for (int i=1; i<=m; ++i) {
                int u, v;
                scanf("%d%d", &u, &v);
                g[u][v] = g[v][u] = 1;
            }
            for (int i=1; i<=n; ++i) scanf("%d", &a[i]);
            tot = 0;
            for (int i=1; i<=n; ++i) for (int j=1; j<n; ++j) mp[i][j] = ++tot;
            cnt = 1;
            for (int i=1; i<=tot; ++i) head[i] = 0;
            head[S] = head[T] = 0;
            memset(dist,0x3f,sizeof dist);
            dist[1] = 0;
            queue<int> q;
            while (q.size()) {
                int u = q.front(); q.pop();
                for (int v=1; v<=n; ++v) if (g[u][v]&&dist[v]==INF) {
                    dist[v] = dist[u]+1;
                    q.push(v);
                }
            }
            for (int i=1; i<=n; ++i) {
                for (int j=1; j<=n; ++j) if (g[i][j]) {
                    for (int d=1;d+1<n;++d) {
                        add(mp[i][d+1],mp[j][d],INF);
                    }
                }
                for (int d=0; d<n; ++d) {
                    int u = mp[i][d], v = mp[i][d+1], cap = (a[i]-d)*(a[i]-d);
                    if (d==0) u = S;
                    if (d+1==n) v = T;
                    if (d>dist[i]) cap = INF;
                    add(u,v,cap);
                }
            }
            printf("%d
    ", dinic());
        }
    }
    View Code

    F 答案是n/(n+m)

    G

    异或可以按位考虑,转化为01序列的情况,用线段树维护即可,复杂度是$O(qlog nlog A)$

    实际上这道题有个$O((n+q)log A)$的做法,考虑维护每次询问与上次之差,类似于莫队,暴力大概像这样写

    int qry(int l, int r) {
        if (l>r) swap(l,r);
        return s[r]^s[l-1];
    }
    int f(int k, int l, int r) {
        int ans = 0;
        for (int i=l; i<=r; ++i) ans += qry(k,i);
        return ans;
    }
    int main() {
        ql = 1, qr = 0;
        for (int i=1; i<=q; ++i) {
            scanf("%d%d", &l, &r);
            while (qr<r) ++qr,ans[i]+=f(qr,ql,qr);
            while (qr>r) ans[i]-=f(qr,ql,qr),--qr;
            while (ql>l) --ql,ans[i]+=f(ql,ql,qr);
            while (ql<l) ans[i]-=f(ql,ql,qr),++ql;
        }
        for (int i=1; i<=q; ++i) printf("%d
    ", ans[i]+=ans[i-1]);
    }
    View Code

    然后可以发现$f$可以拆成前缀,离线扫描线即可,加了个快读只跑了91ms

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10, P = 1e9+7;
    int a[N],ans[N],cnt[N],pw[N][20],c[N][20];
    int64_t h[N];
    struct Q {int l,r,id;};
    vector<Q> g[N];
    char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
    #define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    inline int rd() {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
        return x*f;
    }
    int main() {
        for (int i=1; i<N; ++i) {
            pw[i][0] = i;
            for (int j=1; j<20; ++j) pw[i][j] = pw[i][j-1]*2%P;
        }
        int T=rd();
        while (T--) {
            int n=rd(), q=rd();
            for (int i=1; i<=n; ++i) { 
                h[i] = a[i] = rd();
                a[i] ^= a[i-1];
                g[i].clear();
            }
            for (int i=0; i<20; ++i) { 
                c[1][i] = a[1]>>i&1;
                cnt[i] = 0;
            }
            for (int i=2; i<=n; ++i) {
                for (int j=0; j<20; ++j) { 
                    if (a[i-2]>>j&1) ++cnt[j];
                    c[i][j] = c[i-1][j]+(a[i]>>j&1);
                }
                for (int j=0; j<20; ++j) {
                    if (a[i]>>j&1) h[i] += pw[i-1ll-cnt[j]][j];
                    else h[i] += pw[cnt[j]][j];
                }
                h[i] = (h[i]+h[i-1])%P;
            }
            int ql = 1, qr = 0;
            for (int i=1; i<=q; ++i) { 
                int l = rd(), r = rd();
                ans[i] = 0;
                if (qr<r) { 
                    if (ql>1) g[ql-1].push_back({qr+1,r,-i});
                    ans[i] = (ans[i]+h[r]-h[qr])%P;
                    qr = r;
                }
                if (qr>r) { 
                    if (ql>1) g[ql-1].push_back({r+1,qr,i});
                    ans[i] = (ans[i]+h[r]-h[qr])%P;
                    qr = r;
                }
                if (ql>l) { 
                    if (ql>1) g[ql-1].push_back({ql,qr,i});
                    if (l>1) g[l-1].push_back({l,qr,-i});
                    ans[i] = (ans[i]+h[ql-1]-h[l-1])%P;
                    ql = l;
                }
                if (ql<l) { 
                    if (l>1) g[l-1].push_back({l,qr,-i});
                    if (ql>1) g[ql-1].push_back({ql,qr,i});
                    ans[i] = (ans[i]+h[ql-1]-h[l-1])%P;
                    ql = l;
                }
            }
            for (int i=1; i<=n; ++i) {
                for (auto &&t:g[i]) {
                    int w = 0;
                    for (int d=0; d<20; ++d) {
                        int64_t u = c[t.r][d]-c[t.l-1][d];
                        w = (w+pw[c[i-1][d]][d]*(t.r-t.l+1-u)+pw[i-c[i-1][d]][d]*u)%P;
                    }
                    if (t.id>0) ans[t.id] = (ans[t.id]+w)%P;
                    else ans[-t.id] = (ans[-t.id]-w)%P;
                }
            }
            for (int i=1; i<=q; ++i) { 
                ans[i] = (ans[i]+ans[i-1])%P;
                if (ans[i]<0) ans[i] += P;
                printf("%d
    ", ans[i]);
            }
        }
    }
    View Code

    H 线段树贪心模拟

    I 时限很长,直接冲莫队

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+100;
    int a[N],b[N],blo[N],ans[N][10],cur[10],vis[N],sz[N];
    struct _ {
        int l,r,id;
        bool operator < (const _ &rhs) const {
            return blo[l]^blo[rhs.l]?l<rhs.l:blo[l]&1?r<rhs.r:r>rhs.r;
        }
    } e[N];
    void add(int x) {
        ++vis[x];
        if (vis[x]>1) return;
        int szl = 0, szr = 0;
        while (szl<=10&&x-szl>=2&&b[x-szl-1]+1==b[x-szl]&&vis[x-szl-1]) ++szl;
        while (szr<=10&&b[x+szr]+1==b[x+szr+1]&&vis[x+szr+1]) ++szr;
        if (1<=szl&&szl<=10) --cur[szl-1];
        if (1<=szr&&szr<=10) --cur[szr-1];
        if (1<=szl+szr+1&&szl+szr+1<=10) ++cur[szl+szr];
    }
    void del(int x) {
        --vis[x];
        if (vis[x]>=1) return;
        int szl = 0, szr = 0;
        while (szl<=10&&x-szl>=2&&b[x-szl-1]+1==b[x-szl]&&vis[x-szl-1]) ++szl;
        while (szr<=10&&b[x+szr]+1==b[x+szr+1]&&vis[x+szr+1]) ++szr;
        if (1<=szl&&szl<=10) ++cur[szl-1];
        if (1<=szr&&szr<=10) ++cur[szr-1];
        if (1<=szl+szr+1&&szl+szr+1<=10) --cur[szl+szr];
    }
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int n, m;
            scanf("%d%d", &n, &m);
            int sqn = pow(n,0.55);
            for (int i=1; i<=n; ++i) { 
                scanf("%d", &a[i]);
                b[i] = a[i];
                blo[i] = i/sqn;
            }
            sort(b+1,b+1+n);
            *b = unique(b+1,b+1+n)-b-1;
            for (int i=1; i<=*b; ++i) vis[i] = 0;
            for (int i=1; i<=n; ++i) a[i] = lower_bound(b+1,b+1+*b,a[i])-b;
            for (int i=1; i<=m; ++i) scanf("%d%d",&e[i].l,&e[i].r),e[i].id=i;
            sort(e+1,e+1+m);
            int ql = 1, qr = 0;
            memset(cur,0,sizeof cur);
            for (int i=1; i<=m; ++i) {
                while (ql>e[i].l) add(a[--ql]);
                while (qr<e[i].r) add(a[++qr]);
                while (ql<e[i].l) del(a[ql++]);
                while (qr>e[i].r) del(a[qr--]);
                for (int j=0; j<10; ++j) ans[e[i].id][j] = cur[j]%10;
            }
            for (int i=1; i<=m; ++i) {
                for (int j=0; j<10; ++j) putchar(ans[i][j]+'0');
                putchar(10);
            }
        }
    }
    View Code

    J 跑下状压即可

    K

    B题的加强版,$a$变为$k-a$,然后从大到小排序,那么合法等价于至少有$1$个数$ge a_1$,至少$2$个数$ge a_2$,..... 双指针处理一下即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5+10;
    int a[N], b[N], nxt[N];
    struct seg {
        int mx[N<<2], tag[N<<2];
        void pu(int o) {
            mx[o] = min(mx[o<<1], mx[o<<1|1]);
        }
        void add(int o, int64_t v) {
            mx[o] += v, tag[o] += v;
        }
        void build(int o, int l, int r) {
            tag[o] = 0;
            if (l==r) {
                mx[o] = -l;
                return;
            }
            int mid = (l+r)/2;
            build(o<<1,l,mid);
            build(o<<1|1,mid+1,r);
            pu(o);
        }
        void pd(int o) {
            if (tag[o]) {
                add(o<<1,tag[o]);
                add(o<<1|1,tag[o]);
                tag[o] = 0;
            }
        }
        void add(int o, int l, int r, int ql, int qr, int64_t v) {
            if (ql<=l&&r<=qr) return add(o,v);
            pd(o);
            int mid = (l+r)/2;
            if (mid>=ql) add(o<<1,l,mid,ql,qr,v);
            if (mid<qr) add(o<<1|1,mid+1,r,ql,qr,v);
            pu(o);
        }
        int query(int o, int l, int r, int ql, int qr) {
            if (ql<=l&&r<=qr) return mx[o];
            pd(o);
            int mid = (l+r)/2, ans = 1e9;
            if (mid>=ql) ans = max(ans, query(o<<1,l,mid,ql,qr));
            if (mid<qr) ans = max(ans, query(o<<1|1,mid+1,r,ql,qr));
            return ans;
        }
    } tr;
    
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int n, m, k;
            scanf("%d%d%d", &n, &m, &k);
            for (int i=1; i<=n; ++i) scanf("%d", &a[i]),a[i]=max(0,k-a[i]);
            for (int i=1; i<=m; ++i) scanf("%d", &b[i]), nxt[i] = 1e9;
            tr.build(1,1,n);
            sort(a+1,a+n+1,greater<int>());
            auto add = [&](int x, int v) {
                if (x<a[n]) return;
                int p = lower_bound(a+1,a+1+n,x,greater<int>())-a;
                tr.add(1,1,n,p,n,v);
            };
            int now = 1;
            for (int i=1; i<=m; ++i) {
                while (now<=m&&tr.mx[1]<0) add(b[now++],1);
                if (tr.mx[1]<0) break;
                nxt[i] = now-1;
                add(b[i],-1);
            }
            int q;
            scanf("%d", &q);
            while (q--) {
                int l, r;
                scanf("%d%d", &l, &r);
                puts(nxt[l]<=r?"1":"0");
            }
        }
    }
    View Code

    2016 icpc shenyang

    A 答案A+B+max(A,B)

    B 答案H+12C+16O

    C 矩阵快速幂

    D
    E 枚举每个点作为编号最小点时的团保证不重复,度数很小直接暴力枚举组合数即可

    F

    G

    H
    I 可以得到转移${dp}_x=min {dp}_f+({dep}_x-{dep}_f)^2+p$,${dp}_1=-p$,可以斜率优化一下

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    const int64_t INF = 0x3f3f3f3f3f3f3f3f;
    vector<pair<int,int>> g[N];
    int fa[N],dep[N],p;
    int64_t dp[N];
    deque<int> q{1};
    int64_t dy(int a, int b) {
        return dp[a]+dep[a]*(int64_t)dep[a]-dp[b]-dep[b]*(int64_t)dep[b];
    }
    int64_t dx(int a, int b) {
        return 2*(dep[a]-dep[b]);
    }
    int chk(int a, int b, int c) {
        return dy(a,b)*dx(b,c)<=dy(b,c)*dx(a,b);
    }
    void dfs(int x, int f, int d) {
        fa[x] = f, dep[x] = d;
        vector<int> L,R;
        if (x!=1) {
            while (q.size()>1&&dy(q[1],q[0])<=dx(q[1],q[0])*d) { 
                L.push_back(q[0]);
                q.pop_front();
            }
            dp[x] = dp[q[0]]+(dep[x]-dep[q[0]])*(dep[x]-dep[q[0]])+p;
            while (q.size()>1&&chk(x,q.back(),q[q.size()-2])) { 
                R.push_back(q[0]);
                q.pop_back();
            }
            q.push_back(x);
        }
        for (auto &e:g[x]) if (e.first!=f) dfs(e.first,x,d+e.second);
        if (x!=1) {
            q.pop_back();
            for (int i=(int)R.size()-1; i>=0; --i) q.push_back(R[i]);
            for (int i=(int)L.size()-1; i>=0; --i) q.push_front(L[i]);
            L.clear(),R.clear();
        }
    }
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            int n;
            scanf("%d%d", &n, &p);
            for (int i=1; i<=n; ++i) { 
                g[i].clear();
                dp[i] = INF;
            }
            dp[1] = -p;
            for (int i=1; i<n; ++i) {
                int u, v, w;
                scanf("%d%d%d", &u, &v, &w);
                g[u].push_back({v,w});
                g[v].push_back({u,w});
            }
            dfs(1,0,0);
            int64_t ma = 0;
            for (int i=2; i<=n; ++i) ma = max(ma, dp[i]);
            printf("%lld
    ", ma);
        }
    }
    View Code

    J

    题意:给定$n$点$n$边无向连通图,每次操作把点$u$距离$k$范围的点权$+d$,询问点$u$距离$k$范围内的点权和,$kle 2$

    就是个死做题,首先需要知道$n$点$n$边的无向连通图也就是一棵基环树,先考虑树上怎么做

    对距离$le k$的点进行更新,那么显然不能直接暴力遍历,有个经典套路就是可以在父亲上打标记

    比方说要对点$x$的所有二级儿子权值$+d$,那么就在$x$上打标记,查询一个点权值的时候加上二级父亲的标记值即可

    那么对于这个题,需要对$le 2$范围的点求和,把这个范围拆成$3$种情况

    $a_x$表示点$x$所有二级儿子权值和,$b_x$表示$x$所有儿子权值和,$c_x$表示$x$权值和

    那么$x$范围$2$内的权值和就为$a_x+b_x+b_{f_x}+c_{f_x}+c_{f_{f_x}}$

    对于更新操作,再维护一个${cnt}_{x,1/2}$表示$x$的儿子或二级儿子个数,通过打标记技巧,就可以$O(1)$维护$a,b,c$了

    那么对于基环树的情况,可以先dfs求出一棵生成树,提出那条环边,更新时特判一下经过环边的贡献即可

    这题数据好像比较弱,之前判错了一个地方结果竟然过了

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int n, q, dep[N], fa[N], A, B, cnt[N][2];
    int64_t c[N][3], b[N][2], a[N], d, ans;
    vector<int> g[N];
    void dfs(int x, int f, int d) {
        fa[x] = f, dep[x] = d;
        ++cnt[f][0], ++cnt[fa[f]][1];
        for (int y:g[x]) if (y!=f) {
            if (!dep[y]) dfs(y,x,d+1);
            else if (dep[y]<dep[x]) A = x, B = y;
        }
    }
    void upd(int x, int k) {
        if (!x) return;
        int f = fa[x], ff = fa[f];
        c[x][k] += d;
        if (k==0) { 
            b[f][0] += d;
            a[ff] += d;
        }
        else if (k==1) {
            b[x][0] += d*cnt[x][0];
            a[f] += d*cnt[x][0];
        }
        else {
            b[x][1] += d;
            a[x] += d*cnt[x][1];
        }
    }
    void qry(int x, int k) {
        if (!x) return;
        int f = fa[x], ff = fa[f];
        if (k==0) ans += c[x][0]+c[f][1]+c[ff][2];
        if (k==1) ans += b[x][0]+b[f][1]*cnt[x][0];
        if (k==2) ans += a[x];
    }
    void solve(void(*gao)(int,int),int x, int k) {
        int f = fa[x], ff = fa[f];
        if (k==0) return gao(x,0);
        if (x==A) gao(B,0);
        if (k==1) { 
            gao(x,1),gao(x,0),gao(f,0);
            if (x==B) gao(A,0);
            return;
        }
        gao(x,2),gao(x,1),gao(f,1);
        if (!f) gao(x,0);
        if (x==A) gao(B,1),gao(fa[B],0);
        if (x!=A||dep[A]-dep[B]>=3) gao(f,0);
        if (x!=A||dep[A]-dep[B]>3) gao(ff,0);
        if (fa[x]==A||x==fa[A]&&B!=f&&B!=ff) gao(B,0);
        if (x==fa[B]||fa[x]==B&&fa[A]!=x&&fa[fa[A]]!=x) gao(A,0);
        if (x==B) {
            if (dep[A]-dep[B]>2) gao(A,0);
            if (dep[A]-dep[B]>3) gao(fa[A],0);
            gao(A,1);
        }
    }
    int main() {
        int T;
        scanf("%d", &T);
        while (T--) {
            scanf("%d", &n);
            for (int i=1; i<=n; ++i) { 
                g[i].clear();
                fa[i] = dep[i] = cnt[i][0] = cnt[i][1] = 0;
                c[i][0] = c[i][1] = c[i][2] = b[i][0] = b[i][1] = a[i] = 0;
            }
            for (int i=0; i<n; ++i) {
                int u, v;
                scanf("%d%d", &u, &v);
                g[u].push_back(v);
                g[v].push_back(u);
            }
            dfs(1,0,1);
            scanf("%d", &q);
            while (q--) {
                char s[20];
                int u, k;
                scanf("%s%d%d", s, &u, &k);
                ans = 0;
                if (s[0]=='M') scanf("%lld", &d), solve(upd,u,k);
                else solve(qry,u,k), printf("%lld
    ", ans);
            }
        }
    }
    View Code

    K

    L

    M

  • 相关阅读:
    通用页面调用APP 互通
    HTML5[5]:在移动端禁用长按选中文本功能
    JAVA 中的基础
    手机访问PC网站自动跳转到手机网站代码
    自适应的设置字体的方式
    localStorage 与 sessionStorage
    《高级程序设计》3 基本慨念
    javascript基础
    jQuery技巧
    jQuery性能优化
  • 原文地址:https://www.cnblogs.com/fs-es/p/14068874.html
Copyright © 2020-2023  润新知