10.23集训解题报告
T1 莫比乌斯反演
题面:
R 老师给了你一个长度为 \(n\) 的数列 \(a\),这个数列里的数都是不超过 \(2 × 10^8\) 的正整数。
他定义一个三元正整数组 \((x, y, z)\) 是“可以反演的”,当且仅当这个三元组满足如下条件:
• \(1 ≤ x < y < z ≤ n\)。
• \(\gcd(a_x, a_y) = a_z\)。
其中 \(\gcd(u, v)\) 表示 \(u\) 和 \(v\) 的最大公约数。
现在,请你求出数列 \(a\) 中“可以反演的”三元组的个数。两个三元组不同当且仅当对应的 \(x\) 不同或 \(y\) 不同或 \(z\) 不同。
思路:
范围不大,复杂度在 \(n^2\) 左右均可过,还能带个 \(log\)。
一共三千个数,把每一个不一样的数都存到 \(vector\) 里,下标是 \(map\) 离散化之后的,存的是在数组里的下标,然后枚举 \(x,y\) ,二分找 \(y\) 后面的 \(a_z\) 的个数。
代码:
int n;
map<int, int>mp;
vector<int>q[3003];
int sz[3003];
ll ans;
int tot;
int main(){
n=read();
for(int i=1; i<=n; ++i) {
sz[i]=read();
if(!mp[sz[i]]) mp[sz[i]]=++tot;
q[mp[sz[i]]].push_back(i);
}
for(int i=1; i<=n; ++i) {
for(int j=i+1; j<=n; ++j) {
int k=gcd(sz[i], sz[j]);
if(mp.find(k)==mp.end()) continue ;
int wz=mp[k];
int p=upper_bound(q[wz].begin(), q[wz].end(), j)-q[wz].begin();
ans+=0LL+q[wz].size()-p;
}
}
cout<<ans;
}
T2 柯西收敛准则
题面:
R 老师又给了你一个长度为 \(n\) 的数列 \(a\),这个数列中的数都是绝对值不超过 \(10^9\) 的正整数。
R 老师想让你找到最小的 \(k\),使得 \(a\) 存在一个长度为 \(m\) 子数列 \(b\),满足 \(b\) 中相邻两个数的差值不大于 \(k\),即 \(\forall i \in [1, m − 1], |b_i − b_{i−1}| \le k\)。
思路:
\(k\) 未知,如果按照 \(k\) 尽可能小去 \(DP\) 可能 \(DP\) 不出来长为 \(m\) 的序列。
但是如果去 \(DP\) 长为 \(m\) 的序列,\(k\) 不一定是最小的。
总之,\(k\) 和 \(m\) 不好同时维护。
但是不难发现,当一个 \(k\) 合法的时候,\(k\) 再大一点也合法,\(k\) 不合法时, \(k\) 小了也不合法,所以可以二分 \(k\) 然后去 \(DP\) 看能不能出来一个长为 \(m\) 的序列。
代码:
int n, m;
int sz[1003];
int dp[1003];
int ans=2000000000;
bool chck(int k) {
for(int i=1; i<=n; ++i) {
dp[i]=1;
for(int j=1; j<i; ++j) {
if(abs(sz[i]-sz[j])<=k) dp[i]=max(dp[i], dp[j]+1);
}
if(dp[i]>=m) return 1;
}
return 0;
}
int main(){
n=read(); m=read();
for(int i=1; i<=n; ++i) {
sz[i]=read();
}
int l=0, r=2000000000;
while(l<=r) {
int mid=l+r>>1;
if(chck(mid)) {
ans=mid;
r=mid-1;
} else l=mid+1;
}
cout<<ans;
}
T3 快速树论变换
题面:
你对树论有着深刻的认识,精通带标号无根树计数、不带标号无根树计数、带标号有根
树计数、不带标号有根树计数、带标号生成树计数、不带标号生成树计数等问题。你觉得这
些问题都弱爆了,所以你决定研究一个更加困难的问题。
给你一棵有 n 个节点的树,1 是树的根。这棵树上每个节点有一个权值,第 i 个节点的
权值是 wi。你将从 1 号节点出发,沿着树上的边行走。在一个节点 u 时,你有两种选择:
- 返回 u 的父节点。
- 在 u 的子节点中选择一个没. 有. 到. 达. 过. 的. 节点 v,行走到 v。
你边走边在节点上放石子。我们定义你能在节点 u 上放石子当且仅当如下条件被满足:
- 你当前在节点 u。
- 对 u 的任何子节点 v,v 上当前有 wv 个石子。
此外,你可以在任. 意. 时. 刻. 收回任. 意. 节. 点. 上的石子。这就是说,你收回石子时不需要走到对应节点。
现在,请你算出,对每个节点 u,如果你想在 u 上放上 wu 个石子(不要求其他节点放或不放石子),那么你至少要准备多少个石子。
思路:
儿子放完了,这个节点才能放,这个节点放完了,儿子的石头就都可以拿走给别的节点用了。
为了让这些回收的石头起到最大的作用,就要对所有儿子按照能回收石头的数量的多少从大到小排序,然后一步步往后算贡献就可以了,就是一个简单的树上 \(DP\) 加个小贪心而已。
代码:
int n, m;
int sz[1003];
int dp[1003];
int ans=2000000000;
bool chck(int k) {
for(int i=1; i<=n; ++i) {
dp[i]=1;
for(int j=1; j<i; ++j) {
if(abs(sz[i]-sz[j])<=k) dp[i]=max(dp[i], dp[j]+1);
}
if(dp[i]>=m) return 1;
}
return 0;
}
int main(){
n=read(); m=read();
for(int i=1; i<=n; ++i) {
sz[i]=read();
}
int l=0, r=2000000000;
while(l<=r) {
int mid=l+r>>1;
if(chck(mid)) {
ans=mid;
r=mid-1;
} else l=mid+1;
}
cout<<ans;
fclose(stdin);
fclose(stdout);
ByKonnyaku41377;
/*ACdate:*/
}
T4
题面:
对树上的点染色,以满足一下两个条件。
- 树上相邻两个节点的所覆盖的颜色是不同的。
- 颜色的数量也有限制,任何一个颜色被使用的次数都不能超过 \(⌊\frac{n}{3}⌋+1\)。
当然,虽然每个节点都必须有一个颜色覆盖,但不是所有的颜色都必须被使用。
你快速地完成了这个任务,但是 R 老师并不打算放过你。R 老师还要问你:一共有多少种覆盖颜色的方案满足上面的要求?
答案对 \(998244353\) 取模。
思路:
考试的时候实现出来的只有 \(16\) 的暴力枚举。
正解应该是,先不考虑限制的 \(DP\)。
然后因为最多有两种颜色超过 \(⌊\frac{n}{3}⌋+1\) ,剩下的肯定不会超过,所以直接容斥掉就行了。
代码(暴力):
struct node {
int t, n;
}e[205];
int head[105], head_size;
void ADD(int f, int t) {
e[++head_size]=(node){t, head[f]};
head[f]=head_size;
}
int n, m, u, v, lim, Ans;
int cnt[1000006];
int ans[105];
void dfs(int x) {
if(x==n+1) {
for(int i=1; i<=n; ++i) {
for(int j=head[i]; j; j=e[j].n) {
if(ans[i]==ans[e[j].t]) return ;
}
}
++Ans;
}
for(int i=1; i<=m; ++i) {
if(cnt[i]==lim) continue ;
ans[x]=i;
cnt[i]++;
dfs(x+1);
cnt[i]--;
}
}
int main(){
freopen("cover.in", "r", stdin);
freopen("cover.out", "w", stdout);
n=read(); m=read();
lim=n/3+1;
for(int i=1; i<n; ++i) {
u=read(); v=read();
ADD(u, v);
}
dfs(1);
cout<<Ans;
}