AtCoder Grand Contest 058 简要题解

从这里开始

Problem A Make it Zigzag

  考虑使 $1, 3, 5, 7, \cdots, 2n - 3$ 这些位置后三个中的最大值在中间,最后再处理一下最后两个位置就行了。

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 5;

int n;
int a[N];
vector<int> ans;

int maxp(int a, int b, int c) {
  if (c > max(a, b)) return 2;
  if (b > max(a, c)) return 1;
  return 0;
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= (n << 1); i++) {
    scanf("%d", a + i);
  }
  for (int i = 1; i <= (n << 1) - 2; i += 2) {
    int d = maxp(a[i], a[i + 1], a[i + 2]);
    if (d != 1) {
      ans.push_back(min(i + d, i + 1));
      swap(a[i + d], a[i + 1]);
    }
  }
  int n2 = n << 1;
  if (a[n2 - 1] > a[n2]) {
    ans.push_back(n2 - 1);
  }
  printf("%d\n", (signed) ans.size());
  for (auto x : ans) {
    printf("%d ", x);
  }
  return 0;
}

Problem B Adjacent Chmax

  考虑其实问题相当于一个较大数可以把较小数覆盖掉。

  依次考虑原序列每个数在最终序列中占了多长一段。容易发现只需要满足占的那一段里没有比它大的数就可以。

  然后就是个简单 dp。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend bool operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 5005;

template <typename T>
bool vmax(T& a, T b) {
  return a < b ? (a = b, true) : false;
}

int n;
int vis[N];
int a[N], fa[N];
Zi f[N], g[N];

void build(int l, int r, int v) {
  if (l > r) {
    return;
  }
  int mx = -1, mxp = 0;
  for (int i = l; i <= r; i++) {
    if (vmax(mx, a[i])) {
      mxp = i;
    }
  }
  fa[mxp] = v;
  build(l, mxp - 1, mxp);
  build(mxp + 1, r, mxp);
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", a + i);
  }
  a[0] = N, a[n + 1] = N;
  f[0] = 1;
  for (int i = 1; i <= n; i++) {
    int l = i, r = i;
    while (a[l - 1] < a[i]) l--;
    while (a[r + 1] < a[i]) r++;
    Zi s = 0;
    for (int j = l; j <= r; j++) {
      g[j] += (s += f[j - 1]);
    }
    for (int j = 0; j <= n; j++) {
      g[j] += f[j];
      f[j] = g[j];
      g[j] = 0;
    }
  }
  printf("%d\n", f[n].v);
  return 0;
}

Problem C Planar Tree

  差点不会这题,感觉好毒瘤啊。

  如果只有 1, 2 这个题瞎做就好了。

  注意到如果奇数和偶数都能连,就是只有 1, 2 的情况,可是 1, 4 不能连。考虑先把这两个烦人的东西干掉。

  考虑 $(1, 2)$,$(3, 4)$ 相邻的时候直接连起来,然后分别当做只有一个点 2 或者 3。假如原问题有解且不存在这条边,那么显然这条边可以加上去,然后环上随便删一条边就行了。因此原问题和新问题有解性等价。

  注意到如果有连续两个数是相同的,那么可以在其中 1 个连边的时候,两个一起连。考虑把这两个缩一起。和上面同样的方法证明两个问题有解性等价,只是可以加边的原因略有不同。

  现在问题变成 1 不和 2 相邻,4 不和 3 相邻,相邻的数不同,目标把所有的 1, 4 连到别的点上。可以发现,树上不跨过 4 的 $(1, 2)$ 相连会导致至少 1 个 $3$ 不能继续和 4 相连。(如果有跨过 4 的话就考虑这个 $(3, 4)$,这样处理下去可能会得到不跨过 2 的 $(3, 4)$ 相连,但和这个情形是相似的)这样的情况一定是 $(1, 3, 2)$,考虑把这个缩成 $2$,继续这个操作。所以每消去一个 1 就会减少一个 3,每消去一个 4 就会减少一个 2。

  如果不存在 $(1, 3, 2)$ 也不存 $(4, 2, 3)$,那么说明不存在 $(2, 3)$ 相邻的情况,此时可以得到 $4$ 的数量大于等于 $2$ 的数量, $3$ 的数量大于等于 $1$ 的数量(等于是因为可能都没有),此时显然不可能。

  相反如果满足 $2$ 的数量加上 $3$ 的数量大于 $1$ 的数量加上 $4$ 的数量,同时 1 的数量大于等于 3 的数量,4 的数量大于等于 2 的数量,因为每次两个数量和同时减少,所以不可能出现不存在 $(2, 3)$ 相邻的情形。因此只用处理一下,然后数数量就可以了。

Code

#include <bits/stdc++.h>
using namespace std;

const int N = 3e5 + 5;

int T, n;
int a[N], stk[N], cnt[5];

int merge(int x, int y) {
  if (x > y) swap(x, y);
  if (x == y) {
    return x;
  }
  if (x + 1 == y && x != 2) {
    return x == 1 ? y : x;
  }
  return 0;
}

void solve() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
     scanf("%d", a + i);
  }
  int pos = 1, top = 1;
  while (a[pos] == a[1]) pos++;
  rotate(a + 1, a + pos, a + n + 1);
  stk[top] = a[1];
  for (int i = 2, x; i <= n; i++) {
    x = merge(stk[top], a[i]);
    if (x) {
      stk[top] = x;
    } else {
      stk[++top] = a[i];
    }
  }
  int frt = 1, tmp;
  while (frt < top && (tmp = merge(stk[frt], stk[top]))) {
    stk[top] = tmp;
    frt++;
  }
  memset(cnt, 0, sizeof(cnt));
  for (int i = frt; i <= top; i++) {
    cnt[stk[i]]++;
  }
  if (cnt[4] <= cnt[2] && cnt[1] <= cnt[3] && cnt[1] + cnt[4] < cnt[2] + cnt[3]) {
    puts("Yes");
  } else {
    puts("No");
  }
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem D Yet Another ABC String

  这种题比那种无聊的猜结论题有意思多了。

  朴素容斥的话相当于硬点若干个位置,然后这个位置起始长度为 3 的子串不合法。有重叠的话不好处理,主要因为 3 种字符的数量有限制。

  考虑让硬点的位置后面一定不和它相连。就是 ABC 后面不是 A。相当于就是对每个这样连续的 ABCABCAB 末做一个子集容斥。

  然后就是个简单组合计数。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend bool operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 3e6 + 5;

int A, B, C, S;

Zi fac[N], _fac[N], pw2[N];

void init(int n) {
  fac[0] = 1;
  for (int i = 1; i <= n; i++) {
    fac[i] = fac[i - 1] * i;
  }
  _fac[n] = ~fac[n];
  for (int i = n; i; i--) {
    _fac[i - 1] = _fac[i] * i;
  }
  pw2[0] = 1;
  for (int i = 1; i <= n; i++) {
    pw2[i] = pw2[i - 1] * 2;
  }
}

void cswap(int& a, int& b) {
  if (a > b) {
    swap(a, b);
  }
}

Zi comb(int n, int m) {
  return n < m ? 0 : fac[n] * _fac[m] * _fac[n - m];
}

int main() {
  scanf("%d%d%d", &A, &B, &C);
  S = A + B + C;
  cswap(B, C), cswap(A, B), cswap(B, C);
  init(S);
  Zi ans = 0, tmp;
  for (int i = 0; i <= A; i++) {
    // the last one isn't included
    int rest = S - 3 * i;
    tmp = comb(rest + i - 1, i) * pw2[i] * fac[rest] * _fac[A - i] * _fac[B - i] * _fac[C - i];
    // the last one is included
    if (i) {
      tmp += comb(rest + i - 1, i - 1) * pw2[i - 1] * 3 * fac[rest] * _fac[A - i] * _fac[B - i] * _fac[C - i];
    }
    if (i & 1) {
      ans -= tmp;
    } else {
      ans += tmp;
    }
  }
  printf("%d\n", ans.v);
  return 0;
}

Problem E Nearer Permutation

  感觉这种题和码农题一样无聊,反正顺着思路做就完了,每步难度不大,就是思考的过程特别长。虽然也有可能是因为我菜。

  考虑怎么找出 $z$。

  首先考虑怎么算 $d(x, z)$,相当于将 $x$ 第一个位置上的数,在 $z$ 中改为 $1$,第二位置上的数,在 $z$ 中改为 $2$,然后求 $z'$ 的逆序对数。

  这个显然相当于 $i$ 在两个排列中出现的位置构成的有序二元组的逆序对数。

  那么现在有 2 列二元组,每列二元组只知道其中 1 个数,从小到大把 $1$ 到 $n$ 填进去,每次尽可能填较小的数对应的二元组。

  现在问题变成判定把剩下的填进来其中 1 列的逆序对数是否不超过另一列的逆序对数。

  可以发现,只有每对 $i$ 在 $x$ 和 $y$ 中出现的位置构成的有序二元组的逆序对会对差有贡献,并且贡献为 $+1$ 或者 $-1$。(其实这个逆序对其实就是 $i$ 在 $x$ 中出现的位置 $p_i$ 的逆序对)如果 $p_i > p_j (i < j)$ 并且 $(p_i, ?)$ 被先填上数,那么会产生 $+1$ 的差。(最终需要差小于等于 0)我们下面称违反一个逆序对 $(i, j) (i < j, p_i > p_j)$ 指先在 $(p_i, ?)$ 填数。

  因此我们可以至多违反逆序对总数除以 $2$ 向下取整个逆序对。

  然后很容易得到一个明确的求 $z$ 的做法:假设当前在确定第 $i$ 个位置上的数,找尽量小的 $j$ 满足上一条限制,然后填上 $(p_j, i)$。

  现在回到原问题。

  现在我们要填 $p_1, \cdots, p_n$,题目指定了上面做法填 $(p_i, ?)$ 的顺序。

  当我们填了一个 $(p_i, ?)$  且前面有没有填的 $(p_j, ?)$ 时相当于限制此时能违反的数量小于填 $(p_j, ?)$ 会违反的数量。考虑每次填的过程,允许违反的数量每次单调不增,后者至多减少 1。所以如果下一次填的位置 $(p_k, ?)$ 满足 $k < i$,那么意味着上面这个差是刚好变为 $0$,所以填完 $(p_k, ?)$ 后不能再违反任何逆序对。这之后只能每次填 $p$ 最小的没填的位置。

  我们将 $p_i$ 分为 4 类:

  • 第一类位置:满足 $i = z_i$
  • 第三类位置:第一个满足 $z_i > z_{i + 1}$ 的 $z_{i + 1}$
  • 第二类位置:设第三类位置为 $z_t$,那么在 $z_1, \cdots, z_{t - 1}$ 中满足 $z_i > i$ 的为第二类位置
  • 第四类位置:剩下的

  容易发现对于第一类位置没有限制。如果一个现在要填的位置 $p_i$,之前还有没有填的位置 $p_j$,那么一定满足 $p_i < p_j$,否则能违反的限制数一定满足能先填 $p_j$。因此在不考虑第三类位置的情况下,第二类位置的限制在处理出第四类位置的 $p$ 的相对顺序后就已知了。

  我们希望使得逆序对数减去 2 倍被违反的逆序对数为 $0$ 或者 $1$。我们先考虑这个东西的最小值。注意到每产生一个被违反的逆序对的时候对这个值的贡献为 $-1$。

  在不考虑第三类位置的时候,最优方案为第一类位置依次填 $n, n - 1, \cdots$,第一个第二类位置填能填最大的数(能违反的逆序对数到这里的时候最大值就是这个位置上的限制)剩下按顺序填最小的数。

  注意到你可以连续地把这个值加上 1,所以如果第三类位置在第一个第二类位置的后面就做完了。

  考虑第三类位置在所有第二类位置之前的情形。

  如果第三类位置填上后后面有 $x$ 个数比它小,那么到这个位置的时候允许违反的逆序对数也是 $x$。第三类位置对于倒数第 $i$ 个被填上的第二类位置有一个限制:这后面至多有 $i - 1$ 个数比它小(注意求 $z$ 的过程运行到这里的时候允许违反的逆序对数是 $x$)。此时显然最优的话把第一个第二类位置填能填的最大的,剩下的没填的按顺序填能填的最小的。因此我们只用求一下最大可能的 $x$ 即可。(注意这里所有的第二类位置都对这个第三类位置有限制,因为经过第一个第二类位置后,允许违反的逆序对数是 $x$ 不是 $0$)。

Code

#include <bits/stdc++.h>
using namespace std;

template <typename T>
bool vmin(T& a, T b) {
  return a > b ? (a = b, true) : false;
}

const int N = 3e5 + 5;

typedef class Fenwick {
  public:
    int n;
    int a[N];

    void init(int n) {
      this->n = n;
      fill(a, a + n + 1, 0);
    }
    
#define lowbit(_) ((_) & -(_)) 
    void add(int idx, int val) {
      for ( ; idx <= n; idx += lowbit(idx)) {
        a[idx] += val;
      }
    }
    int query(int idx) {
      int ret = 0;
      for ( ; idx; idx -= lowbit(idx)) {
        ret += a[idx];
      }
      return ret;
    }
    int query(int l, int r) {
      return query(r) - query(l - 1);
    }
} Fenwick;

int T, n;
Fenwick fen;
int a[N], les[N];

void solve() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    scanf("%d", a + i);
  }
  int p1, p2;
  for (p1 = 1; p1 <= n && a[p1] == p1; p1++);
  if (p1 >= n) {
    puts("Yes");
    return;
  }
  for (p2 = p1 + 1; p2 <= n && a[p2] > a[p2 - 1]; p2++);
  
  long long dif = -1ll * (n + n - p1) * (p1 - 1) / 2;   
  fen.init(n);
  for (int i = n; i >= p1; i--) {
    int p = a[i];
    dif += fen.query(p);
    fen.add(p, 1);
  }
  for (int i = 1; i <= n; i++) {
    les[i] = n + 1;
  }
  fen.init(n);
  for (int i = p2; i <= n; i++) {
    int p = a[i];
    if (i != p2) {
      les[p] = fen.query(p, n);
    }
    fen.add(p, 1);
  }
  if (a[p2] > a[p1]) {
    int lim = n + 1;
    for (int i = p1; i <= a[p1]; i++) {
      vmin(lim, les[i]);
    }
    dif -= lim + (p2 - p1 - 1);
  } else {
    int lim = n - a[p2] - (p2 - p1);
    for (int i = 2; i <= n; i++) {
      vmin(les[i], les[i - 1]);
    }
    for (int i = p1; i < p2; i++) {
      vmin(lim, les[a[i]] + p2 - i - 1);
    }
    vmin(lim, les[a[p2]] - 1);
    dif -= lim + p2 - p1 - 1;
  }
  puts((dif < 2) ? "Yes" : "No");
}

int main() {
  scanf("%d", &T);
  while (T--) {
    solve();
  }
  return 0;
}

Problem F Authentic Tree DP

  别催了,在路上了.jpg

  不懂就问,这题是给人做的吗?

  (尝试给题解编一个思路)

  显然你需要给这个式子找一个组合意义。看到 $\frac{1}{n}$ 容易想到是选择某个东西。但是这个 $\sum$ 枚举的是边。考虑每条边拆出一个新点,向原来的两个点连边。但这样变成了 $\frac{1}{2n - 1}$。考虑给这样每个新点挂 $P - 1$ 个叶子,这样 $n - 1$ 个新点的贡献就被消掉了。

  这个式子大概是在算一个概率一样的东西,同时注意到枚举的一定是新点,然后将树分成独立的两部分。如果考虑这样得到一个序列,每次在选择在最前的点,可以为这个过程编一个组合意义:求所有新点在序列中的位置在所有邻居前面的概率。

  问题变成一棵树,给定边的定向,问拓扑序方案数。直接对指向根的有向边容斥做背包就完事了。

Code

#include <bits/stdc++.h>
using namespace std;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend bool operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

typedef vector<Zi> Poly;

const int N = 5005;

Poly operator * (Poly a, Poly b) {
  int n = a.size(), m = b.size();
  Poly c (n + m - 1, 0);
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      c[i + j] += a[i] * b[j];
    }
  }
  return c;
}

int n;
Zi Inv[N];
vector<int> G[N];

void init_inv(int n) {
  Inv[1] = 1;
  for (int i = 2; i <= n; i++) {
    Inv[i] = Inv[Mod % i] * (Mod - Mod / i);
  }
}

Poly dfs(int p, int fa) {
  Poly f {0, 1};
  for (auto e : G[p]) {
    if (e ^ fa) {
      Poly g = dfs(e, p);
      for (int i = 1; i < (signed) g.size(); i++) {
        g[i] *= Inv[i];
        g[0] += g[i];
        g[i] = -g[i];
      }
      f = f * g;
    }
  }
  for (int i = 1; i < (signed) f.size(); i++) {
    f[i] *= Inv[i];
  }
  return f;
}

int main() {
  scanf("%d", &n);
  for (int i = 1, u, v; i < n; i++) {
    scanf("%d%d", &u, &v);
    G[u].push_back(v);
    G[v].push_back(u);
  }
  init_inv(n);
  auto f = dfs(1, 0);
  Zi ans = 0;
  for (auto x : f) {
    ans += x;
  }
  printf("%d\n", ans.v);
  return 0;
}

posted @ 2022-08-22 23:59  阿波罗2003  阅读(160)  评论(0编辑  收藏  举报