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\not =0$时, 枚举以$i-1$结尾的极长连续砖块长度, 可以得到$f_i=\sum\limits_{\substack{0\le j<i \\ {sum}_{i-1}-{sum}_j=i-1-j}} f_j$

维护$g_{x} = \sum\limits_{\substack{0\le 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\n", 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\n", 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\n", 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\n", 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\n", 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 答案是$\lfloor\frac{n}{3}\rfloor$

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!"<<'\n',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<<'\n';
}
 
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^2\log{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\n", ++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\n", ++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}\sum\limits_{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 (\lfloor\frac{t}{a_i}\rfloor - \lfloor\frac{b_i}{a_i}\rfloor)$再减去$t\space mod \space a_i<b_i\space mod \space a_i$的个数

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

复杂度就是$O((n+m)\sqrt{a_i}+1000a_i\log{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\n", ans);
            }
        }
    }
}
View Code

考虑二进制每位的贡献, 可以得到若$\sum\limits_{i=1}^n( \lfloor\frac{n\space mod\space i}{2^k}\rfloor-2\lfloor\frac{n\space mod\space i}{2^k}\rfloor)$为奇数那么第$k$位贡献为$2^k$, 否则贡献为0

乘$2$的项直接忽略, $\sum\limits_{i=1}^n \lfloor\frac{n\space mod\space i}{2^k}\rfloor=\sum\limits_{i=1}^n \lfloor\frac{n-\lfloor\frac{n}{i}\rfloor i}{2^k}\rfloor$

考虑整除分块, 转化为求

$\sum\limits_{t=i}^j \lfloor\frac{n-\lfloor\frac{n}{i}\rfloor t}{2^k}\rfloor=\sum\limits_{t=0}^{j-i} \lfloor\frac{n-\lfloor\frac{n}{i}\rfloor (t+i)}{2^k}\rfloor=\sum\limits_{t=0}^{j-i} \lfloor\frac{n\space mod \space j+\lfloor\frac{n}{i}\rfloor t}{2^k}\rfloor$

用类欧几里得即可

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

 

posted @ 2020-09-18 22:14  dz8gk0j  阅读(222)  评论(0编辑  收藏  举报