2020牛客暑期多校训练营(第一场)
概要
| A | B | C | D | E | F | G | H | I | J | |
|---|---|---|---|---|---|---|---|---|---|---|
| ldx | √ | √ | √ | 🎈 | √ | √ | ||||
| wyc | √ | |||||||||
| yjs | 🎈 |
题目
A-B-Suffix Array
题意
对字符串t定义数组b,\(b[i]=\left\{ \begin{array}{rcl} min(i-j)& & {j<i\cap t_{i}=t_{j}}\\ 0 & & {else} \\ \end{array} \right.\),给定字符串s,s中只有字符a和b,将s的每个后缀的数组b按字典序从小到大排序。
思路
对字符串t定义数组c,\(c[i]=min_{j>i\cap t_{i}=t_{j}}{j-i}\),对c的后缀排序就相当于答案。
该结论只在字符串中有不多于两种字符时成立。
代码
点击展开
include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
char s[MAXN];
int c[MAXN];
namespace SA
{
int sa[MAXN], rk[MAXN], ht[MAXN], s[MAXN << 1], t[MAXN << 1], p[MAXN], cnt[MAXN], cur[MAXN];
#define pushS(x) sa[cur[s[x]]--] = x
#define pushL(x) sa[cur[s[x]]++] = x
#define inducedSort(v) fill_n(sa, n, -1); fill_n(cnt, m, 0);
for (int i = 0; i < n; i++) cnt[s[i]]++;
for (int i = 1; i < m; i++) cnt[i] += cnt[i-1];
for (int i = 0; i < m; i++) cur[i] = cnt[i]-1;
for (int i = n1-1; ~i; i--) pushS(v[i]);
for (int i = 1; i < m; i++) cur[i] = cnt[i-1];
for (int i = 0; i < n; i++) if (sa[i] > 0 && t[sa[i]-1]) pushL(sa[i]-1);
for (int i = 0; i < m; i++) cur[i] = cnt[i]-1;
for (int i = n-1; ~i; i--) if (sa[i] > 0 && !t[sa[i]-1]) pushS(sa[i]-1)
void sais(int n, int m, int *s, int *t, int *p)
{
int n1 = t[n - 1] = 0, ch = rk[0] = -1, *s1 = s + n;
for (int i = n - 2; ~i; i--) t[i] = s[i] == s[i + 1] ? t[i + 1] : s[i] > s[i + 1];
for (int i = 1; i < n; i++) rk[i] = t[i - 1] && !t[i] ? (p[n1] = i, n1++) : -1;
inducedSort(p);
for (int i = 0, x, y; i < n; i++)
if (~(x = rk[sa[i]]))
{
if (ch < 1 || p[x + 1] - p[x] != p[y + 1] - p[y]) ch++;
else
for (int j = p[x], k = p[y]; j <= p[x + 1]; j++, k++)
if ((s[j] << 1 | t[j]) != (s[k] << 1 | t[k]))
{
ch++;
break;
}
s1[y = x] = ch;
}
if (ch + 1 < n1) sais(n1, ch + 1, s1, t + n, p + n1);
else for (int i = 0; i < n1; i++) sa[s1[i]] = i;
for (int i = 0; i < n1; i++) s1[i] = p[sa[i]];
inducedSort(s1);
}
template
int mapCharToInt(int n, const T *str)
{
int m = *max_element(str, str + n);
fill_n(rk, m + 1, 0);
for (int i = 0; i < n; i++) rk[str[i]] = 1;
for (int i = 0; i < m; i++) rk[i + 1] += rk[i];
for (int i = 0; i < n; i++) s[i] = rk[str[i]] - 1;
return rk[m];
}
template
void suffixArray(int n, const T *str)
{
int m = mapCharToInt(++n, str);
sais(n, m, s, t, p);
for (int i = 0; i < n; i++) rk[sa[i]] = i;
for (int i = 0, h = ht[0] = 0; i < n - 1; i++)
{
int j = sa[rk[i] - 1];
while (i + h < n && j + h < n && s[i + h] == s[j + h]) h++;
if (ht[rk[i]] = h) h--;
}
}
};
int main()
{
while (scanf("%d", &n) != EOF)
{
scanf("%s", s);
n = strlen(s);
int _a = n + 1, _b = n + 1;
for (int i = n - 1; i >= 0; --i)
{
if (s[i] == 'a')
{
if (_a == n + 1)
c[i] = n;
else
c[i] = _a - i;
_a = i;
}
else
{
if (_b == n + 1)
c[i] = n;
else
c[i] = _b - i;
_b = i;
}
}
c[n] = n + 1;
c[++n] = 0;
SA::suffixArray(n, c);
for (int i = n - 1; i >= 1; --i)
printf("%d ", SA::sa[i] + 1);
printf("\n");
}
return 0;
}
注意事项
倍增常数大,使用需谨慎。
B-Infinite Tree
题意
给定一颗树,树的结点是所有正整数,设\(mindiv(n)\)是正整数n的最小质因子,那么\(n\)和\(mindiv(n)\)之间有边,边权为一,且\(mindiv(n)\)是\(n\)的父亲。
我们设\(u,v\)之间的边权和为\(d(u,v)\)。
接下来给定一个\(m\),并给出\(m\)个正整数。\(w_1,w_2, ... w_m\),求\(\min_u\sum_{i=1}^m{w_i}d(u,i!)\)。
思路
显然满足要求的结点\(u\)必然是在包含\(1!,2!, ... m!\)这\(m\)个关键节点的原树的虚树上。所以我们只要构建出这颗虚树,就可以在树上求出答案。
构建虚树的过程中,因为在这棵树上\(1!\)到\(m!\)的\(dfs\)序是从小到大的。所以可以省去按\(dfs\)序对节点排序的过程。构建过程中我们可以通过求出\(dep[lca(u,v)]\)来代替\(lca(u,v)\)。
考虑如何求解\(dep[lca((u-1)!,u!)]\),由于每次都是乘上了一个数,我们先对这个数进行质因子分解(\(O(log(n))\))。那么相邻两个阶乘的\(dep[lca((u-1)!,u!)]\)就是\((u-1)!\)的所有质因子中大于等于\(maxdiv(u)\)(\(u\)的最大质因子)的质因子个数,我们可以用树状数组维护一下当前所有质因子的个数的前缀和。
建出虚树之后,问题就十分简单了,只要求出\(u\)是根节点时的答案,然后\(dfs\),\(O(1)\)转移,求出到相邻节点的答案,\(O(n)\)求出所有答案,取最小值即可。
代码
点击展开
include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
vector<pair<int,int> > g[maxn];
ll val[maxn],ans[maxn],siz[maxn];
int n,p[maxn],tot,minp[maxn],maxp[maxn],bit[maxn],dep[maxn],a[maxn],stk[maxn],node[maxn];
bool flag[maxn];
void add(int p,int v){
for(int i = p; i <= n; i += i&(-i))
bit[i] += v;
}
int query(int x){
int sum = 0;
for(; x; x -= x&(-x)) sum += bit[x];
return sum;
}
void sieve(int n){
flag[1] = true;
for(int i = 2; i <= n; i++){
if(!flag[i]) p[++tot] = i,maxp[i] = minp[i] = i;
for(int j = 1; j <= tot&&ip[j] <= n; j++){
flag[ip[j]] = true,minp[ip[j]] = p[j],maxp[ip[j]] = maxp[i];
if(i%p[j]0) break;
}
}
}
int cur_node,top;
void virtual_tree(int n){
cur_node = 0,top = 0;
for(int i = 1; i <= n2; i++) g[i].clear();
for(int i = 1; i <= n2; i++) ans[i] = siz[i] = val[i] = dep[i] = stk[i] = bit[i] = 0;
stk[++top] = ++cur_node;
val[1] = a[1];
node[1] = 1;
for(int i = 2; i <= n; i++){
int lca_dep = query(n)-query(maxp[i]-1);
int t = i;
for(;😉{
add(minp[t],1);
if(tminp[t]) break;
t /= minp[t];
}
if(lca_dep^dep[stk[top]]){
while(top>1&&dep[stk[top-1]]>=lca_dep){
g[stk[top-1]].push_back({stk[top],dep[stk[top]]-dep[stk[top-1]]}),top--;
}
if(dep[stk[top]]^lca_dep){
g[++cur_node].push_back({stk[top],dep[stk[top]]-lca_dep}),stk[top] = cur_node;
dep[cur_node] = lca_dep;
}
}
stk[++top] = ++cur_node;
val[cur_node] = a[i];
node[cur_node] = i;
siz[cur_node] = 1;
dep[cur_node] = query(n);
}
for(int i = 1; i < top; i++) g[stk[i]].push_back({stk[i+1],dep[stk[i+1]]-dep[stk[i]]});
}
void dfs1(int u){
for(auto pi:g[u]){
int v = pi.first,w = pi.second;
dfs1(v);
val[u] += val[v];
}
}
void dfs(int u){
for(auto pi:g[u]){
int v = pi.first,w = pi.second;
ans[v] = ans[u]+1ll(val[1]-2val[v])*w;
dfs(v);
}
}
int main(){
sieve(200000);
while(~scanf("%d",&n)){
for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
virtual_tree(n);
for(int i = 1; i <= cur_node; i++) ans[1] += 1llval[i]dep[i];
//printf("%lld\n",ans[1]);
dfs1(1),dfs(1);
//for(int i = 1; i <= cur_node; i++) printf("ans[%d] = %lld\n",i,ans[i]);
for(int i = 2; i <= cur_node; i++) ans[i] = min(ans[i],ans[i-1]);
printf("%lld\n",ans[cur_node]);
}
return 0;
}
E-Counting Spanning Trees
题意
给定一张连通二分图G,左边每个点\(u_{i}\)都与右边的点\(v_{1}...v_{j}\)相连,求其生成树个数。
思路
论文题,设\(u_{i}\)点的度数为\(\lambda_{i}\),\(v_{i}\)点的度数为\(\theta_{i}\),且\(\lambda_{i}\geq\lambda_{i+1}\),\(\theta_{i}\geq\theta_{i+1}\)。
则生成树个数\(\tau(G)=\prod_{i=2}^{n}\lambda_{i}\cdot \prod_{j=2}^{m}\theta_{j}\)。
代码
点击展开
include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, mod, ans;
ll l[100005], r[100005];
int main()
{
while (scanf("%lld%lld%lld", &n, &m, &mod) != EOF)
{
for (int i = 0; i <= m; ++i)
r[i] = 0;
for (int i = 1; i <= n; ++i)
{
scanf("%lld", &l[i]);
r[1]++;
r[l[i] + 1]--;
}
for (int i = 1; i <= m; ++i)
r[i] += r[i - 1];
sort(l + 1, l + n + 1);
sort(r + 1, r + m + 1);
ans = l[n] == m ? 1 : 0;
for (int i = 1; i < n; ++i)
ans = ans * l[i] % mod;
for (int i = 1; i < m; ++i)
ans = ans * r[i] % mod;
printf("%lld\n", ans);
}
return 0;
}
F-Infinite String Comparision
题意
给两个字符串s和t,问两个字符串循环无数次之后的字典序大小关系。
思路
赛中思路是kmp判一下是否一个是另一个的循环节,否则循环比较即可。
赛后可能是数据太水,把自己ac代码hack了,代码借鉴了一下别人的写法,很妙。
代码
点击展开
include<bits/stdc++.h>
using namespace std;
int main()
{
string s, t, st, ts;
while (cin >> s >> t)
{
st = s + t;
ts = t + s;
puts((st < ts) ? "<" : (st > ts) ? ">" : "=");
}
return 0;
}
注意事项
kmp找循环节可能是不完全的循环节。
H-Minimum-cost Flow
题意
给一个有n个点m条有向边的网络G,每条边都有花费。
q次询问,每次询问将所有边的流量改为\(\frac{u}{v}\)时,从1跑到n流出1点流量的最小费用。
思路
我们可以将问题放大v倍,即所有边的流量为u时,流出v点流量的最小费用。
随后在建图时,我们可以将每条边的流量设为1,跑费用流时,记录下每次新增增广路的费用。
将费用从小到大排序,当询问将流量改为u时,从小到大将每条增广路跑满,直到流出v点流量,花费的费用就是答案。
代码
点击展开
include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 105;
const int MAXM = 205;
const ll INF = 0x3f3f3f3f3f3f3f3f;
struct Edge
{
ll to, nxt, weight, cost;
} edge[MAXM << 1];
ll head[MAXN], tot = 1;
void addedge(ll u, ll v, ll w, ll c)
{
edge[++tot].to = v;
edge[tot].weight = w;
edge[tot].cost = c;
edge[tot].nxt = head[u];
head[u] = tot;
}
struct Node
{
ll v, w;
bool operator<(const Node &x) const
{
return w > x.w;
}
};
ll n, m, s, t, maxflow, mincost;
ll pre[MAXN], flow[MAXN], track[MAXN], dis[MAXN], vis[MAXN], h[MAXN];
vector<pair<ll, ll>> f;
ll dijkstra()
{
for (int i = 1; i <= n; ++i)
{
pre[i] = vis[i] = 0;
dis[i] = INF;
}
flow[s] = INF;
pre[s] = s;
dis[s] = 0;
priority_queue
pq.push({s, 0});
while (!pq.empty())
{
int cur = pq.top().v;
pq.pop();
if (vis[cur])
continue;
vis[cur] = 1;
for (int i = head[cur]; i; i = edge[i].nxt)
{
ll nx = edge[i].to, w = edge[i].weight;
ll c = edge[i].cost + h[cur] - h[nx];
if (w > 0 && !vis[nx] && dis[nx] > dis[cur] + c)
{
dis[nx] = dis[cur] + c;
pre[nx] = cur;
flow[nx] = min(w, flow[cur]);
track[nx] = i;
pq.push({nx, dis[nx]});
}
}
}
return dis[t] != INF;
}
void MCMF()
{
while (dijkstra())
{
for (int i = 1; i <= n; ++i)
h[i] += dis[i];
maxflow += flow[t];
mincost += flow[t] * h[t];
f.push_back({flow[t], flow[t] * h[t]});
ll cur = t;
while (cur != s)
{
edge[track[cur]].weight -= flow[t];
edge[track[cur] ^ 1].weight += flow[t];
cur = pre[cur];
}
}
}
int main()
{
while (scanf("%lld%lld", &n, &m) != EOF)
{
f.clear();
maxflow = mincost = 0;
tot = 1;
memset(head, 0, sizeof(head));
memset(h, 0, sizeof(h));
memset(flow, 0, sizeof(flow));
memset(track, 0, sizeof(track));
s = n + 1;
t = n;
n++;
for (ll i = 1, u, v, c; i <= m; ++i)
{
scanf("%lld%lld%lld", &u, &v, &c);
addedge(u, v, 1, c);
addedge(v, u, 0, -c);
}
addedge(s, 1, INF, 0);
addedge(1, s, 0, 0);
MCMF();
sort(f.begin(), f.end());
ll q;
scanf("%lld", &q);
for (ll i = 1, u, v; i <= q; ++i)
{
scanf("%lld%lld", &u, &v);
if (v > u * maxflow)
{
printf("NaN\n");
continue;
}
ll mc = 0, F = v;
for (auto j:f)
{
if (u * j.first <= F)
{
mc += u * j.second;
F -= u * j.first;
}
else
{
mc += F * j.second;
break;
}
}
ll gcd = __gcd(mc, v);
printf("%lld/%lld\n", mc / gcd, v / gcd);
}
}
return 0;
}
注意事项
比赛时不要着急,代码检查确定后再提交,改正后还错误时,尝试重构代码,不要在错误的代码上反复修改
I-1 or 2
题意
给n个点m条边的图,能否选择一些边使得第i个点的度为\(d_{i}\)。
思路
一般图匹配,对于度为2的点,建图时建两个点,当一条边连接的两个点的度均为2时,为了避免重复匹配这条边,可以通过下图的方法建图。

代码
点击展开
include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
int n, m;
int q[MAXN], ql, qr;
int tim, ans;
int col[MAXN], pre[MAXN], mat[MAXN], fa[MAXN], vis[MAXN];
vector
int find(int x)
{
while (x != fa[x])
x = fa[x] = fa[fa[x]];
return x;
}
int lca(int x, int y)
{
x = find(x);
y = find(y);
for (tim++;; swap(x, y))
if (x)
{
if (vis[x] == tim)
return x;
vis[x] = tim;
if (mat[x])
x = find(pre[mat[x]]);
else
x = 0;
}
}
void flower(int x, int y, int r)
{
while (find(x) != r)
{
pre[x] = y;
if (col[mat[x]] == 1)
col[q[qr++] = mat[x]] = 0;
if (find(x) == x)
fa[x] = r;
if (find(mat[x]) == mat[x])
fa[mat[x]] = r;
y = mat[x];
x = pre[y];
}
}
bool match(int s)
{
memset(col, -1, sizeof(col));
memset(pre, 0, sizeof(pre));
ql = 0;
qr = 1;
q[ql] = s;
col[s] = 0;
for (int i = 1; i <= n; ++i)
fa[i] = i;
while (ql != qr)
{
int x = q[ql++];
for (auto y:adj[x])
{
if (!~col[y])
{
col[y] = 1;
pre[y] = x;
if (!mat[y])
{
for (int lst; x; y = lst, x = pre[y])
{
lst = mat[x];
mat[x] = y;
mat[y] = x;
}
return true;
}
col[q[qr++] = mat[y]] = 0;
}
else if (!col[y] && find(x) != find(y))
{
int l = lca(x, y);
flower(x, y, l);
flower(y, x, l);
}
}
}
return false;
}
int d[MAXN], two[MAXN];
int main()
{
while (scanf("%d%d", &n, &m) != EOF)
{
for (int i = 1; i <= 500; ++i)
adj[i].clear();
memset(d, 0, sizeof(d));
memset(two, 0, sizeof(two));
memset(mat, 0, sizeof(mat));
memset(vis, 0, sizeof(vis));
ans = tim = 0;
int sz = n, sum = 0;
for (int i = 1; i <= n; ++i)
{
scanf("%d", &d[i]);
sum += d[i];
if (d[i] == 2)
two[i] = ++sz;
}
for (int i = 1, u, v; i <= m; ++i)
{
scanf("%d%d", &u, &v);
if (d[u] == 1 && d[v] == 1)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
else if (d[u] == 1 && d[v] == 2)
{
adj[u].push_back(v);
adj[v].push_back(u);
adj[u].push_back(two[v]);
adj[two[v]].push_back(u);
}
else if (d[u] == 2 && d[v] == 1)
{
adj[u].push_back(v);
adj[v].push_back(u);
adj[v].push_back(two[u]);
adj[two[u]].push_back(v);
}
else
{
int a = ++sz;
int b = ++sz;
adj[u].push_back(a);
adj[a].push_back(u);
adj[two[u]].push_back(a);
adj[a].push_back(two[u]);
adj[v].push_back(b);
adj[b].push_back(v);
adj[two[v]].push_back(b);
adj[b].push_back(two[v]);
adj[a].push_back(b);
adj[b].push_back(a);
}
}
if (sum % 2 == 1)
{
printf("No\n");
continue;
}
n = sz;
for (int i = 1; i <= n; ++i)
if (!mat[i] && match(i))
ans++;
if (ans * 2 == sz)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
J-Easy Integration
题意
求 \(\int_0^1(x-x^2)^ndx\)
思路
易得$$\int_01(x-x2)ndx=\frac{\Gamma(n+1)2}{\Gamma(2n+2)}$$
代码
点击展开
include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
ll fac[2000020];
void init()
{
fac[1]=1;
for(int i=2;i<=2000010;i++)
fac[i]=fac[i-1]i%mod;
}
ll qpow(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
ans = (x * ans) % mod;
x = (x * x) % mod;
y >>= 1;
}
return ans;
}
int main()
{
int n;
init();
while(~scanf("%d",&n))
{
ll ans=fac[n]fac[n]%modqpow(fac[2n+1],mod-2)%mod;
printf("%lld\n",ans);
}
return 0;
}

浙公网安备 33010602011771号