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)\),通过手模(当然证明也可以,但是我没学莫反,不会)发现以下公式:
预处理出来\(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;
}