隐藏
2020牛客暑期多校训练营(第五场)解题报告 | Bill Yang's Blog

路终会有尽头,但视野总能看到更远的地方。

0%

2020牛客暑期多校训练营(第五场)解题报告

这场打得不错,拿了39名。

A.Portal

题目描述

You are now in a big factory. The factory could be recognized as a graph with n vertices and m edges. Every edge has its length. You have k missions to do. The i-th mission is going to vertex $a_i$ , picking a block and then sending it to vertex $b_i$ . You should complete the missions in the order from 1-st to k-th. Initially you are standing at vectex 1.

You have a gun in your hand. When you are at some vertex u, you could shoot the gun at the ground, and then a portal will be built at vertex u. When there are two portals in the factory, assuming they are at u and v, you could transfer between u and v with no cost(just like an edge connecting u and v with length 0).

You also have a remote controller in your hand. It allows you to close a portal whenever you want and wherever you are(closing one portal at a time, not all portals at the same time). What’s more, there could be at most two existing portals. So if you want to create another portal when there exists two, you must use your controller to close one before you create.

You need to find the minimum distance you have to walk in order to complete all k missions.

题目分析

第一眼看上去像一个np问题,然后再一想其实状态是可以被表示出来的。同时注意到性质:传送门只需要记录一个,因为另一个一定是你开始传送时脚下的结点。
于是状态可以被固定为$f(k,x)$,表示执行完第$k$个任务,传送门在$x$的最短路径。(其中当没有传送门的时候$x$为$0$,然后把每个任务拆分成两个任务)
考虑转移:(从$a$开始传送,传送到$b$,$x,y$是任务的起始点和终止点)

看上去似乎结束了(导致我们队多了3次罚时),但是还有一种情况:

枚举$a,b$,时间复杂度$O(kn^2)$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include<bits/stdc++.h>

using namespace std;

inline int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(!isdigit(x)) {if(x=='-')bj=-1;x=getchar();}
while(isdigit(x)) {num=num*10+x-'0';x=getchar();}
return num*bj;
}

#define mp make_pair
#define pii pair<int,int>

const int maxn=305;

typedef long long LL;

int n,m,k,a[maxn],b[maxn];
LL Map[maxn][maxn],f[maxn<<1][maxn];
vector<pii> mission;

int main() {
n=Get_Int();
m=Get_Int();
k=Get_Int();
memset(Map,0x0f,sizeof(Map));
for(int i=1; i<=n; i++)Map[i][i]=0;
for(int i=1; i<=m; i++) {
int x=Get_Int(),y=Get_Int();
LL v=Get_Int();
Map[x][y]=Map[y][x]=min(Map[x][y],v);
}
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)Map[i][j]=min(Map[i][j],Map[i][k]+Map[k][j]);
for(int i=1; i<=k; i++) {
a[i]=Get_Int();
b[i]=Get_Int();
}
mission.push_back(mp(1,a[1]));
for(int i=1; i<=k; i++) {
mission.push_back(mp(a[i],b[i]));
if(i<k)mission.push_back(mp(b[i],a[i+1]));
}
k=mission.size();
memset(f,0x0f,sizeof(f));
f[0][0]=0;
for(int i=1; i<=k; i++) {
int x=mission[i-1].first,y=mission[i-1].second;
long long now=0x0f0f0f0f0f0f0f0f;
for(int a=1; a<=n; a++) { //从a开始传送
for(int b=1; b<=n; b++) { //传送到b
f[i][b]=min(f[i][b],f[i-1][a]+Map[a][b]+Map[b][y]); //起点传送到a,在b留下传送门
f[i][a]=min(f[i][a],f[i-1][b]+Map[x][a]+Map[b][y]); //传送,不替换传送门
f[i][b]=min(f[i][b],f[i-1][b]+Map[x][a]+Map[b][y]); //传送,替换传送门
}
for(int b=0; b<=n; b++)f[i][a]=min(f[i][a],f[i-1][b]+Map[x][a]+Map[a][y]); //不传送,留下传送门
f[i][a]=min(f[i][a],f[i-1][a]+Map[x][y]); //不传送,不留下传送门
}
f[i][0]=f[i-1][0]+Map[x][y]; //没有传送门
}
LL ans=LLONG_MAX/2;
for(int i=0; i<=n; i++)ans=min(ans,f[k][i]);
printf("%lld\n",ans);
return 0;
}

B.Graph

题目描述

Mr. W got a new graph with N vertices and N - 1 edges. It’s a connected graph without cycles. Each edge should have an ugly value. To make the graph more beautiful, Mr. W hope you can help him modify it. You can delete or add one edge with an ugly value at a time and you can do it as many times as you want. But the following conditions should be satisfied at any time:

  1. The graph is connected.
  2. For each cycles in the graph, the XOR sum of all ugly values in the cycle is 0.

Mr. W wants to know the minimum sum of ugly values of all edges in the graph.

题目分析

套路题,考虑最小异或生成树,完全图每条边$(x,y)$边权为$d[x]\oplus d[y]$,这道题答案就是最小异或生成树的边权和。
那么如何用题目给出的边权求出$d[i]$呢?

  1. 求异或方程组
  2. $d[i]=i$到根的距离

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int _=2e5+7;
int n,a[_],ch[_*30][2],siz[_*30],cnt,col,rt[_*30];
ll ans;
vector<pair<int,int>> g[_];
std::vector<int> dsr[_*30];
void insert(int x) {
int p=0;
for(int i=29;i>=0;--i) {
bool k=x&(1LL<<i);
siz[p]++;
if(!ch[p][k]) ch[p][k]=++cnt;
p=ch[p][k];
} siz[p]++;
if(!rt[p]) rt[p]=++col;
dsr[rt[p]].push_back(x);
}
int query(int rt,int init,int x) {
int p=rt,ans=0;
for(int i=init;i>=0;--i) {
bool k=x&(1LL<<i);
if(ch[p][k]) p=ch[p][k];
else p=ch[p][!k],ans|=1<<i;
}
return ans;
}
void dfs(int p,int dep) {
if(!ch[p][0]&&!ch[p][1]) return;
if(!ch[p][0]||!ch[p][1]) {
dfs(ch[p][0]|ch[p][1],dep-1);
rt[p]=rt[ch[p][0]|ch[p][1]];
} else {
int k=siz[ch[p][0]]<siz[ch[p][1]];
dfs(ch[p][0],dep-1),dfs(ch[p][1],dep-1);
rt[p]=rt[ch[p][k]];
int tmp=0x3f3f3f3f;
int nb=ch[p][!k];
ch[p][!k]=0;
for(auto x:dsr[rt[nb]]) {
tmp=min(tmp,query(p,dep,x));
dsr[rt[p]].push_back(x);
}
ans+=tmp;
ch[p][!k]=nb;
}
}
void DFS(int u,int fa){
for(auto x:g[u]){
int w=x.second,v=x.first;
if(v^fa){
a[v]=a[u]^w;DFS(v,u);
}
}
}
int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
int main() {
// freopen("a.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<n;i++){
int u=read(),v=read(),x=read();
u++;v++;
g[u].push_back(make_pair(v,x));
g[v].push_back(make_pair(u,x));
}
DFS(1,0);
for(int i=1;i<=n;++i) insert(a[i]);
dfs(0,29);
cout<<ans;
return 0;
}

C.Eazy

题目描述

Mr. W is writing sequences. If he writes two positive integer sequences A and B with length K which satify $\sum_{i = 1}^{K} a_i = N ,\ \ \ \sum_{i = 1}^{K} b_i = M$, he will get $P = \prod_{i=1}^K min(a_i, b_i)$ points.
You want to know the sum of total points he can get in all possible sequences he can write.

题目分析

比赛时没想出来
这是一道生成函数题,考虑生成函数$g(x,y)=\sum\min(i,j)x^iy^j$
将生成函数表示为:

考虑生成函数$h(xy)=\sum_{k}kx^ky^k$

故$h(xy)=\frac{xy}{(1-xy)^2}$
所以

现在我们得到了$n=m=k=1$的时候的答案,也就是$g(x,y)$中$x^ny^m$的系数

根据生成函数的卷积,$k>1$的时候,答案就是

根据广义二项式定理有

考虑枚举$j=i+k$,可以得到$h(xy)$的系数,还剩下$x^{n-j}$的系数由$f(x)=\frac{1}{1-x}$提供,剩下$y^{m-j}$的系数由$f(y)=\frac{1}{1-y}$提供。故枚举一次$j$即可,时间复杂度$O(n)$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>

using namespace std;

inline int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(!isdigit(x)) {if(x=='-')bj=-1;x=getchar();}
while(isdigit(x)) {num=num*10+x-'0';x=getchar();}
return num*bj;
}

typedef long long LL;

const int maxn=1000005;
const LL mod=998244353;

LL fac[maxn],invf[maxn];

LL Quick_Pow(LL a,LL b) {
LL sum=1;
for(; b; b>>=1,a=a*a%mod)if(b&1)sum=sum*a%mod;
return sum;
}

LL inv(int x) {return Quick_Pow(x,mod-2);}

LL C(int n,int m) {return fac[n]*invf[m]%mod*invf[n-m]%mod;}

int main() {
fac[0]=1;
for(int i=1; i<maxn; i++)fac[i]=fac[i-1]*i%mod;
invf[maxn-1]=inv(fac[maxn-1]);
for(int i=maxn-2; i>=0; i--)invf[i]=invf[i+1]*(i+1)%mod;
int t=Get_Int();
while(t--) {
int n=Get_Int(),m=Get_Int(),k=Get_Int();
LL ans=0;
for(int i=k; i<=min(n,m); i++)ans=(ans+C(i-1,k-1)*C(n-i+k-1,k-1)%mod*C(m-i+k-1,k-1)%mod)%mod;
printf("%lld\n",ans);
}
return 0;
}

D.Drop Voicing

题目描述

Inaka composes music. Today’s arrangement includes a chord of nn notes that are pairwise distinct, represented by a permutation $p_{1 \dots n}$ of integers from $1$ to $n$ (inclusive) denoting the notes from the lowest to the highest.

Her friend, Miyako, sneaks in and plays a trick by altering the chord with the following two operations:

  • Drop-2: Take out the second highest note and move it to the lowest position, i.e. change the permutation to $p_{n-1}, p_1, p_2, \dots, p_{n-3}, p_{n-2}, p_n$.
  • Invert: Take out the lowest note and move it to the highest position, i.e. change the permutation to $p_2, p_3, \dots, p_{n-1}, p_n, p_1$.

Any number of consecutive Drop-2 operations is considered a multi-drop. Miyako would like to change the permutation to an ordered permutation, $1, 2, \dots, n$,in the fewest number of multi-drops possible. Please help her find the number of multi-drops needed.

题目分析

将序列变成一个环,两个连续的操作等价于:

  • 将最后一个数插入到另一个位置
  • 旋转序列构成的环

因为只计算第一种操作的数量,故再次等价于:

  • 将某一个数插入到另一个位置

先用$O(n)$枚举旋转次数,通过若干次插入使得原序列有序,最少的次数就是$n-LIS$
$O(n^2\log n)$即可解决问题,也可以$O(n^3)$或者$O(n^2)$解决问题(留给读者思考,滑稽)。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>
using namespace std;
int arr[505];
int pos[505];
bool vis[505];//对应的数是否已经放了
int s[505];
int LIS(vector <int> a) {
s[0] = 0;
int tail = 0;
for (int i=0;i<a.size();i++) {
if (a[i] > s[tail]) {
s[++tail] = a[i];
continue;
}
int p = upper_bound(s,s+tail,a[i]) - s;
s[p] = a[i];
}
return tail;
}
int main() {
int n;
scanf("%d",&n);
for (int i=1;i<=n;i++) {
scanf("%d",&arr[i]);
}
int ans = n;
for (int i=1;i<=n;i++) {
vector <int> tmp;
for (int j=i;j<=n;j++) tmp.push_back(arr[j]);
for (int j=1;j<i;j++) tmp.push_back(arr[j]);
ans = min(ans,n-LIS(tmp));
}
printf("%d\n",ans);
return 0;
}

D.Drop Voicing

题目描述

给定置换,求有多少排列可以通过这个置换变成顺序

题目分析

求所有环的lcm即可,需要高精度
最终答案只有几百位
怕超时可以统计质数个数然后启发式合并

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from collections import Counter
import functools
def dfs(x):
global vst,cnt,a
#vst[x] = True
#Size[cnt] += 1
while (not vst[x]):
vst[x] = True
Size[cnt] += 1
x = a[x]
#if (not vst[a[x]]):
# dfs(a[x])
n = int(input())
a = [int(0)] + list(map(int,input().split()))
Size = [int(0)] * (n + 1)
vst = [False] * (n + 1)
cnt = int(0)
#线性筛 begin
pri_pre = [int(0)] * (n + 1)
primes = []
pri = [int(0)] * (n + 1)
pri[1] = True
for i in range(2,n+1):
if not pri[i]:
primes.append(i)
for p in primes:
if i * p > n:
break
pri[i*p] = True
pri_pre[i*p] = p
if i % p == 0:
break
#线性筛 end
for i in range(1,n+1):
if (not vst[i]):
cnt += 1
dfs(i)
ans = []
for i in range(1,cnt+1):
cur = Size[i]
cur_cnt = Counter()
while (pri_pre[cur] != 0):
cur_cnt[pri_pre[cur]] += 1
cur //= pri_pre[cur]
if cur > 1:
cur_cnt[cur] += 1
if (len(cur_cnt)):
ans.append(cur_cnt)
def cmp(x,y):
return len(y) - len(x)
ans = sorted(ans,key=functools.cmp_to_key(cmp))
merge_ans = Counter()
for x in ans:
merge_ans |= x
result = 1
for p,pp in merge_ans.items():
result *= p ** pp
print(result)

F.DPS

题目描述

When you are playing multiplayer games, you may want to show that you are the MVP of your team — or blame the one always wandering and watching away from the storm center. Well, there’re statistics that show you preformance, but sometimes numbers speak softer than charts. Now, you’re hired to write a program that output ASCII art histograms showing damage dealt to enemies.
There are n players in the game. Given that the i-th player dealt $d_i$ damage to enemies where $\max_i d_i > 0$ is granted, you need to calculate the number $s_i$ of spaces in the bar by the following foluma:
$s_i = \lceil 50 \frac {d_i} {\max_i d_i} \rceil$
Instead of formal definition of bar description, we will give an example. For some player i whose $s_i = 7$ and $d_i = 777$, the bar is shown as:

1
2
3
+-------+
| |777
+-------+

Moreover, you have to mark the player with maximal damage dealt to enemies by replacing the last space into ‘*’. If there’re multiple maximum, mark all of them.
See samples for more ideas.

题目分析

签到题

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>

using namespace std;

inline int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(!isdigit(x)) {if(x=='-')bj=-1;x=getchar();}
while(isdigit(x)) {num=num*10+x-'0';x=getchar();}
return num*bj;
}

const int maxn=105;

int n,d[maxn];

int main() {
int Max=0;
n=Get_Int();
for(int i=1; i<=n; i++) {
d[i]=Get_Int();
Max=max(Max,d[i]);
}
for(int i=1; i<=n; i++) {
int s=ceil(50.0*d[i]/Max);
putchar('+');
for(int i=1; i<=s; i++)putchar('-');
putchar('+');
putchar('\n');
putchar('|');
for(int i=1; i<s; i++)putchar(' ');
if(d[i]==Max) {
if(s)putchar('*');
} else if(s) putchar(' ');
putchar('|');
printf("%d\n",d[i]);
putchar('+');
for(int i=1; i<=s; i++)putchar('-');
putchar('+');
putchar('\n');
}
return 0;
}

H.Interval

题目描述

Mr. W have a sequence A with length N.
$F(l,r) = A_l\&A_{l+1}\&…\&A_r$
Set $S(l,r) = \{ F(a,b)\ | min(l, r)\leq a \leq b \leq max(l, r) \}$
Mr.W makes Q queries. For each query he wants to know the size of S(L,R) for given L, R.
L, R will not be given directly. He will give you L’ and R’.
$L = (L’ \oplus lastans) \% N + 1$
$R = (R’ \oplus lastans) \% N + 1$
$\oplus$ means XOR.
Lastans donates the answer of last query. It’s zero at the beginning.

题目分析

固定$r$,考虑不同的$F(l,r)$最多只有$30n$个
使用map把这些不同的$F(l,r)$全部找出来,相同的保留$l$最大的
这些区间对比他大的区间都有$1$的贡献
接下来就是一个二维偏序问题,可以使用主席树处理

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include<bits/stdc++.h>

using namespace std;

inline int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(!isdigit(x)) {if(x=='-')bj=-1;x=getchar();}
while(isdigit(x)) {num=num*10+x-'0';x=getchar();}
return num*bj;
}

#define mp make_pair

const int maxt=20000005,maxn=100005;

int cnt=0;

struct President_Tree {
struct Tree {
int lson,rson,val;
Tree(int l=0,int r=0,int v=0):lson(l),rson(r),val(v) {}
} tree[maxt];
#define ls tree[index].lson
#define rs tree[index].rson
#define mid ((Left+Right)>>1)
void update(int &index,int Left,int Right,int pos,int val) {
tree[++cnt]=tree[index];
index=cnt;
tree[index].val+=val;
if(Left==Right)return;
if(pos<=mid)update(ls,Left,mid,pos,val);
else update(rs,mid+1,Right,pos,val);
}
int query(int index,int Left,int Right,int left,int right) {
if(Left>right||Right<left)return 0;
if(left<=Left&&right>=Right)return tree[index].val;
return query(ls,Left,mid,left,right)+query(rs,mid+1,Right,left,right);
}
} pt;

int n,root[maxn];

int main() {
map<int,int> last,Max;
n=Get_Int();
for(int i=1; i<=n; i++) {
map<int,int> now;
int x=Get_Int();
now[x]=i;
for(auto p:last)now[p.first&x]=max(now[p.first&x],p.second);
root[i]=root[i-1];
for(auto p:now) {
if(Max.count(p.first))pt.update(root[i],1,n,Max[p.first],-1);
Max[p.first]=p.second;
pt.update(root[i],1,n,p.second,1);
}
last=now;
}
int q=Get_Int(),lastans=0;
while(q--) {
int l=(Get_Int()^lastans)%n+1,r=(Get_Int()^lastans)%n+1;
if(l>r)swap(l,r);
lastans=pt.query(root[r],1,n,l,r);
printf("%d\n",lastans);
}
return 0;
}

I.Interval

题目描述

有一个无穷大的二维网格,每个格子可以是1、2或者3,每个1旁边要有一个2和3,要使1的占比最大,求最大占比:$\lim_{n\to\infty} \lim_{m\to\infty} \frac{f(n,m)}{n\cdot m}$。

题目分析

对于每一个点$(x,y)$,若$(x+y)\bmod3\neq0$,其上下左右必定有且仅有两个点$(x+dx,y+dy)$他们的$(x+dx+y+dy)\bmod3=0$,使它们其中有一个是$2$,有一个是$3$,那么所有$(x+y)\bmod3\neq0$的点都可以填$1$。(交错填$2,3$即可)
因为所有$2,3$不相邻,且每个$1$旁边有且仅有一个$2,3$,故这样的方案是合法的且是上界,故答案即为$\frac23$。

代码

1
2
3
4
5
6
7
8
#include<bits/stdc++.h>

using namespace std;

int main() {
puts("0.666667");
return 0;
}
姥爷们赏瓶冰阔落吧~