Codeforces Round 621[CF1307]

[链接](<http://codeforces.com/contest/1307)

Problem A

暴力贪心即可。

Problem B

显然一直用最大值跳,跳到总和大于 $x$ 就可以了。

注意特判最大值 $>x$ 的情况。

Problem C

显然出现次数最多时,该字符串的长度只为 $1$ 或 $2$。

前缀和计算一下就好了。

Problem D

一道好题。

题意

给 $n$ 个点 $m$ 条边的无向图。现在你需要在图中加上一条不是自环的边,使得最短路最大。这条边的两个顶点必须在给定的集合 $S$ 内。

$n,m\leq 2\times 10^5,|S|\leq n$

题解

分类讨论:

1,这条边无法影响添加前的答案,此时答案为 $dis_{1,n}$

2,这条能影响答案:

设 $f_i$ 表示点 $i$ 到 $1$ 的距离,$g_i$ 表示点 $i$ 到 $n$ 的距离。这个可以用BFS求出。

题目转换成,给 $|S|$ 个点,每个点有两个值 $f_i,g_i$ ,需要选两个点,使得 $\min(f_i+g_j,f_j+g_i)+1$ 最大。

不妨假设 $f_i+g_j\leq f_j+g_i$,亦即 $f_i-g_i\leq f_j-g_j$。

因此对于每个点,按 $f_i-g_i$ 从小到大排序。枚举点 $i$,求出前缀最大的 $g_j$即可。

时间复杂度 $O(n\log n+m)$。

程序

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
const int N=4e5+5;
int n,m,k;
int f[N],g[N],a[N];
vector<int> adj[N];
inline void add(int x,int y)
{
adj[x].pb(y); adj[y].pb(x);
}
queue<int> q;
void bfs(int s,int *dis)
{
fo(i,1,n) dis[i]=inf;
dis[s]=0;
for(q.push(s);!q.empty();)
{
int u=q.front(); q.pop();
for(auto v:adj[u])
if(dis[v]==inf)
dis[v]=dis[u]+1,q.push(v);
}
}
int ans,mx;
int main()
{
n=read(); m=read(); k=read();
fo(i,1,k) a[i]=read();
fo(i,1,m) add(read(),read());
bfs(1,f); bfs(n,g);
ans=0;
sort(a+1,a+k+1,[&](const int &x,const int &y){return f[x]-g[x]<f[y]-g[y];});
mx=f[a[1]];
fo(i,2,k)
{
ans=max(ans,mx+g[a[i]]);
mx=max(mx,f[a[i]]);
}
printf("%d",min(ans+1,f[n]));
return 0;
}

Problem E

题意

令一道好题。

$m$ 头奶牛,每头奶牛的类型为 $f_i$,能吃 $h_i$ 个草。$n$ 个草坪从左到右排列,每个草坪当中长了类型为 $g_i$ 的奶牛才能吃的一单位的草,被牛吃后不再长草。

奶牛从左或者从右边出发,一直吃下去,直到吃了 $h_i$ 个草后,睡在那个位置。

一种合法的吃草方案为安排一些奶牛吃草,每只奶牛走的时候不能跨过睡着的奶牛。

两种方案不同当且仅当存在某只奶牛在一种方案出现而在另一种方案不出现或者某只奶牛的出发点不同。

求出合法的吃草方案中,最多能选出多少奶牛,以及此时的情况总数对 $10^9+7$ 取模后的值。

$n,m\leq 5000$

题解

听说有 $O(n)$ 或 $O(n\log n)$ 做法。。。

显然的是,相同类型的奶牛只能最多放两只。

不妨试试枚举分界点 $k$。

从左边走的和从右边走的都不能跨过 $k$。然后对于每种奶牛的类型算方案数,相乘起来加进贡献里。

设 $l$ 为只能从左边走的奶牛数量,$r$ 为只能从右边走的奶牛数量,$b$ 为都能走的奶牛数量。

分类讨论一下:

  • 用两只奶牛走的方案数,$l\cdot b+r\cdot b+b\cdot (b-1)$。

  • 只用一只奶牛走的方案数,$l+r+2b$。

当 $l\cdot b+r\cdot b+b\cdot (b-1)>0$ 的时候就必须用两只。

暴力计算,时间复杂度 $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
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
const ll mod=1e9+7;
inline ll Add(ll x,ll y){x+=y; return (x<mod)?x:x-mod;}
inline ll Dec(ll x,ll y){x-=y; return (x<0)?x+mod:x;}
inline ll Mul(ll x,ll y){return x*y%mod;}
inline ll Pow(ll x,ll y)
{
y%=(mod-1);
ll ans=1;
for(;y;y>>=1,x=x*x%mod) if(y&1) ans=ans*x%mod;
return ans;
}
const int N=5003;
int n,m,w[N];
vector<int> f[N],L[N],R[N];

int main()
{
n=read(); m=read();
fo(i,1,n) w[i]=read();
int x;
fo(i,1,m) x=read(),f[x].pb(read());
f[0].pb(0); L[0].pb(0); R[0].pb(0);
fo(i,1,n)
if(f[i].size())
{
sort(all(f[i]));
int k,l;
k=0; l=0;
fo(j,1,n)
if(w[j]==i&&l<f[i].size())
{
k++;
if(f[i][l]==k) L[i].pb(j),l++;
}
fo(j,l,f[i].size()-1) L[i].pb(n+1);
k=0; l=0;
fd(j,n,1)
if(w[j]==i&&l<f[i].size())
{
k++;
if(f[i][l]==k) R[i].pb(j),l++;
}
fo(j,l,f[i].size()-1) R[i].pb(-1);
}
int siz=0; ll sum=1;
ll l,r,lr,s,tmp; int sz;
fo(x,0,n)
for(auto k:L[x])
if(k!=n+1)
{
sz=0; s=1;
fo(i,1,n)
if(f[i].size())
{
l=r=lr=0;
fo(j,0,f[i].size()-1)
{
if(L[i][j]<k&&R[i][j]>k) lr++;
else if(L[i][j]<=k) l++;//!!!
else if(R[i][j]>k) r++;
}
if(i==x) {r+=lr; l=lr=0;}
if((tmp=l*r+l*lr+r*lr+lr*(lr-1))>0) sz+=2,s=Mul(s,tmp%mod);
else if((tmp=l+r+lr*2)>0) sz++,s=Mul(s,tmp%mod);
}
if(x) sz++;
if(!sz) continue;
if(sz>siz) siz=sz,sum=0;
if(sz==siz) sum=Add(sum,s);
}
printf("%d %d",siz,sum);
return 0;
}

Problem F

题意

一棵树中,有些给定的点作为中转站,可以加满油。车的油箱容量为 $k$ ,初始是满的。每走一条边消耗 $1$。容量为 $0$ 时不能继续走。

$m$ 次询问,问是否能从 $u_i$ 走到 $v_i$。

$n,m\leq 2\times 10^5$

题解

记一个中转站 $i$ 向外走 $\leq \frac{k}{2}$ 步后(可以走到边上)形成的集合是 $S_i$,若 $S_i$ 和 $S_j$ 的交集不为空则两点间可以互相到达,能互相到达这个条件满足传递性,因此可以用并查集合并之。

每次询问的时候亦可以把起点与终点当做一个临时的中转站。

但是这个 $\frac{k}{2}$ 步比较麻烦,它可以走到一条边的中点上。因此在每条边的中点处多加一个点,变成走 $k$ 步即可。

这个走 $k$ 步可以用BFS实现。

那么首先判断一下两点间的距离是否 $\leq 2k$,如果是,直接输出’YES’就好了。

否则就都向对方跳 $k$ 步,然后判断跳完后两点是否在同一个集合内就可以了。

这个跳 $k$ 步可以用树剖或者倍增实现。

时间复杂度 $O(n\log 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
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <deque>
#include <cmath>
#include <bitset>
#include <cstdio>
#include <vector>
#include <string>
#include <complex>
#include <utility>
#include <cstring>
#include <iostream>
#include <assert.h>
#include <algorithm>
//#include <unordered_set>
//#include <unordered_map>
using namespace std;
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define ll long long
#define ull unsigned ll
#define db double
#define lb long db
#define pb push_back
#define com complex<db>
#define mp(x,y) make_pair((x),(y))
#define fi first
#define se second
#define all(x) (x).begin(),(x).end()
#define cle(x) memset(x,0,sizeof(x))
#define lowbit(x) ((x)&(-(x)))
#define bit(x,i) (((x)>>(i))&1)
#define fo(i,j,k) for(int i=(j),end_i=(k);i<=end_i;i++)
#define fd(i,j,k) for(int i=(j),end_i=(k);i>=end_i;i--)
inline int read()
{
int x=0; char ch=getchar(); bool f=0;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return f?-x:x;
}
const int N=4e5+10;
const int inf=1e9;
int n,anc[N],k,r,rt;
vector<int> adj[N];
int f[N],dis[N];
inline void add(int x,int y)
{
adj[x].pb(y); adj[y].pb(x);
}
int find(int x){return (f[x]==x)?x:f[x]=find(f[x]);}
inline void merge(int x,int y)
{
x=find(x); y=find(y);
if(x!=y) f[x]=y;
}
queue<int> q;
void bfs()
{
fo(i,1,n*2) f[i]=i,dis[i]=inf;
int x;
fo(i,1,r) x=read(),dis[x]=0,q.push(x);
for(;!q.empty();)
{
int u=q.front(); q.pop();
for(auto v:adj[u])
{
merge(u,v);
if(dis[v]==inf)
{
dis[v]=dis[u]+1;
if(dis[v]!=k) q.push(v);
}
}
}
}
int g[N][19],siz[N],dep[N],fa[N],son[N],top[N];
void dfs1(int u,int pre)
{
siz[u]=1; dep[u]=dep[pre]+1;
fa[u]=g[u][0]=pre;
for(int i=1;(1<<i)<=dep[u];i++) g[u][i]=g[g[u][i-1]][i-1];
for(auto v:adj[u]) if(v!=pre)
{
dfs1(v,u);
siz[u]+=siz[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;
if(son[u]) dfs2(son[u],tp);
for(auto v:adj[u]) if(!top[v]) dfs2(v,v);
}
inline int lca(int x,int y)
{
for(;top[x]!=top[y];x=fa[top[x]])
if(dep[top[x]]<dep[top[y]])
swap(x,y);
return dep[x]<dep[y]?x:y;
}
inline int jump(int x,int k)
{
for(int i=0;i<=18;i++)
if((1<<i)&k)
x=g[x][i];
return x;
}
inline int jump(int x,int y,int z)
{
if(k<=dep[x]-dep[z]) return jump(x,k);
else return jump(y,dep[x]+dep[y]-(dep[z]<<1)-k);
}

int main()
{
n=read(); k=read(); r=read();
int x,y,z,_x,_y;
fo(i,1,n-1)
{
x=read(); y=read();
add(x,i+n); add(i+n,y);
}
bfs();
dfs1(1,0); dfs2(1,1);
fo(_,1,read())
{
x=read(),y=read(),z=lca(x,y);
if(dep[x]+dep[y]-(dep[z]<<1)<=k*2) {puts("YES"); continue;}
_x=jump(x,y,z); _y=jump(y,x,z);
puts(find(_x)==find(_y)?"YES":"NO");
}
return 0;
}