arc086 题解
D
题目叙述
给定 \(n\) 个数,每次将 \(a_x\) 加到 \(a_y\) 上。要求构造一种方案,使得整个序列单调不降。
题解
考虑全变成正数/负数,然后直接一遍前缀和/后缀和就可以了。
E
题目叙述
给定 \(n\) 个点的树,现在可以往点上放石子。每次做这样的操作:将每个节点的石子向父亲移动。最后移动到根节点的石子会移动到箱子里。每个点有放/不放石子两种状态,问所有情况移动到箱子里的石子总数是多少。
题解
容易发现同一层的石子并不会相互影响。如果针对第 \(i\) 层的石子进行操作,设 \(f_{u}\) 表示 \(u\) 的子树内,到这个点还有一个石子的方案,相当于所有儿子中恰好一个石子。转移是简单的。
考虑 \(f_{i,j}\) 表示 \(i\) 的子树内深度为 \(j\) 到这还有一个节点的方案。这是一个关于深度的 dp ,直接使用长链剖分优化即可。
以前我写的长链剖分都很麻烦,今天学了一种简单的写法。直接在长链剖分的 dfs 序上进行动态规划。
总结
- 长链剖分新的写法。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#define macro_expand(x) #x
#define print_macro(x) printf("%s\n",macro_expand(x))
#define FOR(i,l,r) for(int i=(l),i##ADJK=(r);i<=i##ADJK;++i)
#define ROF(i,r,l) for(int i=(r),i##ADJK=(l);i>=i##ADJK;--i)
using namespace std;
typedef long long LL;
const int MN=2e5+5,Mod=1e9+7;
int ad(int x,int y){return ((x+y)>=Mod)?(x+y-Mod):(x+y);}
int dc(int x,int y){return ((x-y)<0)?(x-y+Mod):(x-y);}
int ml(int x,int y){return (LL)x*y%Mod;}
int add(int &x,int y){return ((x+=y)>=Mod)?(x-=Mod):x;}
int dec(int &x,int y){return ((x-=y)<0)?(x+=Mod):x;}
template<typename T>bool chkmax(T &x,const T y){return (x<y)?(x=y,1):0;}
template<typename T>bool chkmin(T &x,const T y){return (x>y)?(x=y,1):0;}
int N,par[MN];
vector<int> tr[MN];
int son[MN],h[MN],buc[MN],dep[MN];
void dfs1(int u,int ff){
if(ff)dep[u]=dep[ff]+1;
++buc[dep[u]];
for(int v:tr[u]){
dfs1(v,u);
if(chkmax(h[u],h[v]+1))son[u]=v;
}
}
int f[MN][3];
void dfs2(int u,int bg){
++bg;
if(son[u])dfs2(son[u],bg);
int t=0;
int fu=bg+h[u];
for(int v:tr[u]){
if(v==son[u])continue;
dfs2(v,fu);
FOR(i,0,h[v]){
int a=f[bg+i][0],b=f[bg+i][1],c=f[bg+i][2];
f[bg+i][0]=ml(a,f[fu+i][0]);
f[bg+i][1]=ad(ml(a,f[fu+i][1]),ml(b,f[fu+i][0]));
f[bg+i][2]=ad(ml(c,ad(f[fu+i][0],f[fu+i][1])),ml(b,f[fu+i][1]));
}
chkmax(t,h[v]);
}
--bg;
FOR(i,1,t+1)add(f[bg+i][0],f[bg+i][2]),f[bg+i][2]=0;
f[bg][0]=f[bg][1]=1;
f[bg][2]=0;
}
int pw[MN];
int main(){
// freopen("c.in","r",stdin);
// freopen("c.out","w",stdout);
scanf("%d",&N);++N;
FOR(i,2,N){
scanf("%d",&par[i]),++par[i];
tr[par[i]].push_back(i);
}
dfs1(1,0);
dfs2(1,0);
int ans=0;
pw[0]=1;
FOR(i,1,N)pw[i]=ml(pw[i-1],2);
FOR(i,0,h[1])add(ans,ml(pw[N-buc[i]],f[i][1]));
printf("%d\n",ans);
// fclose(stdin);
// fclose(stdout);
return 0;
}
F
题目叙述
给定 \(n\) 个数 \(a_1\sim a_n\) ,每次可以进行全局-1或者全局/2两种操作。问在最多使用 \(k\) 次这样操作的情况下可以得到多少种最终局面。
题解
考虑这个操作最终等价于什么。首先一步基础的操作把每个数都写成二进制。相当于去掉每个数都一位或者每个数都 -1 。如果这个 -1 减得早的话,那么他的价值就小。否则就大。
所以方案可以表示为减去一个数(花费为他二进制1的数量)然后把后面若干位去掉(相当于除二),最后再减去若干个 1 。
最后这一大堆 -1 一定不会影响相邻的差。因此只考虑前面那些的影响。首先枚举 \(k\) ,就是说最后消掉多少位。然后发现,最后减掉的数其实只会对前面一位造成影响。只要比后 \(k\) 位组成的数大,前面就 -1,否则就不变。因此会带来本质变化的减掉的数的情况数比较少,只有 \(\mathcal O(n)\) 种。每种本质变化只要找到减掉的数的 popcount 尽量小的那种就可以了,然后最后可能减掉的数构成一个区间。数量加起来就可以了。
总结
- 像这种看起来操作过程不是很复杂但又有点复杂的,考虑简化他。
- 如果只有/2没有/别的的操作,那么直接化二进制。

浙公网安备 33010602011771号