9.21
今天开始集训告一段落了,去补文化课一周。
如果写不完回来会补(?)
115=100+0+15
t1
先等会
t2
先等会
t3
9.22:回来补债了
\([\gcd(i,j)=1]=[\gcd(p_i,p_j)=1]\)
这个限制初看好像很难转化,只能猜到与组合数和素数有关。
于是我们从原始情况入手,考虑如何变换之后得到的排列是符合条件的。
发现对于 2,4,8,16 \(\cdots\) 这些数任意交换位置后得到排列都是合法的,考虑从此入手。
进一步手模数据,发现关系一:
对于所有质因数集合相同的数,任意交换位置后排列合法
解释:我们只关心两个数及其下标是否互质,因此我们只关心其质因数集合是否相同。(多模会数据就理解了)
可是这还不够,继续寻找。
关系二:
在 \(1\le i \le n\) 中,对于所有满足 $\lfloor \frac{n}{pri_i} \rfloor\le 1 $ 的素数可以与 \(1\) 交换位置
解释:\(1\) 永远与所有数互质,而对于 $\lfloor \frac{n}{pri_i} \rfloor\le 1 $ 的素数在 \(1\le i \le n\) 范围内也与所有数互质,与 \(1\) 等效,故交换后合法。
此时已经可以过掉 \(n=13\) 了,但当 \(n=14\) 时答案依旧错误。
发现错误原因:5,10 与 7,14 可以整体交换!
我们发现它们的质因数的出现次数相同 ,即说明对于一个质因数的约束,我们只关心其约束条件是否成立而并不关心质因数的值,因此对于质因数出现次数相同也可整体交换 。
此为关系三。
到此结论已推完,实现即可。
交换对应排列,乘对应阶乘即可。
只需找出所有质因数集合并统计个数 \(\cdots\)
等等,这一步咋实现 ?
map 里放 vector
这个我也是赛后才知道可以这么套,学到了。
具体实现看代码。
code
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
const int p = 13131;
const int mod = 1e9 + 7;
int n, ans;
int f[N], cnt[N];
int pri[N], tot;
int t[N];
bool flag[N];
vector<int> e[N];
map<vector<int>, int> mp;inline void init()
{f[0] = 1, f[1] = 1;flag[1] = 1;for (int i = 2; i <= n; ++i){f[i] = f[i - 1] * i % mod;if (!flag[i])pri[++tot] = i;for (int j = 1; j <= tot; ++j){if (i * pri[j] > n)break;flag[i * pri[j]] = 1;if (i % pri[j] == 0)break;}}
}signed main()
{freopen("tg.in", "r", stdin);freopen("tg.out", "w", stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> n;init();for (int j = 1; j <= tot; ++j)for (int i = pri[j]; i <= n; i += pri[j]){e[i].push_back(j);++cnt[j];}for (int i = 1; i <= n; ++i){if (!flag[i]){if (e[i].size() == 1 && cnt[e[i][0]] == 1)e[i][0] = tot + 1;//将关系二中满足条件的素数与1放入一个集合}if (i == 1)e[i].push_back(tot + 1);++mp[e[i]];}ans = 1;for (int i = 1; i <= tot; ++i)++t[cnt[i]];for (int i = 2; i <= n; ++i) // 5 10 7 14 可互换:质因数出现次数相同时可互换,桶记录ans = ans * f[t[i]] % mod;for (auto y : mp) // 记录质因数种类相同的数的个数ans = ans * f[y.second] % mod;cout << ans << "\n";return 0;
}
t4
9.23:补ing
看完题第一感觉就是最小生成树,但发现将所有边连出再生成显然要爆炸,而只对每个城市内的图做最小生成树又不好处理高铁的情况。
这时我们要开始利用最小生成树的思想。
肯定要用到最小生成树,再做一部分后续处理。
对于每个城市内的图是同构的,显然可以做一遍最小生成树,先筛选出可能被选的边,再进行后续处理。
我们把枢纽站叫作关键点。
若无高铁站的情况,则显然为每个城市内最小生成树。
考虑有高铁站后的影响:
对于城市内的点,不一定可以在城市内直接到达,也就是说,若建造高铁站更优,则可以通过高铁站到达其它城市,再到达目标点,即在同一城市内,图不一定连通。
考虑如何选择这些边:
观察易得,所有不在关键点之间路径上的树边都一定会被选,为了保证整张图连通,这些边必选,直接累加答案即可。
对于其它边,将其记录下来( \(e\) 数组)当作备选。
高铁站更优当且仅当 \(a_i\le b_i+d_j\) ,即 \(a_i-b_i\le d_j\) 。
也就是说,对于一个城市,所有边权(被选出的边)大于 \(a_i-b_i\) 的边,都应不修建。
这启发我们对于城市按 \(a_i\) 升序排列(后有解释),对于 \(e\) 数组按照边权升序进行排序,求前缀和,随后对于每个城市,二分找出哪一条边以后都应不修建,利用前缀和求出应修建的边的贡献,不修建的边数乘 \(a_i\) 作为修高铁的贡献。
对于 \(1\) 号城市与 \(l\) 号城市直接连权值最小的前 \(r-1\) 条边保证在最后一个城市内部连通,否则若没有城市内部连通,则高铁再多图也不连通。(这时体现将城市排序的作用,修建高铁时选择费用少的,对于费用最大的城市令其内部连通,以此在保证图连通时总费用最小)。
关于如何选出关键点之间的路径:
我们将关键点的 \(fa\) 都设为 \(0\) ,表示它们已被连通,之后正常最小生成树,若 \(find(u)\not=find(v)\) ,则不为关键点路径上的边,直接累加答案即可,否则表示为关键点路径上的点(因为所有关键点已事先“被连通”,发现两点的 \(fa\) 相同则一定为关键点加入树中路径上的点,不理解就造样例手模),将其加入 \(e\) 数组即可。
code
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, m, l, r, sumb, ans;
int fa[N], id[N];
int a[N], b[N], sumq[N];
int s[N], q[N], tot;
struct node
{int u, v;int val;
} e[N];
inline int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
inline bool cmp(node a, node b) { return a.val < b.val; }
inline bool cmp2(int x, int y) { return a[x] < a[y]; }signed main()
{freopen("rb.in", "r", stdin);freopen("rb.out", "w", stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> n >> m;for (int i = 1; i <= m; ++i){cin >> e[i].u >> e[i].v >> e[i].val;++e[i].u, ++e[i].v;}sort(e + 1, e + 1 + m, cmp);for (int i = 1; i <= n; ++i)fa[i] = i;for (int i = 1; i <= m; ++i) // 先最小生成树降低边数至n{int fu = find(e[i].u), fv = find(e[i].v);if (fu != fv)e[++tot] = e[i], fa[fu] = fv;}for (int i = 1; i <= n; ++i)fa[i] = i;cin >> l;for (int i = 1; i <= l; ++i)cin >> a[i] >> b[i], sumb += b[i];cin >> r;for (int i = 1; i <= r; ++i)cin >> s[i], ++s[i], fa[s[i]] = 0;tot = 0;for (int i = 1; i < n; ++i){int u = find(e[i].u), v = find(e[i].v);if (u != v)fa[u] = v, ans += sumb + e[i].val * l;elseq[++tot] = e[i].val;}for (int i = 1; i < r; ++i)sumq[i] = sumq[i - 1] + q[i];for (int i = 1; i <= l; ++i) // 城市之间fa[i] = id[i] = i;sort(id + 1, id + 1 + l, cmp2);for (int i = 1; i < l; ++i) // 连高铁{int x = id[i];int y = x % l + 1;x = find(x), y = find(y);if (b[x] < b[y])swap(x, y);int pos = upper_bound(q + 1, q + r, a[id[i]] - b[x]) - q - 1; // a b+d --> a-b d 二分找位置ans += b[x] * pos + sumq[pos] + (r - pos) * a[id[i]];fa[x] = y;}ans += b[find(1)] * (r - 1) + sumq[r - 1]; // a最大的直接连权值最小r-1条边保证连通cout << ans << "\n";return 0;
}
咕...