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 时,你有两种选择:

  1. 返回 u 的父节点。
  2. 在 u 的子节点中选择一个没. 有. 到. 达. 过. 的. 节点 v,行走到 v。

你边走边在节点上放石子。我们定义你能在节点 u 上放石子当且仅当如下条件被满足:

  1. 你当前在节点 u。
  2. 对 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

题面:

对树上的点染色,以满足一下两个条件。

  1. 树上相邻两个节点的所覆盖的颜色是不同的。
  2. 颜色的数量也有限制,任何一个颜色被使用的次数都不能超过 \(⌊\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;
}
posted @ 2022-10-23 21:32  Konnya_ku  阅读(38)  评论(0编辑  收藏  举报