CF1605D & CF1605E

挺有价值的两个题

D Treelabeling

题意:

E 和 S 在玩博弈游戏, E 先手 S 后手。

给一个 \(n\) 个节点的树,节点的值包含 \([1,n]\) 中的每一个值。

E 先随便选择一个点,占领它(点 u ), S 只能选择与这个点相邻的,没有被占领的点(点 v )且这两个点满足 \(u⊕v≤min(u,v)\)\(⊕\) 是异或操作

现在把树给 E 了, E 想要重新排列节点的值(树的形状不变,只是调换结点的值)来达到这个目的:

最大化第一轮能够选择的点的数量,在选了这个点之后,E 必赢。

赢:对手无路可走,你就算赢

思路:

首先\(u⊕v≤min(u,v)\)的条件一定是\(u\)\(v\)二进制的最高位都为1.

遍历整个树,容易发现只需要把最高位都为1的节点都全部放置在奇数层或者偶数层就可以使相连的两个节点\(u⊕v>min(u,v)\),并且一定能使先手赢,还能让先手的选择最多。

可以将奇数层与偶数层的节点编号分别存入两个\(vector\)内,借助\(vis\)标记已经处理过的节点(二进制拆分),必须注意的是一定要让节点个数小的那个先标记,否则会导致上界过大而遗漏节点。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=300000;
int n,m,t;
int vis[maxn],a[maxn];
vector<int> e[maxn];
vector<int> s[3];
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
inline void add(int u,int v){
	e[u].push_back(v);
}
inline void pre(){
	for(int i=1;i<=n;++i) e[i].clear(); 
	memset(vis,0,sizeof(int)*(n+10));
	s[0].clear();s[1].clear(); 
}
inline void dfs(int u,int fa,int p){
	s[p].push_back(u);
	//区分奇偶层 
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i];
		if(v==fa) continue;
		dfs(v,u,p^1);
	}
}
inline void solve(){
	dfs(1,0,0);
	if(s[0].size()>s[1].size()) swap(s[0],s[1]);
	//注意:这里必须先找最小的size,否则会导致节点遗漏
	//超出边界的情况,会漏掉一部分节点 
	int cnt=0,p=s[0].size();
	for(int i=30;i>=0;i--){
		if((p>>i)&1){
			//二进制拆分 
			for(int j=(1<<i);j<=(1<<(i+1))-1;j++){
				//hignbit的位数不可相同 要么就一起放奇层,要么就放在偶层,一定能保证必胜且选择最多 
				vis[j]=1;
				a[s[0][cnt++]]=j;
			}
		}
	}
	cnt=0;
	for(int i=1;i<=n;++i){
		if(!vis[i]) a[s[1][cnt++]]=i;
	}
	for(int i=1;i<=n;++i) printf("%d ",a[i]);
	printf("\n");
}
int main(){
	t=read();
	while(t--){
		n=read();
		pre();
		for(int i=1;i<=n-1;++i){
			int x=read(),y=read();
			add(x,y);add(y,x);
		}
		solve();
	}
	return 0;
} 
/*
1
14
5 9
9 14
7 9
4 9
9 11
9 12
2 9
9 8
9 3
13 9
10 9
1 9
9 6
*/

E Array Equalizer

题意:

Jeevan 有两个长度为 \(n\) 的数组:\(a\)\(b\)。他有以下两种操作:

  • 选择一个 \(i\)\(1 \le i \le n\)),对所有 \(1 \le i \times k \le n\),令 \(a_{ik}=a_{ik} + 1\)
  • 选择一个 \(i\)\(1 \le i \le n\)),对所有 \(1 \le i \times k \le n\),令 \(a_{ik}=a_{ik} - 1\)

不幸的是,他忘记了 \(b_1\),因此他会向你提问 \(q\) 次,每次给出一个 \(x\),表示

  • 如果 \(b_1 = x\),那么把 \(a\) 变为 \(b\) 至少需要几次操作?

思路:

\(tmp(i)=b(i)-a(i)\),通过手模(当然证明也可以,但是我没学莫反,不会)发现以下公式:

\[\textstyle \displaystyle f(i)=\sum_{d|n}u(\frac{i}{d})\times tmp(d) \]

预处理出来\(f(i)\),显然当\(tmp(1)\)改变时,其他位置会因为自身的\(u(i)\)受到影响。

分别讨论\(u(i)=1\),\(u(i)=-1\)\(u(i)=0\)的情况:

  • \(u(i)=0\),则可以直接加绝对值(因为不需要修改)。

  • \(u(i)=1\),二分查找大于等于\(-tmp(1)\)的,以下的都要取负,以上的都要取正,加绝对值。

  • \(u(i)=-1\),二分查找大于等于\(tmp(1)\)的,以下的都要取正,以上的都要取负,加绝对值。

加绝对值的操作可以利用前缀和预处理。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2005000;
int n,m,t,cnt,sum3;
int sum1[maxn],sum2[maxn]; 
int mu[maxn],prime[maxn];
vector<int> s1,s2;
bool nwp[maxn];
int a[maxn],b[maxn],tmp[maxn],f[maxn];
inline int read(){
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+(ch^48);
		ch=getchar();
	}
	return s*w;
}
inline void ms(){
	mu[1]=1;
	for(int i=2;i<=n;++i){
		if(!nwp[i]){
			mu[i]=-1;
			prime[++cnt]=i;
		}
		for(int j=1;j<=cnt;++j){
			if(i*prime[j]>n) break;
			nwp[i*prime[j]]=1;
			if(i%prime[j]==0) break;
			mu[i*prime[j]]=-mu[i];
		}
	}
}
//公式为sum(mu(i/d)*tmp(d))(d|n),然后再求和
//只有tmp(1)为变量 那么只需要关注mu(i) 
//先求一下莫比乌斯函数 
//分别讨论mu(i)=1,mu(i)=-1与mu(i)=0的情况
//若mu(i)=0,则可以直接加绝对值(因为不需要修改)
//否则当mu(i)=1时二分查找大于等于-tmp(1)的,以下的都要取负,以上的都要取正
//mu(i)=1时同理 
inline void solve(){
	n=read();ms();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=1;i<=n;++i) b[i]=read(),tmp[i]=b[i]-a[i];
	tmp[1]=0;
	for(int i=2;i<=n;++i){
		for(int j=i;j<=n;j+=i){
			f[j]+=mu[j/i]*tmp[i];
		}
	} 
	for(int i=2;i<=n;++i){
		if(mu[i]==-1) s1.push_back(f[i]);
		else if(mu[i]==1) s2.push_back(f[i]);
		else sum3+=abs(f[i]);
	}
	sort(s1.begin(),s1.end());
	sort(s2.begin(),s2.end());
	for(int i=0;i<s1.size();++i) sum1[i+1]=sum1[i]+s1[i]; 
	for(int i=0;i<s2.size();++i) sum2[i+1]=sum2[i]+s2[i];
	m=read();int ans=0;
	for(int i=1;i<=m;++i){
		int x=read();
		x-=a[1];
		ans=abs(x);
		//tmp(1)的修改 
		int y=lower_bound(s1.begin(),s1.end(),x)-s1.begin();
		ans+=x*y-2*sum1[y]+sum1[s1.size()]-x*(s1.size()-y);
		//小于下界的要减去,多于上界的要增加
		//剩下的就是维护tmp(1)的影响了,必须注意绝对值 
		int z=lower_bound(s2.begin(),s2.end(),-x)-s2.begin();
		ans+=-x*z-2*sum2[z]+sum2[s2.size()]+x*(s2.size()-z);
		printf("%lld\n",ans+sum3);
	}
}
signed main(){
	solve();
	return 0;
} 
posted @ 2022-06-30 11:35  Broken_Eclipse  阅读(38)  评论(0编辑  收藏  举报

Loading