2020牛客暑期多校训练营(第一场)题解及补题
比赛过程
这场比赛总的来说我们发挥的一般,F的签到不够快,J的公式也是靠OEIS推的。由于比赛是线上的,互相之间的交流也相对比较困难。总的来说我们能力上确实还有所欠缺,之间的配合和交流也有待加强。
题解
F
题意
给出两个串,各自首尾相接重复循环无限次,判断两者大小。
解法
官方的解法中,用到了Periodicity Lemma定理,即两个串s,t,若两者无限循环的前 ∣ s ∣ + ∣ t ∣ − g c d ( ∣ s ∣ , ∣ t ∣ ) |s|+|t|-gcd(|s|,|t|) ∣s∣+∣t∣−gcd(∣s∣,∣t∣)位都相同,则两个串相等。用这个定理的话这题就很裸了。另外似乎直接扩两倍也能得正解。
代码
#pragma region
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (long long i = a; i <= n; ++i)
#define per(i, a, n) for (long long i = n; i >= a; --i)
#define debug() W(" ");
template <class T>
void _R(T &x) { cin >> x; }
void _R(int &x) { scanf("%d", &x); }
void _R(int64_t &x) { scanf("%lld", &x); }
void _R(double &x) { scanf("%lf", &x); }
void _R(char &x) { x = getchar(); }
void _R(char *x) { scanf("%s", x); }
void R() {}
template <class T, class... U>
void R(T &head, U &... tail) { _R(head), R(tail...); }
template <class T>
void _W(const T &x) { cout << x; }
void _W(const int &x) { printf("%d", x); }
void _W(const int64_t &x) { printf("%lld", x); }
void _W(const double &x) { printf("%.16f", x); }
void _W(const char &x) { putchar(x); }
void _W(const char *x) { printf("%s", x); }
template <class T, class U>
void _W(const pair<T, U> &x) { _W(x.F), putchar(' '), _W(x.S); }
template <class T>
void _W(const vector<T> &x) {
for (auto i = x.begin(); i != x.end(); _W(*i++))
if (i != x.cbegin()) putchar(' ');
}
void W() {}
template <class T, class... U>
void W(const T &head, const U &... tail) { _W(head), putchar(sizeof...(tail) ? ' ' : '\n'), W(tail...); }
#define IO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
template <typename T>
void read(T &x) {
x = 0;
char ch = getchar();
ll f = 1;
while (!isdigit(ch)) {
if (ch == '-') f *= -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - 48;
ch = getchar();
}
x *= f;
}
#pragma endregion
const int maxn = 55;
int main() {
IO;
string s, t;
while (cin >> s >> t) {
s += s, t += t;
int flag = 0;
rep(i, 0, max(s.length(), t.length()) * 2 - 1) {
if (s[i % s.length()] != t[i % t.length()]) {
cout << (s[i % s.length()] < t[i % t.length()] ? "<\n" : ">\n");
flag = 1;
break;
}
}
if (!flag) cout << "=\n";
}
}
H
题意
给出一个n点m边的有向图,每条边都有各自费用 c o s t i cost_i costi ,给出 q q q 个询问,问当所有边流量设置为 u i / v i u_i/v_i ui/vi 之后,从 1 1 1 到 n n n 的流量为 1 1 1 的最小花费。
解法
首先,由于每条边的容量相同,所以每条增广路的最大容量相同(即为最大容量)。我们可以预处理出每条增广路的花费。然后对于每个询问,可以转化为每条路容量 u u u ,所需最大流 v v v , v = a ∗ u + b ( b < u ) v=a*u+b(b < u) v=a∗u+b(b<u) ,然后取预处理中前 a a a 条所有容量和第 a + 1 a+1 a+1 的 b b b 。
代码
#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include <bits/stdc++.h>
//#include <ext/pb_ds/assoc_container.hpp>
//#include <ext/pb_ds/tree_policy.hpp>
//#include <ext/pb_ds/priority_queue.hpp>
//using namespace __gnu_pbds;
using namespace std;
#define ll long long
#define ld long double
#define ull unsigned long long
#define mst(a, b) memset((a), (b), sizeof(a))
#define mp(a, b) make_pair(a, b)
#define pi acos(-1)
#define endl '\n'
#define pii pair<int, int>
#define pll pair<ll, ll>
#define pdd pair<double, double>
#define vi vector<int>
#define vl vector<ll>
#define pb push_back
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
#define lowbit(x) x &(-x)
#define all(x) (x).begin(), (x).end()
#define sf(x) scanf("%d", &x)
#define pf(x) printf("%d", x)
#define debug(x) cout << x << endl
#define mod(x) (x % mod + mod) % mod
template <typename T>
void read(T &x)
{
x = 0;
char ch = getchar();
ll f = 1;
while (!isdigit(ch))
{
if (ch == '-')
f *= -1;
ch = getchar();
}
while (isdigit(ch))
{
x = x * 10 + ch - 48;
ch = getchar();
}
x *= f;
}
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-8;
const int maxn = 2e5 + 7;
const int maxm = 1e5 + 7;
const int mod = 1e9 + 7;
#define IO ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//template<typename T> using ordered_set = tree<T,null_type,less<T>,rb_tree_tag,tree_order_statistics_node_update>;
struct edge
{
ll nex, v, cost, flow;
} edges[maxm];
ll head[maxn], cnt = 1;
int n, m;
map<ll, ll> mp;
void addedge(ll u, ll v, ll cost, ll flow)
{
edges[++cnt] = {head[u], v, cost, flow};
head[u] = cnt;
}
ll inque[maxn];
ll dis[maxn], flow[maxn], pre[maxn], last[maxn];
bool spfa(int s, int t)
{
queue<ll> q;
mst(dis, INF);
mst(flow, INF);
mst(inque, 0);
q.push(s);
inque[s] = 1;
dis[s] = 0;
pre[t] = -1;
while (!q.empty())
{
ll u = q.front();
q.pop();
inque[u] = 0;
for (ll i = head[u]; i; i = edges[i].nex)
{
ll v = edges[i].v;
if (dis[v] > dis[u] + edges[i].cost && edges[i].flow)
{
dis[v] = dis[u] + edges[i].cost;
pre[v] = u;
last[v] = i;
flow[v] = min(flow[u], edges[i].flow);
if (!inque[v])
{
q.push(v);
inque[v] = 1;
}
}
}
}
if (pre[t] != -1)
return true;
return false;
}
ll maxflow, mincost;
void mcmf(int s, int t)
{
maxflow = 0;
mincost = 0;
while (spfa(s, t))
{
int u = t;
maxflow += flow[t];
mincost += flow[t] * dis[t];
mp[flow[t] * dis[t]] += flow[t];
while (u != s)
{
edges[last[u]].flow -= flow[t];
edges[last[u] ^ 1].flow += flow[t];
u = pre[u];
}
}
return;
}
int main()
{
while (scanf("%lld%lld",&n,&m)!=EOF)
{
mst(head, 0);
cnt = 1;
mp.clear();
mst(last, 0);
ll u, v, c;
for (int i = 1; i <= m; ++i)
{
scanf("%lld%lld%lld",&u,&v,&c);
addedge(u, v, c, 1);
addedge(v, u, -c, 0);
}
int q;
mcmf(1, n);
scanf("%d",&q);
while (q--)
{
scanf("%lld%lld",&u,&v);
ll x = 0, y = v;
if (maxflow * u < v)
printf("NaN\n");
else
{
for (auto &i : mp)
{
if (v > i.second * u)
{
v -= i.second * u;
x += i.first * u;
}
else
{
x += i.first * v / i.second;
break;
}
}
ll g = __gcd(x, y);
printf("%lld/%lld\n",x/g,y/g);
}
}
}
return 0;
}
I
题意
给出n个点和m条边组成的无向图,无自环,无重边。问是否能通过删边使得每个点的度为所要求 d i d_i di。
解法
现场写的时候,首先记录既有图的每个点的度 i n [ i ] in[i] in[i],然后可以发现若 i n [ i ] < d [ i ] in[i] < d[i] in[i]<d[i] 无解,若 i n [ i ] = d [ i ] in[i]=d[i] in[i]=d[i] 则此点的边不变,由此,这些点不在我们决策范围内,可以建立一个只包含可删边的点的子图,而后由一般图的匹配来做,没想到的是搜一般图匹配算法板子的时候找到原题了。网络流听说是个假算法,那就不补网络流方法了。
代码
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
using namespace std;
const int maxn = 600;
bool vis[maxn], flag1[maxn], flag2[maxn];
int match[maxn], fa[maxn], base[maxn], Q[maxn], adj[maxn][maxn];
int n, head, tail, start, finish;
inline void Contract(int x, int y) {
memset(vis, 0, sizeof(vis));
memset(flag1, 0, sizeof(flag1));
int lca, i;
for (i = x; i; i = fa[match[i]]) {
i = base[i];
vis[i] = 1;
}
for (i = y; i; i = fa[match[i]]) {
i = base[i];
if (vis[i]) {
lca = i;
break;
}
}
for (i = x; base[i] != lca; i = fa[match[i]]) {
if (base[fa[match[i]]] != lca) fa[fa[match[i]]] = match[i];
flag1[base[i]] = 1;
flag1[base[match[i]]] = 1;
}
for (i = y; base[i] != lca; i = fa[match[i]]) {
if (base[fa[match[i]]] != lca) fa[fa[match[i]]] = match[i];
flag1[base[i]] = 1;
flag1[base[match[i]]] = 1;
}
if (base[x] != lca) fa[x] = y;
if (base[y] != lca) fa[y] = x;
for (i = 1; i <= n; ++i) {
if (flag1[base[i]]) {
base[i] = lca;
if (!flag2[i]) {
Q[++tail] = i;
flag2[i] = 1;
}
}
}
}
inline void Change() {
int x, y, z;
z = finish;
while (z) {
y = fa[z];
x = match[y];
match[y] = z;
match[z] = y;
z = x;
}
}
inline void FindAugmentPath() {
memset(fa, 0, sizeof(fa));
memset(flag2, 0, sizeof(flag2));
for (int i = 1; i <= n; ++i) base[i] = i;
head = 0;
tail = 1;
Q[1] = start;
flag2[start] = 1;
while (head != tail) {
int x = Q[++head];
for (int y = 1; y <= n; ++y) {
if (adj[x][y] && base[x] != base[y] && match[x] != y)
if (start == y || match[y] && fa[match[y]])
Contract(x, y);
else if (!fa[y]) {
fa[y] = x;
if (match[y]) {
Q[++tail] = match[y];
flag2[match[y]] = 1;
} else {
finish = y;
Change();
return;
}
}
}
}
}
int deg[maxn], D[maxn], M, N;
pair<int, int> edge[maxn], id[maxn];
int main() {
while (~scanf("%d%d", &N, &M)) {
memset(deg, 0, sizeof(deg));
memset(adj, 0, sizeof(adj));
for (int i = 0; i < N; ++i) {
scanf("%d", &D[i]);
}
for (int i = 0; i < M; ++i) {
int u, v;
scanf("%d%d", &u, &v);
u--;
v--;
edge[i] = make_pair(u, v);
deg[u]++;
deg[v]++;
}
bool flag = true;
int cnt = 1;
for (int i = 0; i < maxn; ++i) id[i] = make_pair(-1, -1);
for (int i = 0; i < N; ++i)
if (deg[i] < D[i]) {
flag = false;
break;
}
if (!flag) {
puts("No");
continue;
}
for (int i = 0; i < M; ++i) {
int u = edge[i].first;
int v = edge[i].second;
if (id[u].first == -1) {
id[u] = make_pair(cnt, cnt + deg[u] - D[u] - 1);
cnt += (deg[u] - D[u]);
}
if (id[v].first == -1) {
id[v] = make_pair(cnt, cnt + deg[v] - D[v] - 1);
cnt += (deg[v] - D[v]);
}
if (id[N + i].first == -1) {
id[N + i] = make_pair(cnt, cnt + 1);
cnt += 2;
}
int t = id[N + i].first;
adj[t][t + 1] = adj[t + 1][t] = true;
for (int j = id[u].first; j <= id[u].second; ++j)
adj[t][j] = adj[j][t] = true;
for (int j = id[v].first; j <= id[v].second; ++j)
adj[t + 1][j] = adj[j][t + 1] = true;
}
int j, sum = 0;
n = cnt - 1;
flag = 1;
memset(match, 0, sizeof(match));
for (start = 1; start <= n; ++start)
if (match[start] == 0) FindAugmentPath();
for (int i = 1; i <= n; ++i) {
if (!match[i]) {
flag = 0;
break;
}
}
if (flag)
puts("Yes");
else
puts("No");
}
}
J
题意
对于指定的正整数 n n n,求 ∫ 0 1 ( x − x 2 ) n d x \int_{0}^{1} (x-x^2)^n dx ∫01(x−x2)ndx。
解法
无脑OEIS法:首先二项展开,然后化简得到
∑
r
=
0
n
(
−
1
)
r
(
n
2
)
n
+
r
+
1
\sum\limits_{r=0}^{n} \frac{(-1)^r{n\choose 2}}{n+r+1}
r=0∑nn+r+1(−1)r(2n),然后随便算丢OEIS。
正解:分部积分。
代码
#include <stdio.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
const ll maxn = 2e6 + 111;
const ll mod=998244353;
ll pre[maxn];
ll qm(ll a,ll b){
ll ret=1,cnt=a;
while(b){
if(b&1){
ret=ret*cnt%mod;
}
cnt=cnt*cnt%mod;
b>>=1;
}
return ret;
}
int main() {
pre[1]=1;
for(ll i=2;i<=2000010;++i){
pre[i]=pre[i-1]*i%mod;
}
ll n;
while(~scanf("%lld",&n)){
printf("%lld\n",(pre[n]*pre[n]%mod*qm(pre[2*n+1],mod-2))%mod);
}
return 0;
}