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\n", 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)$的异或和

枚举每个二进制位,转化为求$\sum\limits_{i=1}^n \lfloor\frac{a_i-\lfloor\frac{a_i}{x}\rfloor x}{2^d}\rfloor$的奇偶性

枚举$a_i$值很小,可以枚举$t=\lfloor\frac{a_i}{x}\rfloor$,那么等价于求$\sum\limits_{t\le i<i+t}\lfloor\frac{i-t}{2^d}\rfloor$

$\lfloor\frac{i-t}{2^d}\rfloor=\lfloor\frac{i}{2^d}\rfloor - \lfloor\frac{t}{2^d}\rfloor - [i\space mod 2^d<t\space mod 2^d]$

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

可以发现$i \space mod 2^d$是循环的,预处理每块的前缀和就可以$O(n\log^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")<<" \n"[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\n", 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\n", X+x, Y+y);
        else {
            int t = vis[{-X,-Y}];
            printf("%d %d\n", 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\n", 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\n", 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\n", 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: \n", clk);
        for (int i=1; i<=n; ++i) printf("%d%c", ans[i], " \n"[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\n", 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\n", 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\n", 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\n", 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\n", clk);
            continue;
        }
        ans = 0;
        dfs1(1);
        dfs(1);
        printf("Case #%d: %d\n", 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:\n", clk);
        for (int i=1; i<=n; ++i) printf("%d\n", d[i][0]==INF?-1:d[i][0]);
    }
}
View Code

H

答案就等于$\sum\limits_{\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\n", 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\n", 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\n", ans, ans_x, ans_y);
    }
}
View Code

C

E

题意:$n\times 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}=\prod\sum {dp}_{y,j}[gcd(i,j)=1]$,其中$y$是$x$的儿子

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

这样就可以$O(nM\log 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\n", 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\n", 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)=\sum\limits_{1\le 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\n", ans+ma);
                continue;
            }
            if (n>2) ma = max(ma, 2ll*x+2ll*T.query(1,2,n-1,l+1,r-1));
            printf("%lld\n", 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 mod\space 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 n\log 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\n", 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\n", 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(n\log 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\n", 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\n", dinic());
    }
}
View Code

F 答案是n/(n+m)

G

异或可以按位考虑,转化为01序列的情况,用线段树维护即可,复杂度是$O(q\log n\log 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\n", 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\n", 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\n", ma);
    }
}
View Code

J

题意:给定$n$点$n$边无向连通图,每次操作把点$u$距离$k$范围的点权$+d$,询问点$u$距离$k$范围内的点权和,$k\le 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\n", ans);
        }
    }
}
View Code

K

L

M

posted @ 2020-12-03 14:36  dz8gk0j  阅读(181)  评论(0编辑  收藏  举报