UOJ Test Round #2 社论()

比赛链接 .

模拟赛 C, D 出的 UTR 题,,,

开始复读官方题解 /oh/oh/oh 魔怔码风见谅 QwQ

  • A. 题目排列顺序
  • B. 题目交流通道
  • C. 题目难度提升

A. 题目排列顺序

给一个序列 \(\{f_n\}\),重排标准排列 \(\pi\),使得 \(\pi[1:i]\) 的 LIS 长度恰为 \(f_i\) .
\(n\le 10^5\) .

给一个序列 \(\{f_n\}\),重排标准排列 \(\pi\),使得 \(\pi[1:i]\) 的 LIS 长度恰为 \(f_i\) .

\(n\le 10^5\) .

按权值第一关键字升序,下标第二关键字降序排序分配 \(1\dots n\) 即可 .

using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
const int N = 1e5 + 7;
int n, ans[N];
pii a[N];
int main()
{
	scanf("%d", &n);
	for (int i=1, x; i<=n; i++){scanf("%d", &x); a[i] = make_pair(x, -i);}
	stable_sort(a+1, a+1+n);
	for (int i=1; i<=n; i++) ans[-a[i].second] = i;
	for (int i=1; i<=n; i++) printf("%d ", ans[i]);
	puts("");
	return 0;
}

B. 题目交流通道

无向完全图 \(G(V,E)\) 给定任意两点间最短路,每条边边权不大于 \(k\),问边权分配方案数 .
\(n\le 400\),\(k\le 10^9\),对 \(998244353\) 取模 .

无向完全图 \(G(V,E)\) 给定任意两点间最短路,每条边边权不大于 \(k\),问边权分配方案数 .

\(n\le 400\),\(k\le 10^9\),对 \(998244353\) 取模 .

首先是无解情况(\(\exists i,j,k\)):

  • \(d_{i,j}>k\) .
  • \(d_{i,i}\neq 0\) .
  • \(d_{i,j}\neq d_{j,i}\) .
  • \(d_{i,j}>d_{i,k}+d_{k,j}\) .

\(d_{u,v}>0\) 的情况,我们只需要对于每条边 \((u,v)\),判断是否存在一个点 \(k\) 满足 \(d_{u,v}=d_{u,k}+d_{k,v}\),如果不存在这样的 \(k\),那么这条边的权值就只能是 \(d_{u,v}\),否则这条边只要大于等于 \(d_{u,v}\) 就可以了,即有 \(kd_{u,v}+1\) 种方案,把所有边的方案乘起来就可以了 .

若 \(d_{u,v}\) 可能等于 \(0\),则缩起来所有 \(d_{u,v}=0\) 的边构成的团,于是贡献分两部分:

  • 团间:等价于有重边的 \(d_{u,v}>0\) 情况,设是 \(a\) 重边,则显然如果存在 \(k\) 使得 \(d_{u,v}=d_{u,k}+d_{k,v}\),有 \((k-d_{u,v}+1)^a\) 种方案,若不然则是 \((k-d_{u,v}+1)^a)-()k-d_{u,v})^a\) 种 .
  • 团内,考虑容斥,令 \(f_i\) 为 \(n\) 个点的距离为 \(0\) 的团的方案数,\(g_i\) 为 \(n\) 个点的图的方案数,则\[\begin{aligned}g_n&=(k+1)^{\binom n2}\\\displaystyle f_n&=g_n-\sum_{i=1}^{n-1}\dbinom{n-1}{i-1}f_ig_{n-i}\cdot k^{i(n-i)}\end{aligned}
    \]

爆算乘起来即可,\(O(n^3)\) .

using namespace std;
typedef long long ll;
typedef double db;
typedef pair<ll, ll> pll;
const int N = 444, P = 998244353;
int n, k;
ll d[N][N], v[N][N], cc[N][N], bel[N], siz[N], f[N], g[N], ans = 1;
bool ok[N][N];
struct dsu
{
	int fa[N];
	dsu(){iota(fa, fa+N, 0);}
	int get(int x){return x == fa[x] ? x : fa[x] = get(fa[x]);}
	inline void merge(int u, int v){fa[get(u)] = get(v);}
}D;
inline int qpow(int a, int n)
{
	int ans = 1;
	while (n)
	{
		if (n & 1) ans = 1ll * ans * a % P;
		a = 1ll * a * a % P; n >>= 1;
	} return ans;
}
inline int inv(int x){return qpow(x, P-2);}
inline bool check()
{
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++)
		{
			if ((i == j) && d[i][j]) return false;
			if (d[i][j] > k) return false;
			if (d[i][j] != d[j][i]) return false;
			for (int k=1; k<=n; k++)
				if ((i != j) && (i != k) && (d[i][j] > d[i][k] + d[k][j])) return false;
		}
	return true;
}
int fac[N], ifac[N];
inline int initf()
{
	fac[0] = 1;
	for (int i=1; i<N; i++) fac[i] = 1ll * fac[i-1] * i % P;
	ifac[N-1] = inv(fac[N-1]);
	for (int i=N-2; i>=0; i--) ifac[i] = 1ll * ifac[i+1] * (i+1) % P;
	return 0xDEADC0DE;
} int _____________ = initf();
inline int binom(int n, int m){return n < m ? 0 : 1ll * fac[n] * ifac[m] % P * ifac[n-m] % P;}
int main()
{
	scanf("%d%d", &n, &k);
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++) scanf("%lld", d[i] + j);
	if (!check()){puts("0"); return 0;}
	for (int i=2; i<=n; i++)
		for (int j=1; j<i; j++)
			if (!d[i][j] && (i != j)) D.merge(i, j);
	for (int i=1; i<=n; i++){bel[i] = D.get(i); ++siz[bel[i]];}
	for (int i=2; i<=n; i++)
		for (int j=1; j<i; j++)
		{
			int bi = bel[i], bj = bel[j];
			if (bi == bj) continue;
			if (v[bi][bj] && (v[bi][bj] != d[i][j])){puts("0"); return 0;}
			v[bi][bj] = v[bj][bi] = d[i][j]; ++cc[bi][bj]; ++cc[bj][bi];
		}
	for (int k=1; k<=n; k++)
		for (int i=1; i<=n; i++)
			for (int j=1; j<=n; j++)
				ok[i][j] |= ((i != k) && (i != j) && (j != k) && siz[i] && siz[j] && siz[k] && (v[i][j] == v[i][k] + v[k][j]));
	for (int i=2; i<=n; i++)
		for (int j=1; j<i; j++)
			if (siz[i] && siz[j])
			{
				if (ok[i][j]) ans = 1ll * ans * qpow(k - v[i][j] + 1, cc[i][j]) % P;
				else ans = 1ll * ans * ((qpow(k - v[i][j] + 1, cc[i][j]) - qpow(k - v[i][j], cc[i][j])) % P + P) % P;
			}
	f[0] = g[0] = 1;
	for (int i=1; i<=n; i++) g[i] = qpow(k + 1, i * (i - 1) >> 1);
	for (int i=1; i<=n; i++)
	{
		f[i] = g[i];
		for (int j=1; j<i; j++)
			f[i] = (f[i] - 1ll * f[j] * g[i-j] % P * binom(i-1, j-1) % P * qpow(k, j * (i-j)) % P + P) % P;
	}
	for (int i=1; i<=n; i++) ans = 1ll * ans * f[siz[i]] % P;
	printf("%lld\n", ans);
	return 0;
}

C. 题目难度提升

给一个序列 \(\{a_n\}\),重排使得前缀中位数单调不减,求重排后字典序最大的 \(\{a\}\) .
\(n\le 10^5\),无解输出 .

给一个序列 \(\{a_n\}\),重排使得前缀中位数单调不减,求重排后字典序最大的 \(\{a\}\) .

\(n\le 10^5\),无解输出 .

QwQ

考虑没有相同的数的情况,注意到若目前中位数为 \(m\),加一个 \(a\) 进去,则如果 \(a<m\) 则中位数减小,\(a>m\) 则中位数增加 .

于是按位枚举,一个前缀有解当且仅当前缀的中位数 \(m\) 小于等于所有还没有放的数 .

最开始先放最小的数,每一阶段放两个数进去 . 阶段开始时因为有奇数个数,所以中位数一定落在某个数上 .

假设开始时中位数是 \(m\),大于 \(m\) 的第一个还没有放的数是 \(k\),如果 \((m,k)\) 中已经有数放了,那么这个数不管放什么都可以,直接放最大的数就可以了 . 否则新放入的数 \(a\) 一定要满足 \(a+m\le 2k\),即 \(a\le 2km\),令 \(R=2km\),则如果 \((m,R]\) 中已经有数放了,直接放最大的数就可以了,否则找到 \((m,R]\) 中最大的数放进去即可 .

假设此时中位数是 \(M\),大于等于 \(M\) 的第一个还没有放的数是 \(L\),如果 \((m,L)\) 中已经有数放了,那么直接放最大的数,否则就只能放 \(L\) 了 .

这样就没了,有相同的数的情况就令所有数的中位数是 \(m\),如果小于等于中位数的数中没有重复出现的数字,那么算法一仍然适用,否则令 \(R\) 为第一个小于等于 \(m\) 的重复出现的数字,然后整一个类似 two-pointers 的东西放 \(R\),按字典序一次放两个大概就行了 .

时间复杂度大概 \(O(n\log n)\) 吧 .

using namespace std;
typedef long long ll;
typedef double db;
typedef pair<ll, ll> pll;
const int N = 114514;
multiset<int> S;
struct
{
	priority_queue<int, vector<int>, less<int> >    L;
	priority_queue<int, vector<int>, greater<int> > G;
	inline void push(int val)
	{
		if (L.empty() || (val <= L.top())) L.push(val);
		else G.push(val);
		if (L.size() < G.size()){L.push(G.top()); G.pop();}
		if (L.size() - G.size() > 1){G.push(L.top()); L.pop();}
	}
	inline int le(){return L.top();}
	inline int gr(){return G.top();}
	inline bool even(){return L.size() == G.size();}
	inline bool existG(){return G.size();}
}T;
int n, a[N], cc;
bool vis[N];
int main()
{
	scanf("%d", &n);
	for (int i=1; i<=n; i++) scanf("%d", a+i);
	stable_sort(a+1, a+1+n);
	int ptr = (n + 1) >> 1;
	if (a[ptr] == a[ptr+1])
	{
		while (a[ptr] == a[ptr+1]) ++ptr;
		printf("%d ", a[ptr]);
		int l = ptr-1, r = n;
		while (l || (r > ptr))
		{
			if (l) printf("%d ", a[l--]);
			if (r > ptr) printf("%d ", a[r--]);
		}
		return 0;
	}
	while ((ptr > 1) && (a[ptr] != a[ptr-1])) --ptr;
	printf("%d ", a[ptr]); vis[ptr] = true;
	int l = ptr-1, r = n;
	while (l && (r > ptr))
	{
		printf("%d ", a[l]); vis[l--] = true;
		printf("%d ", a[r]); vis[r--] = true;
	}
	for (int i=1; i<=n; i++)
		if (vis[i]) T.push(a[i]);
		else S.insert(a[i]);
	while (!S.empty())
	{
		l = *S.begin();
		printf("%d ", r = ((T.even()) ? ((l >= T.gr()) ? *(--S.end()) : *S.begin()) : ((T.existG() && ((l << 1) >= T.le() + T.gr())) ? *(--S.end()) : *(--S.upper_bound((l<<1) - T.le())))));
		S.erase(S.find(r)); T.push(r);
	}
	return 0;
}
————————

比赛链接 .

模拟赛 C, D 出的 UTR 题,,,

开始复读官方题解 /oh/oh/oh 魔怔码风见谅 QwQ

  • A. 题目排列顺序
  • B. 题目交流通道
  • C. 题目难度提升

A. 题目排列顺序

给一个序列 \(\{f_n\}\),重排标准排列 \(\pi\),使得 \(\pi[1:i]\) 的 LIS 长度恰为 \(f_i\) .
\(n\le 10^5\) .

给一个序列 \(\{f_n\}\),重排标准排列 \(\pi\),使得 \(\pi[1:i]\) 的 LIS 长度恰为 \(f_i\) .

\(n\le 10^5\) .

按权值第一关键字升序,下标第二关键字降序排序分配 \(1\dots n\) 即可 .

using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
const int N = 1e5 + 7;
int n, ans[N];
pii a[N];
int main()
{
	scanf("%d", &n);
	for (int i=1, x; i<=n; i++){scanf("%d", &x); a[i] = make_pair(x, -i);}
	stable_sort(a+1, a+1+n);
	for (int i=1; i<=n; i++) ans[-a[i].second] = i;
	for (int i=1; i<=n; i++) printf("%d ", ans[i]);
	puts("");
	return 0;
}

B. 题目交流通道

无向完全图 \(G(V,E)\) 给定任意两点间最短路,每条边边权不大于 \(k\),问边权分配方案数 .
\(n\le 400\),\(k\le 10^9\),对 \(998244353\) 取模 .

无向完全图 \(G(V,E)\) 给定任意两点间最短路,每条边边权不大于 \(k\),问边权分配方案数 .

\(n\le 400\),\(k\le 10^9\),对 \(998244353\) 取模 .

首先是无解情况(\(\exists i,j,k\)):

  • \(d_{i,j}>k\) .
  • \(d_{i,i}\neq 0\) .
  • \(d_{i,j}\neq d_{j,i}\) .
  • \(d_{i,j}>d_{i,k}+d_{k,j}\) .

\(d_{u,v}>0\) 的情况,我们只需要对于每条边 \((u,v)\),判断是否存在一个点 \(k\) 满足 \(d_{u,v}=d_{u,k}+d_{k,v}\),如果不存在这样的 \(k\),那么这条边的权值就只能是 \(d_{u,v}\),否则这条边只要大于等于 \(d_{u,v}\) 就可以了,即有 \(kd_{u,v}+1\) 种方案,把所有边的方案乘起来就可以了 .

若 \(d_{u,v}\) 可能等于 \(0\),则缩起来所有 \(d_{u,v}=0\) 的边构成的团,于是贡献分两部分:

  • 团间:等价于有重边的 \(d_{u,v}>0\) 情况,设是 \(a\) 重边,则显然如果存在 \(k\) 使得 \(d_{u,v}=d_{u,k}+d_{k,v}\),有 \((k-d_{u,v}+1)^a\) 种方案,若不然则是 \((k-d_{u,v}+1)^a)-()k-d_{u,v})^a\) 种 .
  • 团内,考虑容斥,令 \(f_i\) 为 \(n\) 个点的距离为 \(0\) 的团的方案数,\(g_i\) 为 \(n\) 个点的图的方案数,则\[\begin{aligned}g_n&=(k+1)^{\binom n2}\\\displaystyle f_n&=g_n-\sum_{i=1}^{n-1}\dbinom{n-1}{i-1}f_ig_{n-i}\cdot k^{i(n-i)}\end{aligned}
    \]

爆算乘起来即可,\(O(n^3)\) .

using namespace std;
typedef long long ll;
typedef double db;
typedef pair<ll, ll> pll;
const int N = 444, P = 998244353;
int n, k;
ll d[N][N], v[N][N], cc[N][N], bel[N], siz[N], f[N], g[N], ans = 1;
bool ok[N][N];
struct dsu
{
	int fa[N];
	dsu(){iota(fa, fa+N, 0);}
	int get(int x){return x == fa[x] ? x : fa[x] = get(fa[x]);}
	inline void merge(int u, int v){fa[get(u)] = get(v);}
}D;
inline int qpow(int a, int n)
{
	int ans = 1;
	while (n)
	{
		if (n & 1) ans = 1ll * ans * a % P;
		a = 1ll * a * a % P; n >>= 1;
	} return ans;
}
inline int inv(int x){return qpow(x, P-2);}
inline bool check()
{
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++)
		{
			if ((i == j) && d[i][j]) return false;
			if (d[i][j] > k) return false;
			if (d[i][j] != d[j][i]) return false;
			for (int k=1; k<=n; k++)
				if ((i != j) && (i != k) && (d[i][j] > d[i][k] + d[k][j])) return false;
		}
	return true;
}
int fac[N], ifac[N];
inline int initf()
{
	fac[0] = 1;
	for (int i=1; i<N; i++) fac[i] = 1ll * fac[i-1] * i % P;
	ifac[N-1] = inv(fac[N-1]);
	for (int i=N-2; i>=0; i--) ifac[i] = 1ll * ifac[i+1] * (i+1) % P;
	return 0xDEADC0DE;
} int _____________ = initf();
inline int binom(int n, int m){return n < m ? 0 : 1ll * fac[n] * ifac[m] % P * ifac[n-m] % P;}
int main()
{
	scanf("%d%d", &n, &k);
	for (int i=1; i<=n; i++)
		for (int j=1; j<=n; j++) scanf("%lld", d[i] + j);
	if (!check()){puts("0"); return 0;}
	for (int i=2; i<=n; i++)
		for (int j=1; j<i; j++)
			if (!d[i][j] && (i != j)) D.merge(i, j);
	for (int i=1; i<=n; i++){bel[i] = D.get(i); ++siz[bel[i]];}
	for (int i=2; i<=n; i++)
		for (int j=1; j<i; j++)
		{
			int bi = bel[i], bj = bel[j];
			if (bi == bj) continue;
			if (v[bi][bj] && (v[bi][bj] != d[i][j])){puts("0"); return 0;}
			v[bi][bj] = v[bj][bi] = d[i][j]; ++cc[bi][bj]; ++cc[bj][bi];
		}
	for (int k=1; k<=n; k++)
		for (int i=1; i<=n; i++)
			for (int j=1; j<=n; j++)
				ok[i][j] |= ((i != k) && (i != j) && (j != k) && siz[i] && siz[j] && siz[k] && (v[i][j] == v[i][k] + v[k][j]));
	for (int i=2; i<=n; i++)
		for (int j=1; j<i; j++)
			if (siz[i] && siz[j])
			{
				if (ok[i][j]) ans = 1ll * ans * qpow(k - v[i][j] + 1, cc[i][j]) % P;
				else ans = 1ll * ans * ((qpow(k - v[i][j] + 1, cc[i][j]) - qpow(k - v[i][j], cc[i][j])) % P + P) % P;
			}
	f[0] = g[0] = 1;
	for (int i=1; i<=n; i++) g[i] = qpow(k + 1, i * (i - 1) >> 1);
	for (int i=1; i<=n; i++)
	{
		f[i] = g[i];
		for (int j=1; j<i; j++)
			f[i] = (f[i] - 1ll * f[j] * g[i-j] % P * binom(i-1, j-1) % P * qpow(k, j * (i-j)) % P + P) % P;
	}
	for (int i=1; i<=n; i++) ans = 1ll * ans * f[siz[i]] % P;
	printf("%lld\n", ans);
	return 0;
}

C. 题目难度提升

给一个序列 \(\{a_n\}\),重排使得前缀中位数单调不减,求重排后字典序最大的 \(\{a\}\) .
\(n\le 10^5\),无解输出 .

给一个序列 \(\{a_n\}\),重排使得前缀中位数单调不减,求重排后字典序最大的 \(\{a\}\) .

\(n\le 10^5\),无解输出 .

QwQ

考虑没有相同的数的情况,注意到若目前中位数为 \(m\),加一个 \(a\) 进去,则如果 \(a<m\) 则中位数减小,\(a>m\) 则中位数增加 .

于是按位枚举,一个前缀有解当且仅当前缀的中位数 \(m\) 小于等于所有还没有放的数 .

最开始先放最小的数,每一阶段放两个数进去 . 阶段开始时因为有奇数个数,所以中位数一定落在某个数上 .

假设开始时中位数是 \(m\),大于 \(m\) 的第一个还没有放的数是 \(k\),如果 \((m,k)\) 中已经有数放了,那么这个数不管放什么都可以,直接放最大的数就可以了 . 否则新放入的数 \(a\) 一定要满足 \(a+m\le 2k\),即 \(a\le 2km\),令 \(R=2km\),则如果 \((m,R]\) 中已经有数放了,直接放最大的数就可以了,否则找到 \((m,R]\) 中最大的数放进去即可 .

假设此时中位数是 \(M\),大于等于 \(M\) 的第一个还没有放的数是 \(L\),如果 \((m,L)\) 中已经有数放了,那么直接放最大的数,否则就只能放 \(L\) 了 .

这样就没了,有相同的数的情况就令所有数的中位数是 \(m\),如果小于等于中位数的数中没有重复出现的数字,那么算法一仍然适用,否则令 \(R\) 为第一个小于等于 \(m\) 的重复出现的数字,然后整一个类似 two-pointers 的东西放 \(R\),按字典序一次放两个大概就行了 .

时间复杂度大概 \(O(n\log n)\) 吧 .

using namespace std;
typedef long long ll;
typedef double db;
typedef pair<ll, ll> pll;
const int N = 114514;
multiset<int> S;
struct
{
	priority_queue<int, vector<int>, less<int> >    L;
	priority_queue<int, vector<int>, greater<int> > G;
	inline void push(int val)
	{
		if (L.empty() || (val <= L.top())) L.push(val);
		else G.push(val);
		if (L.size() < G.size()){L.push(G.top()); G.pop();}
		if (L.size() - G.size() > 1){G.push(L.top()); L.pop();}
	}
	inline int le(){return L.top();}
	inline int gr(){return G.top();}
	inline bool even(){return L.size() == G.size();}
	inline bool existG(){return G.size();}
}T;
int n, a[N], cc;
bool vis[N];
int main()
{
	scanf("%d", &n);
	for (int i=1; i<=n; i++) scanf("%d", a+i);
	stable_sort(a+1, a+1+n);
	int ptr = (n + 1) >> 1;
	if (a[ptr] == a[ptr+1])
	{
		while (a[ptr] == a[ptr+1]) ++ptr;
		printf("%d ", a[ptr]);
		int l = ptr-1, r = n;
		while (l || (r > ptr))
		{
			if (l) printf("%d ", a[l--]);
			if (r > ptr) printf("%d ", a[r--]);
		}
		return 0;
	}
	while ((ptr > 1) && (a[ptr] != a[ptr-1])) --ptr;
	printf("%d ", a[ptr]); vis[ptr] = true;
	int l = ptr-1, r = n;
	while (l && (r > ptr))
	{
		printf("%d ", a[l]); vis[l--] = true;
		printf("%d ", a[r]); vis[r--] = true;
	}
	for (int i=1; i<=n; i++)
		if (vis[i]) T.push(a[i]);
		else S.insert(a[i]);
	while (!S.empty())
	{
		l = *S.begin();
		printf("%d ", r = ((T.even()) ? ((l >= T.gr()) ? *(--S.end()) : *S.begin()) : ((T.existG() && ((l << 1) >= T.le() + T.gr())) ? *(--S.end()) : *(--S.upper_bound((l<<1) - T.le())))));
		S.erase(S.find(r)); T.push(r);
	}
	return 0;
}