校内训练 2019-10-03
今天的考试的题目都还挺友好的。
适合 NOIP 难度,题目质量和数据强度近乎完美,不愧是 JS 队长 xyx 的题。但是为什么 T3 的 std 在我校 OJ 上 T 掉了呢
T1 作为一个签到题,比较符合 NOIP D2T1 的难度,有一定的思维量,但是思维量很小,无论是找规律还是直接推都可以很快地得到正解,并且对拍非常方便。
T2 的题面在文字理解上有一些难度,但是解决了文字问题以后,只要注意到一个 每个单位 不会在同一种联系中出现大于一次
的限制,并观察观察样例 2, 3 以后,也可以得出正确的做法,难度略低于 NOIP D2T2 难度,与去年的 D1T3 难度相近。
T3 中的一些条件容易把思路带偏,因此需要及时的考虑一些限制条件是否有必要,是否可以缩小限制的范围。如果考虑周全的话,这道题也可以使用各种数据结构快速地答出。难度大约与 D2T2 或者 2017 年及以前的 D1T3 相近。
在 NOIP 的模拟赛中,值得注意的是时间问题,毕竟 NOIP 只有 3.5h,不能把 NOIP 当成 5h。
T1. 时之终结 review
题目传送门
http://192.168.21.187/contest/31/problem/1 = http://192.168.21.187/problem/1310
http://47.100.137.146/contest/31/problem/1 = http://47.100.137.146/problem/1310
题解
发现最终的限制是 \(N \leq 64\),很容易想到这个题目和二进制有关。
发现对于一个 \(T\) 个点的有向完全图,从 \(1\) 到 \(T\) 的方案数恰好为 \(2^{T-2}\)。
联系一下上面的两个结论,我们很容易想到把 \(Y\) 二进制拆分(拆成一堆 \(2\) 的整数幂的和),再建立一个有向完全图,那么一定可以从这个图中找到对应的到 \(N\) 的方案为 \(2\) 的整次幂的点。让 \(1\) 向这些点分别建边即可。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
ll Y;
int dy[100];
pii ans[10000 + 7];
inline void work() {
int n;
if (Y > 1) n = std::__lg(Y - 1) + 4;
else n = 3;
int ccc = 0, cnt = 0;
for (int i = n - 1; i >= 2; --i) {
for (int j = n; j > i; --j) ans[++cnt] = pii(i, j);
dy[ccc++] = i;
}
for (int i = 0; Y; ++i, Y >>= 1) if (Y & 1) ans[++cnt] = pii(1, dy[i]);
printf("%d %d\n", n,cnt);
for (int i = 1; i <= cnt; ++i) printf("%d %d\n", ans[i].fi, ans[i].se);
}
inline void init() {
read(Y);
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#else
File(review);
#endif
init();
work();
fclose(stdin), fclose(stdout);
return 0;
}
T2. 博士之时 refrain
题目传送门
http://192.168.21.187/contest/31/problem/2 = http://192.168.21.187/problem/1311
http://47.100.137.146/contest/31/problem/2 = http://47.100.137.146/problem/1311
题解
因为每个单位不会在同一种联系中出现大于一次,所以如果我们把整个图建立出来以后,一定可以划分成很多的环和链。
我们发现直接计算能够破坏的排列数不太好求,因此转化为不能破坏的方案数。不能破坏就意味着,原来在一个环上的两个相邻的点,转换后,也要在同一个环上相邻(而且边的颜色不能改变)。链同理。
在一个环的内部,我们想要让每个点不脱离这个环。如果环的长度为 \(len\),若 \(len\) 是奇数的话,显然只能不动,贡献为 \(1\);如果 \(len\) 是偶数,那么我们的置换方案有两种:第一种是沿着一条边的中垂线翻折;第二种是旋转偶数长度,两种方案的方案数都是 \(\frac {len}2\),于是这个环的贡献为 \(len\)。
UPD: 上面有些问题,不存在长度为奇数的环。
但是最终,对于 \(x\) 个长度为 \(i\) 的环,它们之间也可以相互做整体置换。所以 \(x\) 个长度为 \(i\) 的环的贡献为 \(x!i^x\)。
链同理。
链要分为长度为奇数个点的链和偶数个点的链。
奇数个点的链没有任何操作方法,只能保持不动,贡献为 \(1\);所以 \(x\) 个长度为 \(i\) 的链的贡献为 \(x!\)。
偶数个点的链可以沿着中垂线翻折,所以贡献为 \(2\);所以 \(x\) 个长度为 \(i\) 的链的贡献为 \(x!2^x\)。
把每一堆长度相同的链或者环的贡献乘起来就可以了。
最后用 \(n!\) 减去上面的答案。时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 2e5 + 7;
const int P = 1e9 + 7;
int n, m1, m2;
int to1[N], to2[N], vis[N];
int huan[N], lian1[N], lian2[N];
int fac[N], inv[N], ifac[N];
inline void build() {
for (int i = 1; i <= n; ++i) if (!vis[i] && !to1[i]) {
int x = i, cnt = 0;
while (x) {
++cnt, vis[x] = 1;
if (cnt & 1) x = to2[x];
else x = to1[x];
}
++lian1[cnt];
}
for (int i = 1; i <= n; ++i) if (!vis[i] && !to2[i]) {
int x = i, cnt = 0;
while (x) {
++cnt, vis[x] = 1;
if (cnt & 1) x = to1[x];
else x = to2[x];
}
++lian2[cnt];
}
for (int i = 1; i <= n; ++i) if (!vis[i]) {
int x = i, cnt = 0;
while (!vis[x]) {
++cnt, vis[x] = 1;
if (cnt & 1) x = to1[x];
else x = to2[x];
}
++huan[cnt];
}
}
inline int fpow(int x, int y) {
int ans = 1;
for (; y; y >>= 1, x = (ll)x * x % P) if (y & 1) ans = (ll)ans * x % P;
return ans;
}
inline void ycl() {
fac[0] = 1; for (int i = 1; i <= n; ++i) fac[i] = (ll)fac[i - 1] * i % P;
inv[1] = 1; for (int i = 2; i <= n; ++i) inv[i] = (ll)(P - P / i) * inv[P % i] % P;
ifac[0] = 1; for (int i = 1; i <= n; ++i) ifac[i] = (ll)ifac[i - 1] * inv[i] % P;
}
inline void work() {
build();
ycl();
int ans = 1;
for (int i = 1; i <= n; ++i) {
if (!(i & 1)) ans = (ll)ans * fac[lian1[i]] % P * fpow(2, lian1[i]) % P;
else ans = (ll)ans * fac[lian1[i]] % P;
if (!(i & 1)) ans = (ll)ans * fac[lian2[i]] % P * fpow(2, lian2[i]) % P;
else ans = (ll)ans * fac[lian2[i]] % P;
if (!(i & 1)) ans = (ll)ans * fac[huan[i]] % P * fpow(i, huan[i]) % P;
// if (i <= 10) dbg("i = %d, lian1 = %d, lian2 = %d, huan = %d\n", i, lian1[i], lian2[i], huan[i]);
}
// dbg("ans = %d\n", ans);
ans = fac[n] + P - ans;
printf("%d\n", ans % P);
}
inline void init() {
read(n), read(n), read(m1), read(m2);
int x, y;
for (int i = 1; i <= m1; ++i) read(x), read(y), to1[x] = y, to1[y] = x;
for (int i = 1; i <= m2; ++i) read(x), read(y), to2[x] = y, to2[y] = x;
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#else
File(refrain);
#endif
init();
work();
fclose(stdin), fclose(stdout);
return 0;
}
T3. 曾有两次 rebirth
题目传送门
http://192.168.21.187/contest/31/problem/3 = http://192.168.21.187/problem/1312
http://47.100.137.146/contest/31/problem/3 = http://47.100.137.146/problem/1312
题解
考试的时候想多了,认为应该维护的边是一个点位于在最短路图上 \(x\) 能够到达的点,另一个端点不被 \(x\) 支配的边。
如上文所述,对于这样的一条边,以及一个最短路的普及组性质“一个最短路上的两个点之间路径也是最短路”,我们很容易得到用这条边更新以后的 \(x\) 的最短路径的长度,即 \(dis[u] + w + dis[v] - dis[x]\)。
但是呢,我们可以发现做最短路图是没有任何必要的。只需要最短路树即可。对于新的路径,一定存在一条非树边,从 \(x\) 的子树外连向子树内。所以我们只需要使用线段树合并维护一下所有满足条件的非树边的贡献的最小值就可以了。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
#define dbg(...) fprintf(stderr, __VA_ARGS__)
#define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define fi first
#define se second
#define pb push_back
template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b, 1 : 0;}
template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b, 1 : 0;}
typedef long long ll; typedef unsigned long long ull; typedef std::pair<ll, int> pii;
template<typename I> inline void read(I &x) {
int f = 0, c;
while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
x = c & 15;
while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
f ? x = -x : 0;
}
const int N = 2e5 + 7;
const int M = 5e5 + 7;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, dfc, nod;
int dfn[N], rt[N], siz[N];
bool vis[N];
ll dis[N], ans[N];
std::priority_queue<pii, std::vector<pii>, std::greater<pii>> q;
struct Edge { int to, ne, w; } g[M << 1]; int head[N], tot;
inline void addedge(int x, int y, int z) { g[++tot].to = y, g[tot].w = z, g[tot].ne = head[x], head[x] = tot; }
inline void adde(int x, int y, int z) { addedge(x, y, z), addedge(y, x, z); }
struct Node {
int lc, rc;
ll val;
} t[(M + N) * 20];
inline void qins(int &o, int L, int R, int x, ll k) {
if (!o) o = ++nod, t[o].val = k;
else smin(t[o].val, k);
if (L == R) return;
int M = (L + R) >> 1;
if (x <= M) qins(t[o].lc, L, M, x, k);
else qins(t[o].rc, M + 1, R, x, k);
}
inline ll qmin(int o, int L, int R, int l, int r) {
// dbg("o = %d, L = %d, R = %d, l = %d, r = %d, t[o].val = %lld\n", o, L, R, l, r, t[o].val);
if (!o) return INF;
if (l > r) return INF;
if (l <= L && R <= r) return t[o].val;
int M = (L + R) >> 1;
if (r <= M) return qmin(t[o].lc, L, M, l, r);
if (l > M) return qmin(t[o].rc, M + 1, R, l, r);
return std::min(qmin(t[o].lc, L, M, l, r), qmin(t[o].rc, M + 1, R, l, r));
}
inline int merge(int o, int p) {
if (!o || !p) return o ^ p;
t[o].val = std::min(t[o].val, t[p].val);
t[o].lc = merge(t[o].lc, t[p].lc);
t[o].rc = merge(t[o].rc, t[p].rc);
return o;
}
inline void dijkstra() {
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0, q.push(pii(dis[1], 1));
while (!q.empty()) {
int x = q.top().se; q.pop();
if (vis[x]) continue;
vis[x] = 1;
for fec(i, x, y) if (smin(dis[y], dis[x] + g[i].w)) q.push(pii(dis[y], y));
}
}
inline void dfs1(int x, int fa = 0) {
dfn[x] = ++dfc, siz[x] = 1;
for fec(i, x, y) if (y != fa && dis[y] == dis[x] + g[i].w) dfs1(y, x), siz[x] += siz[y];
}
inline void dfs2(int x, int fa = 0) {
for fec(i, x, y) if ((y != fa || dis[x] != dis[y] + g[i].w) && dis[y] != dis[x] + g[i].w) qins(rt[x], 1, n, dfn[y], dis[y] + dis[x] + g[i].w);//, dbg("x = %d, y = %d, g[i].w = %d\n", x, y, g[i].w);
for fec(i, x, y) if (y != fa && dis[y] == dis[x] + g[i].w) dfs2(y, x), rt[x] = merge(rt[x], rt[y]);
if (fa) {
// dbg("rt[%d] = %d, %lld, %lld\n", x, rt[x], qmin(rt[x], 1, n, 1, dfn[x] - 1), qmin(rt[x], 1, n, dfn[x] + siz[x], n));
ans[x] = std::min(qmin(rt[x], 1, n, 1, dfn[x] - 1), qmin(rt[x], 1, n, dfn[x] + siz[x], n));
if (ans[x] == INF) ans[x] = -1;
else ans[x] -= dis[x];
}
}
inline void work() {
dijkstra();
// for (int i = 1; i <= n; ++i) dbg("dis[%d] = %lld\n", i, dis[i]);
dfs1(1);
dfs2(1);
for (int i = 1; i <= n; ++i) printf("%lld%c", ans[i], " \n"[i == n]);
}
inline void init() {
read(n), read(n), read(m);
int x, y, z;
for (int i = 1; i <= m; ++i) read(x), read(y), read(z), adde(x, y, z);
}
int main() {
#ifdef hzhkk
freopen("hkk.in", "r", stdin);
#else
File(rebirth);
#endif
init();
work();
fclose(stdin), fclose(stdout);
return 0;
}