练
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]); }
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);
}
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); } }
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(); }
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); } } } }
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<<' '; }
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(); }
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); } }
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); } }
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(""); } } }
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); } } } }
L
考虑二进制每位的贡献, 可以得到若$\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(""); } }
H 模拟
I 暴力dfs
J
构造题, 多画一下找结论即可
先把所有数模$3$, 合法情况满足任意两个$0$不相邻, $1$和$2$不相邻
$0$的个数超过$n$无解, 否则若没有$1$或没有$2$一定有解
同时有$1$和$2$的话一定要用$0$隔开, 可以如果$0$的个数不超过$1$, 那么一定无法隔开
如果恰好有$2$个$0$, 那么必须有奇数个$1$或奇数个$2$才能隔开
如果$0$的个数不少于$3$, 那么一定可以隔开
L 可以发现合法数满足$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; } } }
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); } } }
D