• swerc 2018

    A 模拟

    B 考虑二分答案$x$, 一个区间[i,i+x-1]合法等价于右端点最小值-左端点最大值+1>=x, RMQ预处理最值即可

    C

    D 答案在类似于一个中位数的位置最优, 坐标范围很小可以直接暴力

    E 水题

    F

    G

    H

    I 先判掉边界周围的石头和单独一块的石头, 然后其他连通块只能为ABC, A下边界有空, C右边界有空, 否则为B

    J

    K kmp预处理一下, 然后区间dp就行了

    seerc 2017

    A 简单dp

    B

    显然最终结构与操作顺序无关, 假设$a_i$为第$i$个位置放砖块次数, ${sum}_i$为$a$的前缀和

    可以发现一段极长连续砖块$[l,r]$要满足$r-l+1={sum}_r-{sum}_{l-1}wedge a_{l-1}=a_{r+1}=0$

    考虑$dp$, 设$f_i$表示点$i$不填砖块时前$i$个位置的方案数

    当$a_i=0$时$f_i=0$

    当$a_i ot =0$时, 枚举以$i-1$结尾的极长连续砖块长度, 可以得到$f_i=sumlimits_{substack{0le j<i \ {sum}_{i-1}-{sum}_j=i-1-j}} f_j$

    维护$g_{x} = sumlimits_{substack{0le j<i \ {sum}_{j}-j=x}} f_j, $那么$f_{i}=g_{{sum}_{i-1}-i+1},$ 这样复杂度就为$O(n)$ 

    #include <stdio.h>
    const int N = 2e6+10, P = 1e9+7;
    int n, m, a[N], sum[N], f[N], g[N];
    void add(int &a, int b) {a+=b;if (a>=P)a-=P;}
    int main() {
        scanf("%d%d", &n, &m);
        for (int i=1; i<=m; ++i) {
            int x;
            scanf("%d", &x);
            ++a[x];
        }
        for (int i=1; i<=n; ++i) sum[i] = sum[i-1]+a[i];
        g[n] = 1;
        for (int i=1; i<=n+1; ++i) if (!a[i]) {
            f[i] = g[sum[i-1]-i+1+n];
            add(g[sum[i]-i+n], f[i]);
        }
        printf("%d
    ", f[n+1]);
    }
    View Code

    C

    预处理出每种颜色极长路径, 那么这种颜色时间要比路径上其他颜色早, 可以用倍增优化拓扑排序

    还有种思路是暴力遍历每种颜色的链, 碰见链中有异色的点就递归下去, 最后把出栈序列倒序输出即可, 暴力遍历每条边以用并查集优化, 复杂度是$O(n)$的

    D 每个向量两个非零点连下边, 每个连通块答案就为点数-1

    E 求环上最小划分, 可以枚举首尾属于哪个音阶, 这样就不用考虑首尾影响, 中间直接贪心即可

    F 最优策略一定是先按权值从大到小翻转$1$, 最后按权值从小到大翻转$0$, 有可能翻转的$1$中$b$值为$1$, 那么最后还有翻转回来, 可以发现这样的$1$权值一定是连续最大的一段, 枚举这一段暴力即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10;
    int n, a[N];
    char b[N], c[N];
    int main() {
        scanf("%d", &n);
        for (int i=0; i<n; ++i) scanf("%d", a+i);
        scanf("%s%s", b, c);
        vector<int> A,B,C;
        long long sum = 0;
        for (int i=0; i<n; ++i) {
            b[i] -= '0', c[i] -= '0';
            if (b[i]) sum += a[i];
            if (b[i]&&c[i]) A.push_back(a[i]);
            if (b[i]&&!c[i]) B.push_back(a[i]);
            if (!b[i]&&c[i]) C.push_back(a[i]);
        }
        sort(A.begin(),A.end(),greater<int>());
        sort(B.begin(),B.end(),greater<int>());
        sort(C.begin(),C.end());
        long long ans = 1e18;
        for (int i=0; i<=A.size(); ++i) {
            long long ret = 0, now = sum;
            vector<int> x = B, y = C, tmp, xx, yy;
            for (int j=0; j<i; ++j) tmp.push_back(A[j]);
            merge(x.begin(),x.end(),tmp.begin(),tmp.end(),back_inserter(xx),greater<int>());
            tmp.clear();
            for (int j=i-1; j>=0; --j) tmp.push_back(A[j]);
            merge(y.begin(),y.end(),tmp.begin(),tmp.end(),back_inserter(yy));
            for (auto &t:xx) now -= t, ret += now;
            for (auto &t:yy) now += t, ret += now;
            ans = min(ans, ret);
        }
        printf("%lld
    ", ans);
    }
    View Code

    G 模拟

    H

    I

    J 相当于是先手每次取一次,后手每次取两次,暴力打表找下规律即可

    K 简单构造

    L 总边数是$2(n-1)$, 所以度数最小值$<=3$, 那么答案只能为$2$或$3$

    那么一定有一棵树只切了$1$条边, 枚举这一条边, 假设得到一个连通块$S$, 那么另一棵树中一定要把$S$与不在$S$内的点连的边全部切掉.

    考虑维护这样的边的个数, 对于另一棵树每一条边$(u,v)$, $++w[u], ++w[v], w[lca(u,v)]-=2$, 然后边的个数就为子树和

    2014 icpc anshan

    B 模拟

    C 求同色三角形数, 转化为求异色三角形数, 异色三角形个数就为异色角个数/2

    等价于对于每个$a_i$, 求有多少个数和它互素, 莫比乌斯反演即可

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e5+10;
    int n,a[N],cnt[N],mu[N],f[N];
    int main() {
        mu[1] = 1;
        for (int i=1; i<N; ++i) {
            for (int j=2*i; j<N; j+=i) mu[j]-=mu[i];
        }
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d", &n);
            memset(cnt,0,sizeof cnt);
            memset(f,0,sizeof f);
            for (int i=1; i<=n; ++i) scanf("%d", a+i),++cnt[a[i]];
            for (int i=1; i<N; ++i) {
                for (int j=2*i; j<N; j+=i) cnt[i]+=cnt[j];
                int ret = mu[i]*cnt[i];
                if (!ret) continue;
                for (int j=i; j<N; j+=i) f[j]+=ret;
            }
            ll ans = 0;
            for (int i=1; i<=n; ++i) if (a[i]>1) ans += (ll)f[a[i]]*(n-1-f[a[i]]);
            ll tot = n*(n-1ll)*(n-2)/6;
            printf("%lld
    ", tot-ans/2);
        }
    }
    View Code

    D 最优情况一定是取连续$n-k$个点, 预处理前缀和即可

    E 简单dp

    H 直接暴力bfs打表, 大概要跑半个小时

    I 模拟

    J

    设${ans}_{i}$表示美丽值$<i$的方案数, 那么美丽值恰好为$i$的答案就为${ans}_{i+1}-{ans}_{i}$

    考虑求${ans}_{x}$, 设${dp}_{i,s}$为前$i$行状态为$s$的方案数, 状态$s$中存$n-x+1$个数, 第$j$个数表示$[j,j+x-1]$中向上连续白色高度的最小值, 暴力$O(2^n)$枚举一行转移即可

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int P = 1e9+7;
    int n, ans[10], f[10];
    map<vector<int>,int> dp[10];
    void add(int &a, int b) {a+=b;if (a>=P)a-=P;}
    char s[10][10];
    int calc(int x) {
        int mx = (1<<n)-1, ans = 0;
        for (int i=1; i<=n; ++i) {
            dp[i].clear();
            for (int j=0; j<=mx; ++j) {
                int ok = 1;
                for (int k=0; k<n; ++k) if ((j>>k&1)&&s[i][k]=='*') ok = 0;
                if (!ok) continue;
                for (int l=0,r=x-1; r<n; ++l,++r) {
                    int t = 1;
                    for (int k=l; k<=r; ++k) t &= j>>k&1;
                    f[l] = t;
                }
                if (i==1) {
                    if (i==n) add(ans, 1);
                    else ++dp[1][vector<int>(f,f+n-x+1)];
                    continue;
                }
                for (auto &t:dp[i-1]) {
                    auto v = t.first;
                    int ok = 1;
                    for (int u=0; u<n-x+1; ++u) {
                        if (f[u]) { 
                            if (++v[u]>=x) ok = 0;
                        }
                        else v[u] = 0;
                    }
                    if (!ok) continue;
                    if (i==n) add(ans, t.second);
                    else add(dp[i][v], t.second);
                }
            }
        }
        return ans;
    }
    void work() {
        scanf("%d", &n);
        for (int i=1; i<=n; ++i) scanf("%s", s[i]);
        ans[0] = 1;
        for (int i=1; i<=n; ++i) ans[i] = calc(i+1);
        for (int i=n; i>=1; --i) { 
            ans[i] = (ans[i]-ans[i-1])%P;
            if (ans[i]<0) ans[i] += P;
        }
        for (int i=0; i<=n; ++i) printf("%d
    ", ans[i]);
    }
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) work();
    }
    View Code

    K poyla 不会

    L ac自动机fail树建出来, 那么修改操作等价与对子树并权值+1, 询问操作等价于求点到根的链的并的权值和

    按dfs序排序, 然后树状数组维护即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int rt, n, clk, ch[N][26], fa[N], dep[N], L[N], R[N];
    int tr[N], a[N], sz[N], top[N], son[N];
    int64_t tr2[N];
    vector<int> g[N];
    void init() {
        clk = 0, rt = 1;
        for (int i=1; i<=n; ++i) {
            memset(ch[i],0,sizeof ch[i]);
            fa[i] = tr[i] = tr2[i] = son[i] = 0;
            g[i].clear();
        }
    }
    void dfs(int x, int d) {
        L[x] = ++clk, dep[x] = d, sz[x] = 1;
        for (int y:g[x]) { 
            dfs(y,d+1);
            sz[x] += sz[y];
            if (sz[y]>sz[son[x]]) son[x] = y;
        }
        R[x] = clk;
    }
    void dfs2(int x, int tf) {
        top[x] = tf;
        if (son[x]) dfs2(son[x],tf);
        for (int y:g[x]) if (y!=son[x]) dfs2(y,y);
    }
    int lca(int x, int y) {
        while (top[x]!=top[y]) {
            if (dep[top[x]]<dep[top[y]]) y = fa[top[y]];
            else x = fa[top[x]];
        }
        return dep[x]<dep[y]?x:y;
    }
    void build() {
        queue<int> q;
        for (int i=0; i<26; ++i) {
            if (ch[rt][i]) fa[ch[rt][i]]=rt,q.push(ch[rt][i]);
            else ch[rt][i]=rt;
        }
        while (q.size()) {
            int u = q.front(); q.pop();
            g[fa[u]].push_back(u);
            for (int i=0; i<26; ++i) {
                int &v = ch[u][i];
                if (v) { 
                    fa[v] = ch[fa[u]][i];
                    q.push(v);
                }
                else v = ch[fa[u]][i];
            }
        }
        dfs(1,0),dfs2(1,1);
    }
    void add(int x) {
        for (int i=L[x]; i<=n; i+=i&-i) ++tr[i],tr2[i]-=dep[x];
        for (int i=R[x]+1; i<=n; i+=i&-i) --tr[i],tr2[i]+=dep[x];
    }
    int64_t qry(int x) {
        int ans1 = 0;
        int64_t ans2 = 0;
        for (int i=L[x]; i; i^=i&-i) ans1 += tr[i], ans2 += tr2[i];
        return ans1*(dep[x]+1ll)+ans2;
    }
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            init();
            scanf("%d", &n);
            for (int i=2; i<=n; ++i) {
                int f;
                char c;
                scanf("%d %c", &f, &c);
                ch[f][c-'a'] = i;
            }
            build();
            int m;
            scanf("%d", &m);
            set<int> s;
            while (m--) {
                int op, k;
                scanf("%d%d", &op, &k);
                for (int i=0; i<k; ++i) scanf("%d", &a[i]);
                sort(a,a+k,[](int a,int b){return L[a]<L[b];});
                if (op==1) {
                    int mx = 0;
                    for (int i=0; i<k; ++i) {
                        if (L[a[i]]<=mx) continue;
                        mx = max(mx, R[a[i]]);
                        add(a[i]);
                    }
                }
                else {
                    int64_t ans = 0;
                    for (int i=0; i<k; ++i) {
                        ans += qry(a[i]);
                        if (i) ans -= qry(lca(a[i],a[i-1]));
                    }
                    printf("%lld
    ", ans);
                }
            }
        }
    }
    View Code

    2014 icpc tokyo

    A 模拟

    B 模拟

    C 交叉的区间合并一下即可

    F 先求出一棵MST, 然后枚举每条非树边, 暴力删掉所在换上与它权值相等的边

    G

    左括号看做$1$, 右括号看做$-1$

    如果修改左括号, 直接找到最左侧的右括号翻转即可

    如果修改右括号, 要在$[1,r-1]$中找到一个最小的位置$l$, 满足[l,r-1]前缀和最小值$>=2$, 用支持区间加查询最值的线段树即可

    2019 opencup tokyo

    E 假设确定每个数选取频率$b_i$, 那么方案数为$frac{n!}{b_1!...b_k!}$, 当它为奇数的时候有贡献, 也就是说每个$b_i$二进制与和等于$n$, 所以可以把每个$A_i$拆成若干个$2^xA_i$, 然后按$x$排序, 暴力背包即可, 因为每次$x$增加时当前和与$S$关于$2^x$要同余, 背包只要遍历$O(A_i)$的体积, 总复杂度是$O(log{n}K A_i)$

    F 最小值显然是$sum |a_i-b_i|$, 考虑构造让每个$b_i$与$a_i$匹配

    可以发现如果存在$a_i<b_i$并且$a_{i+1}>b_{i+1}$那么$i$或$i+1$一定有一个合法的, 若不存在这样的$i$, 那么要么$1$合法要么$N$合法

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+50;
    int n,a[N],b[N];
    int main() {
        cin>>n;
        for(int i=0;i<n;++i)cin>>a[i];
        for(int i=0;i<n;++i)cin>>b[i];
        long long ans = 0;
        vector<int> v, s;
        for(int i=0;i<n;++i) {
            ans += abs(a[i]-b[i]);
            if (a[i]>b[i]) { 
                while (s.size()&&b[s.back()]-a[s.back()]<=a[i]-b[i]) v.push_back(s.back()),s.pop_back();
                v.push_back(i);
            }
            else s.push_back(i);
        }
        while (s.size()) v.push_back(s.back()),s.pop_back();
        cout<<ans<<endl;
        for (int t:v) cout<<t+1<<' ';
    }
    View Code

    H 构造两条几乎平行的直线(-1e9,0),(1,1e9)和(0,-1e9),(1e9,-1)

    2016 ccpc final

    A 简单贪心

    B 堆贪心预处理出每件衣服洗完最小时间, 然后再倒序堆贪心求出最短烘干时间

    E 对于$m$道题, 要让每道题可选的区间数$x$尽量多, 最终答案就是$max(n-x+1)$

    所以可以考虑把每个区间按左端点排序, 每道题排序, 对于每道题贪心选择可选范围内右端点最小的, 这样就可以保证之后的题可选区间数尽量多

    G 无向图最小环问题. 先求出最小生成树, 然后枚举非树边即可

    H 范围好小, 随便状压dp一下就行

    I dijkstra跑出每个物品最小花费, 然后跑背包

    J 模拟

    L 模拟

    2016 icpc china final

    A 答案是$lfloorfrac{n}{3} floor$

    B 题目等价于求给定一个前缀, 求后面有多少种填法使得奇数位为回文或者偶数位为回文, 简单统计一下即可

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll M = 4e18+10;
    const int N = 1e5+10;
    int n,tot0,tot1,ck0,ck1,clk;
    ll k,ans,pw[N];
    string s0,s1,s;
    ll calc() {
        int L = -1, R = -1;
        if (s0.size()<=tot0/2) L = (tot0-s0.size()*2+1)/2;
        else if (ck0) {
            L = 0;
            if (s0.back()!=s0[tot0-s0.size()]) L = -1;
        }
        if (s1.size()<=tot1/2) R = (tot1-s1.size()*2+1)/2;
        else if (ck1) {
            R = 0;
            if (s1.back()!=s1[tot1-s1.size()]) R = -1;
        }
        if (L<0&&R<0) return 0;
        if (L<0) return pw[R+tot0-s0.size()];
        if (R<0) return pw[L+tot1-s1.size()];
        ll Z = L+R;
        L += tot1-s1.size(), R += tot0-s0.size();
        return pw[L]+pw[R]-pw[Z];
    }
     
    void work() {
        cin>>n>>k;
        s0.clear(),s1.clear(),s.clear();
        tot1 = n/2, tot0 = n-n/2;
        ck1 = ck0 = 1;
        ++clk;
        if (calc()<k) return cout<<"Case #"<<clk<<": NOT FOUND!"<<'
    ',void();
        for (int i=0; i<n; ++i) {
            s += '0';
            int t0 = ck0, t1 = ck1;
            if (i&1) s1.push_back(s.back());
            else s0.push_back(s.back());
            ll w = calc();
            if (k>w) {
                s.back() = '1';
                if (i&1) s1.back() = '1';
                else s0.back() = '1';
                k -= w;
            }
            if (tot0<2*s0.size()&&s0.back()!=s0[tot0-s0.size()]) ck0 = 0;
            if (tot1<2*s1.size()&&s1.back()!=s1[tot1-s1.size()]) ck1 = 0;
        }
        cout<<"Case #"<<clk<<": "<<s<<'
    ';
    }
     
    int main() {
        ios::sync_with_stdio(0);
        pw[0] = 1;
        for (int i=1; i<N; ++i) pw[i] = min(M, pw[i-1]*2);
        int t;
        cin>>t;
        while (t--) work();
    }
    View Code

    C 考虑$O(n^2)$枚举右侧区间$[l,r]$, 维护左侧区间每个点对应最小左端点

    固定$l$, 当$r$右移时, 那么左侧所有区间都不能包含$a_r$, 用一个支持区间取最大值的线段树维护即可

    复杂度是$O(n^2log{n})$, 感觉做法跟CF1396D做法差不多

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e3+10;
    int n, a[N], b[N], pre[N], vis[N];
    vector<int> g[N];
    struct {
        int len[N<<2],mi[N<<2],ma[N<<2],tag[N<<2];
        void upd(int o, int l, int r, int v) {
            tag[o] = mi[o] = ma[o] = v;
            len[o] = r-v;
        }
        void pu(int o) {
            mi[o] = min(mi[o<<1], mi[o<<1|1]);
            ma[o] = max(ma[o<<1], ma[o<<1|1]);
            len[o] = max(len[o<<1], len[o<<1|1]);
        }
        void pd(int o, int l, int r) {
            if (tag[o]!=-1) {
                int mid = (l+r)>>1;
                upd(o<<1,l,mid,tag[o]);
                upd(o<<1|1,mid+1,r,tag[o]);
                tag[o] = -1;
            }
        }
        void build(int o, int l, int r) {
            tag[o] = -1;
            if (l==r) return upd(o,l,l,pre[l]);
            int mid = (l+r)>>1;
            build(o<<1,l,mid), build(o<<1|1,mid+1,r);
            pu(o);
        }
        void update(int o, int l, int r, int ql, int qr, int v) {
            if (mi[o]>=v) return;
            if (ql<=l&&r<=qr&&ma[o]<=v) return upd(o,l,r,v);
            pd(o,l,r); int mid = (l+r)>>1;
            if (mid>=ql) update(o<<1,l,mid,ql,qr,v);
            if (mid<qr) update(o<<1|1,mid+1,r,ql,qr,v);
            pu(o);
        }
    } sgt;
    int main() { 
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d", &n);
            for (int i=1; i<=n; ++i) scanf("%d", a+i), b[i] = a[i];
            sort(b+1,b+1+n);
            *b = unique(b+1,b+1+n)-b-1;
            for (int i=1; i<=*b; ++i) g[i].clear();
            for (int i=1; i<=n; ++i) { 
                a[i] = lower_bound(b+1,b+1+*b,a[i])-b;
                if (g[a[i]].size()) pre[i] = g[a[i]].back();
                else pre[i] = 0;
                g[a[i]].push_back(i);
                pre[i] = max(pre[i], pre[i-1]);
            }
            int ans = 0;
            for (int l=1; l<=n; ++l) {
                if (l>1) sgt.build(1,1,l-1);
                for (int i=1; i<=*b; ++i) vis[i] = 0;
                for (int r=l; r<=n; ++r) {
                    if (vis[a[r]]) break;
                    vis[a[r]] = 1;
                    for (auto &t:g[a[r]]) {
                        if (t>=l) break;
                        if (l>1) sgt.update(1,1,l-1,t,l-1,t);
                    }
                    int ret = r-l+1;
                    if (l>1) ret += sgt.len[1];
                    ans = max(ans, ret);
                }
            }
            static int clk;
            printf("Case #%d: %d
    ", ++clk, ans);
        }
    }
    View Code

    D 二分答案, 每次取最小的一层一层贪心放即可

    E 可以发现赚钱等价于$sum frac{a}{a+b}<1$, 所以排序后加一下即可

    精度问题可以用高精维护分子分母

    #include <bits/stdc++.h>
    using namespace std;
    pair<int,int> w[111];
    vector<int> x, y;
    void gao(vector<int> &a) {
        int sz = a.size();
        for (int i=0; i<sz; ++i) {
            int t = a[i]/10;
            a[i] -= t*10;
            if (t) {
                if (i==sz-1) ++sz, a.push_back(t);
                else a[i+1] += t;
            }
        }
    }
    vector<int> mul(vector<int> a, int b) {
        for (auto &t:a) t *= b;
        return a;
    }
    vector<int> add(vector<int> a, vector<int> b) {
        if (a.size()>b.size()) {
            for (int i=0; i<b.size(); ++i) a[i] += b[i];
            return gao(a), a;
        }
        for (int i=0; i<a.size(); ++i) b[i] += a[i];
        return b;
    }
    int cmp(vector<int> &a, vector<int> &b) {
        gao(a), gao(b);
        if (a.size()!=b.size()) return a.size()<b.size()?-1:1;
        for (int i=a.size()-1; i>=0; --i) {
            if (a[i]>b[i]) return 1;
            if (a[i]<b[i]) return -1;
        }
        return 0;
    }
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            int n;
            scanf("%d", &n);
            for (int i=1; i<=n; ++i) {
                double x, y;
                scanf("%lf:%lf", &x, &y);
                int a = x*1000+0.5;
                int b = y*1000+0.5;
                w[i] = {a,a+b};
            }
            sort(w+1,w+1+n,[](pair<int,int> a,pair<int,int> b){ 
                    return (long long)a.first*b.second<(long long)b.first*a.second;
            });
            x.clear(), y.clear();
            int ans = 0;
            for (int i=1; i<=n; ++i) {
                int a = w[i].first;
                int b = w[i].second;
                if (i==1) x.push_back(a), y.push_back(b);
                else {
                    x = add(mul(x,b),mul(y,a));
                    y = mul(y,b);
                }
                if (cmp(x,y)==-1) ans = i;
                else break;
            }
            static int clk;
            printf("Case #%d: %d
    ", ++clk, ans);
        }
    }
    View Code

    F 枚举第一个串的后缀$i$, 其他串后缀排序一下, 每次二分出后缀$i$与其他串后缀最大$lcp$值, 那么$s[i,i+lcp]$一定不是其他串后缀, 也可以按后缀顺序枚举第一个串的后缀, 双指针求出$lcp$值, 这样常数会小一点

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e6+10;
    int Log[N];
    struct SA {
        int n,c[N],rk[N],h[N],sa[N],a[N],f[N][20];
        //rk[i]为后缀i的排名, sa[i]为排名i的后缀位置
        //h[i]为lcp(sa[i-1],sa[i]), h[1]无意义
        void build(int *a, int m) {
            a[n+1] = rk[n+1] = h[n+1] = 0;
            int i,*x=rk,*y=h;
            for(i=1;i<=m;i++) c[i]=0;
            for(i=1;i<=n;i++) c[x[i]=a[i]]++;
            for(i=1;i<=m;i++) c[i]+=c[i-1];
            for(i=n;i;i--) sa[c[x[i]]--]=i;
            for(int k=1,p;k<=n;k<<=1) {
                p=0;
                for(i=n-k+1;i<=n;i++) y[++p]=i;
                for(i=1;i<=n;i++) if(sa[i]>k) y[++p]=sa[i]-k;
                for(i=1;i<=m;i++) c[i]=0;
                for(i=1;i<=n;i++) c[x[y[i]]]++;
                for(i=1;i<=m;i++) c[i]+=c[i-1];
                for(i=n;i;i--) sa[c[x[y[i]]]--]=y[i];
                swap(x,y); x[sa[1]]=1; p=1;
                for(i=2;i<=n;i++)
                    x[sa[i]]=(y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k])?p:++p;
                if(p==n) break; m=p;
            }
            for(i=1;i<=n;i++) rk[sa[i]]=i;
            for(int i=1,j,k=0;i<=n;i++) if (rk[i]!=1) {
                if(k) k--;
                j=sa[rk[i]-1];
                while(a[i+k]==a[j+k]) k++;
                h[rk[i]] = k;
            }
        }
        void init(int _n) {
            n = _n;
            build(a,100);
            for (int i=1; i<=n; ++i) f[i][0] = h[i];
            for (int j=1; j<=19; ++j) {
                for (int i=0; i+(1<<j-1)-1<=n; ++i) {
                    f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]);
                }
            }
        }
        int lcp(int l, int r) {
            if (l==r) return n-l+1;
            l = rk[l], r = rk[r];
            if (l>r) swap(l,r); ++l;
            int t = Log[r-l+1];
            return min(f[l][t], f[r-(1<<t)+1][t]);
        }
    } sa;
    
    int L[N], R[N];
    char s[N];
    int cmp(int l1, int r1, int l2, int r2) {
        int p = sa.lcp(l1,l2);
        if (p==r1-l1+1) return 0;
        if (s[l1+p]<s[l2+p]) return -1;
        if (s[l1+p]==s[l2+p]) return 0;
        return 1;
    }
    int main() {
        Log[0] = -1;
        for (int i=1; i<N; ++i) Log[i] = Log[i>>1]+1;
        int t;
        scanf("%d", &t);
        while (t--) {
            int n;
            scanf("%d", &n);
            for (int i=1; i<=n; ++i) {
                L[i] = R[i-1]+1;
                scanf("%s", s+L[i]);
                R[i] = R[i-1]+strlen(s+L[i]);
                if (i==1) s[++R[i]] = 'z'+1;
                else s[++R[i]] = 'z'+2;
            }
            for (int i=1; i<=R[n]; ++i) sa.a[i] = s[i]-'a'+1;
            sa.init(R[n]);
            vector<int> v;
            for (int i=2; i<=n; ++i) {
                for (int j=L[i]; j<R[i]; ++j) v.push_back(sa.rk[j]);
            }
            sort(v.begin(),v.end());
            int anslen = -1, x, y;
            for (int i=L[1]; i<R[1]; ++i) {
                auto p = lower_bound(v.begin(),v.end(),sa.rk[i]);
                int len = 0;
                if (p!=v.end()) len = max(len, sa.lcp(i,sa.sa[*p]));
                if (p!=v.begin()) len = max(len, sa.lcp(i,sa.sa[*(--p)]));
                if (i+len!=R[1]) {
                    if (anslen==-1||len<anslen||len==anslen&&cmp(i,i+len,x,y)<0) { 
                        anslen = len;
                        x = i, y = i+len;
                    }
                }
            }
            static int clk;
            printf("Case #%d: ", ++clk);
            if (anslen==-1) puts("Impossible");
            else {
                for (int i=x; i<=y; ++i) putchar(s[i]);
                puts("");
            }
        }
    }
    View Code

    G

    H

    $sum (g+1) A_g=sum g A_g+K^{NM}$, $sum g A_g$也就是每个位置的贡献

    所以$sum g A_g = NMK^{N+M-1}sumlimits_{i=1}^K (i-1)^{N+M-2}$

    I

    J

    K

    L 直接暴力

    2017 ccpc hangzhou

    A 奇数位必须相同, 偶数位必须相同

    B 可以推出来f(p^k)=(p^k-p^{k-1})k+p^k

    C 跟seerc2017J几乎一样, 暴力一下, 规律很好找

    D 暴力找规律

    E 点分治处理连通块问题, 跟hdu5909一样, 点分治时根为$x$时, 可以自上到下$dp$求出所有包含$x$的连通块方案

    F

    G

    H

    I

    J 差分模拟一下

    K 考虑求$S(t)$, 对于固定的$a_i$, $S(t)=sum (lfloorfrac{t}{a_i} floor - lfloorfrac{b_i}{a_i} floor)$再减去$tspace mod space a_i<b_ispace mod space a_i$的个数

    修改操作用分块后缀加, 询问操作二分答案暴力计算每种$a_i$的答案

    复杂度就是$O((n+m)sqrt{a_i}+1000a_ilog{k})$

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e5+10, S = 50;
    int n, m, a[N], b[N], cnt[1010];
    ll sum;
    struct {
        int s1[1010],s2[1010];
        void init(int n) {
            for (int i=0; i<=n; ++i) s1[i] = s2[i] = 0;
        }
        void add(int x, int v) {
            for (int i=x/S*S; i<=x; ++i) s1[i]+=v;
            for (int i=x/S-1; i>=0; --i) s2[i]+=v;
        }
        int qry(int x) {
            return s1[x]+s2[x/S];
        }
    } bit[1010];
    void add(int x, int y) {
        sum += y/x;
        bit[x].add(y%x,1);
        ++cnt[x];
    }
    void del(int x, int y) {
        sum -= y/x;
        bit[x].add(y%x,-1);
        --cnt[x];
    }
    ll calc(ll x) {
        ll ans = -sum;
        for (int i=1; i<=1000; ++i) if (cnt[i]) {
            ans += x/i*cnt[i]-bit[i].qry(x%i+1);
        }
        return ans;
    }
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d", &n, &m);
            sum = 0;
            for (int i=1; i<=1000; ++i) cnt[i]=0,bit[i].init(i);
            for (int i=1; i<=n; ++i) scanf("%d", a+i);
            for (int i=1; i<=n; ++i) scanf("%d", b+i);
            for (int i=1; i<=n; ++i) add(a[i],b[i]);
            while (m--) {
                int op, x, y;
                scanf("%d%d", &op, &x);
                if (op==1) {
                    scanf("%d", &y);
                    del(a[x],b[x]);
                    a[x] = y;
                    add(a[x],b[x]);
                }
                else if (op==2) {
                    scanf("%d", &y);
                    del(a[x],b[x]);
                    b[x] = y;
                    add(a[x],b[x]);
                }
                else {
                    ll l = 1, r = 1e13, ans;
                    while (l<=r) {
                        ll mid = (l+r)/2;
                        if (calc(mid)>=x) ans=mid,r=mid-1;
                        else l=mid+1;
                    }
                    printf("%lld
    ", ans);
                }
            }
        }
    }
    View Code

    考虑二进制每位的贡献, 可以得到若$sumlimits_{i=1}^n( lfloorfrac{nspace modspace i}{2^k} floor-2lfloorfrac{nspace modspace i}{2^k} floor)$为奇数那么第$k$位贡献为$2^k$, 否则贡献为0

    乘$2$的项直接忽略, $sumlimits_{i=1}^n lfloorfrac{nspace modspace i}{2^k} floor=sumlimits_{i=1}^n lfloorfrac{n-lfloorfrac{n}{i} floor i}{2^k} floor$

    考虑整除分块, 转化为求

    $sumlimits_{t=i}^j lfloorfrac{n-lfloorfrac{n}{i} floor t}{2^k} floor=sumlimits_{t=0}^{j-i} lfloorfrac{n-lfloorfrac{n}{i} floor (t+i)}{2^k} floor=sumlimits_{t=0}^{j-i} lfloorfrac{nspace mod space j+lfloorfrac{n}{i} floor t}{2^k} floor$

    用类欧几里得即可

    2017 ccpc harbin

    2017 icpc hongkong

    A 高精预处理每个数的幂, 对个询问枚举$y$, 二分最优$x$

    B 简单线段树扫描线

    D floyd水题

    E 二分答案水题

    F 按题意模拟

    G dp求出后缀方案数, 然后从前往后递推贪心尽量取最大

    I

    2017 icpc nanning

    A 模拟

    F 答案是不超过n的2的幂, 高精度模拟一下

    #include <iostream>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    int len[200];
    char A[200],B[200],pw[200][55];
    int main() {
        pw[0][1] = len[0] = 1;
        for (int i=1; i<=170; ++i) {
            for (int j=1; j<=len[i-1]; ++j) pw[i][j] = pw[i-1][j]*2;
            len[i] = 51;
            for (int j=1; j<=len[i]; ++j) pw[i][j+1] += pw[i][j]/10, pw[i][j]%=10;
            while (!pw[i][len[i]]) --len[i];
        }
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%s", A+1);
            int n = strlen(A+1), cnt = 0;
            reverse(A+1,A+1+n);
            for (int i=1; i<=n; ++i) A[i] -= '0';
            while (n) {
                B[++cnt] = A[1]&1;
                for (int k=n,c=0; k; --k) {
                    int t = c*10+A[k];
                    A[k] = t>>1;
                    c = t&1;
                }
                while (n&&!A[n]) --n;
            }
            --cnt;
            for (int i=len[cnt]; i; --i) putchar(pw[cnt][i]+'0');
            puts("");
        }
    }
    View Code

    H 模拟

    I 暴力dfs

    J

    构造题, 多画一下找结论即可

    先把所有数模$3$, 合法情况满足任意两个$0$不相邻, $1$和$2$不相邻

    $0$的个数超过$n$无解, 否则若没有$1$或没有$2$一定有解

    同时有$1$和$2$的话一定要用$0$隔开, 可以如果$0$的个数不超过$1$, 那么一定无法隔开

    如果恰好有$2$个$0$, 那么必须有奇数个$1$或奇数个$2$才能隔开

    如果$0$的个数不少于$3$, 那么一定可以隔开

    可以发现合法数满足$f(n)=6f(n-1)-f(n-2)+2$, 高精度模拟

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <queue>
    using namespace std;
    vector<int> f[333];
    char s[222];
    int main() {
        f[1].push_back(3), f[2].push_back(0), f[2].push_back(2);
        for (int i=3; i<=300; ++i) {
            for (auto &t:f[i-1]) f[i].push_back(t*6);
            for (int j=0; j<f[i-2].size(); ++j) f[i][j] -= f[i-2][j];
            f[i][0] += 2;
            int sz = f[i].size();
            for (int j=0; j<sz; ++j) {
                int t = f[i][j]/10;
                f[i][j] %= 10;
                if (f[i][j]<0) f[i][j]+=10, --t;
                if (t) {
                    if (j==sz-1) ++sz, f[i].push_back(t);
                    else f[i][j+1] += t;
                }
            }
        }
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%s", s);
            int n = strlen(s);
            reverse(s,s+n);
            for (int i=0; i<n; ++i) s[i] -= '0';
            for (int i=1; i<=300; ++i) {
                if (f[i].size()<n) continue;
                if (f[i].size()==n) {
                    int ok = 1;
                    for (int j=n-1; j>=0; --j) {
                        if (f[i][j]>s[j]) break;
                        if (f[i][j]<s[j]) {
                            ok = 0;
                            break;
                        }
                    }
                    if (!ok) continue;
                }
                for (int j=f[i].size()-1; j>=0; --j) putchar(f[i][j]+'0');
                puts("");
                break;
            }
        }
    }
    View Code

    M 偏序集最长反链问题, floyd传递闭包后求最小路径覆盖

    2018 ccpc jilin

    A 简单分块

    B 简单模拟

    C

    $k_i>n$的可以先不考虑, $k_i<=n$的模拟高精加, 加到$frac{1}{2}$的时候不往上进位, 复杂度均摊是$O(n)$的

    输出方案可以用并查集

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e5+10;
    int n, fa[N];
    pair<int,int> a[N];
    vector<int> g[N];
    char ans[N];
    int Find(int x) {return fa[x]?fa[x]=Find(fa[x]):x;}
    void add(int x, int y) {
        x = Find(x), y = Find(y);
        if (x!=y) fa[x] = y;
    }
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d", &n);
            for (int i=1; i<=n; ++i) {
                scanf("%d", &a[i].first);
                a[i].second = i;
                fa[i] = 0;
                g[i].clear();
            }
            for (int i=1; i<=n; ++i) if (a[i].first<=n) {
                g[a[i].first].push_back(i);
                int now = a[i].first;
                while (now>1&&g[now].size()>=2) {
                    int x = g[now].back();
                    g[now].pop_back();
                    int y = g[now].back();
                    g[now].pop_back();
                    add(x, y);
                    g[--now].push_back(y);
                }
            }
            static int clk;
            printf("Case %d: ", ++clk);
            if (g[1].size()<2) puts("NO");
            else {
                puts("YES");
                for (int i=1; i<=n; ++i) {
                    if (Find(i)==g[1][0]) ans[i] = '1';
                    else ans[i] = '0';
                }
                ans[n+1] = 0;
                puts(ans+1);
            }
        }
    }
    View Code

    D

  • 相关阅读:
    java截取字符串
    Integer
    Sql语句常用关键字
    mybatis三种传值方式
    mybatis中的#和$的区别
    374. Guess Number Higher or Lower
    278. First Bad Version
    69. Sqrt(x)
    35. Search Insert Position
    167. Two Sum II
  • 原文地址:https://www.cnblogs.com/fs-es/p/13662472.html
Copyright © 2020-2023  润新知