10 月做题记录(二)
10.19 夜空*
被卡常 \(80\) 分。
题目链接。
感受一下,把序列顺时针写成一个环,环上有一个指针,每次指针向左或者向右移两格,把经过的数字合并。这里认为右是顺时针,左是逆时针。
首先,最终的 \(b\) 一定从指针开始,顺时针对应了原本 \(a\) 上的一个环上的连续段。问题变成给出一种分段与指针位置的合法判定,尽量简洁。
第一:如果这个指针没有遍历完所有的格子,理应会有一段连续的长为 \(1\) 的段。
第二:如果这个指针先向右走,产出了若干个 \(2\),接下来向左走,会把两个 \(2\) 合成 \(4\),而 \(4\) 在模 \(3\) 意义下还是 \(1\)。
第三:如果这个指针向左走的最后一步合并了 \(1\) 和 \(2\),那么才会产生 \(3\),这一步结束后,序列只像 \(1,1,1,1,1|3,4,4,4,4\) 这样。
再继续,中途当然会出现 \(4,4,4|2,2,3,4,4,4,4\) 这样的情况。
然而只有这个 \(3\) 是跨过了起点的,也就是考察初始的 \(a\),这个 \(3\) 包含了首尾。
于是我们猜测,判定形如:

- 粉色部分形如若干个长为 \(3k+4\) 的段。红色部分形如若干个长为 \(1\) 的段。
- 橙色部分是一个段,假设序列开头那段长度为 \(l\),结尾那段长度为 \(r\),则这个形态要求 \(l=r=0\) 或 \((l-r)\bmod 3=1\)。需要对称的再做一次(黄色部分在橙色部分顺时针 / 逆时针)。
- 黄色部分形如若干个长为 \(3k+2\) 的段。
- end 是最终指针所在的位置。
可以证明这些都是必要条件。自然给出判定后还需要构造。容易发现长度为 \(3k+2\) 的段被跨过了恰好奇数次,而长度为 \(3k+4\) 的段被跨过了恰好偶数次,于是形如:

实际上,当指针在某个位置时,你先往右动 \(2\) 步,再往左动 \(1\) 步,就把右侧的 \(4\) 个数合成了 \(1\) 个数。
而从起点出发,右侧的粉色是怎么处理的呢?右 \(2\),操作剩余的粉色,然后左 \(1\)。所以现在问题变成构造橙色部分和黄色部分的操作。
我们在碰到橙粉交界线或者黄粉边界线时,直接开始处理粉色,处理完还可以回来。
这里认为黄色在橙色逆时针侧,左侧。处理黄色时不能回来,每次都得左 \(1\),所以相当于处理橙色部分,然后到达橙色部分的左侧。
橙色其实就是有两个数 \(l,r\)(虽然这里 \(r\) 是左侧的,\(l\) 是右侧的,有点绕),每次 \(l-2,r+1\) 或者 \(l+1,r-2\),这个暴搜都可以。如果 \(l\ge 4\) 或者 \(r\ge 4\) 就先把 \(4\) 个数合成 \(1\) 个数。然后贪心即可,在 \(l=0\) 时去操作粉色。
最后到 \(l=1,r=0\) 时就可以去操作黄色。
然后就可以开始把判定运用到题目上了,枚举终点的位置,终点右侧是黄段,如果有多种可能决策的话,显然选长度最短的决策不劣,直到达到橙色部分为止。
橙色部分确定之后,粉色部分还是选最短的段(但必须是 \(>1\)),因为如果选了较长的段收回来时把红色部分同化为了粉色部分,也是没有问题的。

对 \(b\) 来说长这样,中间开始红色之后,每步显然也是尽量红色,所以关键的位置只有两个,也就是开始橙色和开始红色,这样复杂度是 \(O(n^2m^2)\) 的。
仍然是枚举起点,这次 \(O(n^2m)\) 预处理出从 \(i\) 左侧开始匹配 \([j,m]\) 的最大左端点和对应的方案,这样就能做完全部 \(nm^2\) 种判定,复杂度 \(O(n^2m)\)。
具体的,预处理 \(f_{i,j,k}\) 表示 \([1,i]\) 的一个后缀匹配 \(b\) 中的 \([j,m]\),钦定红色区间的右端点是 \(k\),对应的匹配到的左端点就是 \(f_{i,j,k}\)。
10.20 木棉*
已通过。
题目链接。
考虑还原 Prufer 序列。
数字 \(i\) 的最后一次出现在 \(e_i\),那么在 \(e_i+1\) 时刻点 \(i\) 进入叶子队列。每个时刻,我们删除叶子队列中的最小值。
考虑数字 \(x\) 的最后一次出现在 \(e_x\),在 \(e_x+1\) 时刻点 \(x\) 进入叶子队列,然而等到时刻 \(y\),点 \(x\) 才被弹出。只需要考虑所有 \(<x\) 的数,等他们都被删完,点 \(x\) 就有机会了。
这样就可以写出正确的暴力了。
发现除了 \(e_i=l-1\) 以外,其余的 \(e\) 互不相同,这意味着序列可以被分成两个部分:
- \([l,p]\) 的所有位置都被 \(<x\) 的数占用。
- \([p+1,r]\) 虽然也有位置被占用,但是不会互相冲突。
把所以 \(e_i\neq l-1\) 的位置填上后,再从小到大填 \(e_i=l-1\) 的即可。
记序列 \(a\) 上,下一个和位置 \(i\) 相同的数在位置 \(nxt_i\)。若 \(nxt_i\ge r\),则位置 \(i+1\) 被填上。不过具体来说,位置 \(i+1\) 被填上有三个条件:
- \(a_i<x\)
- \(nxt_i\ge r\)
- \(l\le i\)
且我们要找 \([l,r]\) 中不满足条件的第 \(k+1\) 个数。其中 \(k\) 是 \([0,x)\) 的数有几个没有在 \([l,r)\) 中。\(k\) 就只需要统计满足上面的条件的 \(i\) 的个数。
那么至少要做三维偏序,因为空间充裕,考虑树套树。我们按照 \(r\) 从大到小扫描线,每次加入所有 \(nxt_i\ge r\) 的数。
二分第 \(k+1\) 个数的位置,假设位置在 \(p\),求得 \(\le p\) 没填的位置有 \(s_p\) 个,则找到最小的 \(p\) 满足 \(s_p-s_{l-1}\ge k+1\) 即可。
考虑树状数组套线段树,在树状数组上倍增即可。找最大的 \(p\) 满足 \(s_p-s_{l-1}<k+1\),把 \(j\) 从大到小枚举,能跳就跳即可。
:::info[暴力]
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+6;
int a[N],ls[N],nxt[N];
bool solve(int l,int r,int x,int y){
if(x==y||x==r-l+1) return 0;
int k=x,e=l-1;
for(int i=l;i<r;i++){
if(a[i]<x&&nxt[i]>=r) k--;
if(a[i]==x) e=i;
}
int p=r;
if(k==0) p=l-1;
else{
for(int i=l;i<r;i++){
if(a[i]<x&&nxt[i]>=r) continue;
k--;
if(k<=0){
p=i;
break;
}
}
}
p++;
p=max(p,e+1);
//cout<<"pos: "<<x<<" "<<p<<endl;
return (p==r?r-l+1==y:min(a[p],r-l+1)==y);
}
vector<bool> kapok(int c,int n,int m,vector<int> aa,vector<int> l,vector<int> r,vector<int> x,vector<int> y){
vector<bool> ans;
for(int i=0;i<n;i++) a[i]=aa[i];
for(int i=0;i<n+2;i++) ls[i]=n;
for(int i=n-1;i>=0;i--){
nxt[i]=ls[a[i]];
ls[a[i]]=i;
}
for(int i=0;i<m;i++){
ans.push_back(solve(l[i],r[i],x[i],y[i])|solve(l[i],r[i],y[i],x[i]));
}
return ans;
}
/*
signed main(){
int c,n,m;
cin>>c>>n>>m;
vector<int> a,l,r,x,y;
for(int i=1,z;i<=n;i++) cin>>z,a.push_back(z);
for(int i=1,L,R,X,Y;i<=m;i++){
cin>>L>>R>>X>>Y;
l.push_back(L);
r.push_back(R);
x.push_back(X);
y.push_back(Y);
}
vector<bool> ans=kapok(c,n,m,a,l,r,x,y);
for(int i=0;i<m;i++) cout<<ans[i]<<endl;
}*/
:::
10.20 树上查询
已通过。
题目链接。
一个关键性质是,设 \(a_i\) 为 \(\mathrm{dep}_{\mathrm{LCA}(i,i+1)}\),则 \(\mathrm{dep}_{\mathrm{LCA}(l,r)}=\min_{i=l}^{r-1} a_i\)。\(l=r\) 要特判。
问题变成在 \([l,r-1]\) 里选一个大小 \(\ge k-1\) 的子区间,使得最小值最大。
假设最小值是 \(a_p\),找到区间 \([L,R]\) 是一个极长子区间,\(L\le p\le R\),且 \([L,R]\) 的最小值是 \(a_p\)。问题变成对于查询 \([l,r]\),找到 \(|[L,R]\cap[l,r]|\ge k\) 中最大的 \(a_p\)。
分类讨论,有两种情况:
- \(R\ge r\),且 \(L\le r-k+1\)。
- \(l+k-1\le R\le r\),且 \(R-L+1\ge k\)。
第一个直接树状数组,第二个按照 \(k\) 从大到小扫描线,线段树即可。
10.21 序列变换*
已通过。
题目链接。
先考虑操作的形态再考虑如何最优化或计数。
一次操作必会置 \(0\),而 \(0\) 的两半是独立的。
更进一步,考察最终序列上的每一个非 \(0\) 数,都是由一个“封闭的区间”得来的。
若区间 \([l,r]\) 是一个封闭的区间,注意到,其中奇数位置的和减去偶数位置的和永远不变,所以最后留下的那个数的数值是唯一确定的。位置的奇偶性也被确定了。
然而我们仍然不知道有哪些位置 \(i\) 可以承载最后留下的数。不过可以这样考虑,从 \(l\) 开始往右推,最多可以推到第 \(x\) 个位置,从 \(r\) 开始往左推,最多可以推到第 \(y\) 个位置。那么 \([l,i]\) 要可以被推到 \(i\),\([i+1,r]\) 要可以被推到 \(i+1\)。那么只要 \(i\) 的奇偶性正确就必定可以被留下。
据此可以写出 \(40\) 分代码。找到 \([y-1,x]\) 中奇偶性正确的位置上的最小值即可。然后再做一个暴力 dp。
:::info[\(40\) 分代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=5007,inf=1e9+7;
int n,a[N],b[N],c[N];
int L[N],R[N];
int mn[2][N][20],Lo[N];
typedef long long ll;
ll su[N],f[N],v[N];
void ST(int o){
Lo[0]=-1;
for(int i=1;i<=n;i++) Lo[i]=Lo[i>>1]+1;
for(int i=1;i<=n;i++) mn[o][i][0]=(i%2==o?b[i]:inf);
for(int j=1;j<=Lo[n];j++){
for(int i=1;i+(1<<j)-1<=n;i++){
mn[o][i][j]=min(mn[o][i][j-1],mn[o][i+(1<<j-1)][j-1]);
}
}
}
int qry(int o,int a,int b){
if(a>b) return inf;
int k=Lo[b-a+1];
return min(mn[o][a][k],mn[o][b-(1<<k)+1][k]);
}
void subt(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1;i<=n;i++){
L[i]=i;
int x=a[i];
for(int j=i+1;j<=n;j++){
if(x<=a[j]){
L[i]=j;
x=a[j]-x;
}else break;
}
}
for(int i=n;i>=1;i--){
R[i]=i;
int x=a[i];
for(int j=i-1;j>=1;j--){
if(x<=a[j]){
R[i]=j;
x=a[j]-x;
}else break;
}
}
ST(0),ST(1);
for(int i=1;i<=n;i++) su[i]=su[i-1]+(i&1?a[i]:-a[i]),v[i]=v[i-1]+b[i];
for(int i=1;i<=n;i++){
f[i]=0;
for(int j=0;j<i;j++){
int l=R[i]-1,r=L[j+1];
l=max(l,j+1),r=min(r,i);
ll s=su[i]-su[j];
if(s==0) continue;
int w=qry(s>0?1:0,l,r);
if(w==inf) continue;
f[i]=max(f[i],f[j]+v[i]-v[j]-w);
}
}
cout<<f[n]<<" "<<0<<endl;
}
}
int c,t;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c>>t;
while(t--) tymxz::subt();
return 0;
}
:::
如果直接把最优化的代码套到计数,显然会算重。但是我们要考虑什么时候才会算重。
假设 \(r\) 可以从 \(r_1\) 扩张成 \(r_2\),那么 \([r_1+1,r_2]\) 一定是一个可以完全消除的区间。
钦定右端点需要最小,再加上一些特判即可。
:::info[\(100\) 分代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=5007,mod=1e9+7;
int n,a[N],b[N],c[N];
int L[N],R[N];
int mn[2][N][20],Lo[N];
typedef long long ll;
ll su[N],f[N],v[N];
int deg[2][N],g[N];
int qpow(int a,int b){
int r=1;
while(b){
if(b&1) r=1ll*r*a%mod;
a=1ll*a*a%mod;
b=b>>1;
}
return r;
}
void ST(int o){
Lo[0]=-1;
for(int i=1;i<=n;i++) Lo[i]=Lo[i>>1]+1;
for(int i=1;i<=n;i++) mn[o][i][0]=(i%2==o?b[i]:mod);
for(int j=1;j<=Lo[n];j++){
for(int i=1;i+(1<<j)-1<=n;i++){
mn[o][i][j]=min(mn[o][i][j-1],mn[o][i+(1<<j-1)][j-1]);
}
}
for(int i=1;i<=n;i++) deg[o][i]=(deg[o][i-1]+(i%2==o?qpow(c[i],mod-2):0))%mod;
}
int qry(int o,int a,int b){
if(a>b) return mod;
int k=Lo[b-a+1];
return min(mn[o][a][k],mn[o][b-(1<<k)+1][k]);
}
void subt(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1;i<=n;i++){
L[i]=i;
int x=a[i];
for(int j=i+1;j<=n;j++){
if(x<=a[j]){
L[i]=j;
x=a[j]-x;
}else break;
}
}
for(int i=n;i>=1;i--){
R[i]=i;
int x=a[i];
for(int j=i-1;j>=1;j--){
if(x<=a[j]-(i!=n)){
R[i]=j;
x=a[j]-x;
}else break;
}
}
ST(0),ST(1);
for(int i=1;i<=n;i++) su[i]=su[i-1]+(i&1?a[i]:-a[i]),v[i]=v[i-1]+b[i];
int prod=1;
for(int i=1;i<=n;i++) prod=1ll*prod*c[i]%mod;
g[0]=1;
for(int i=1;i<=n;i++){
f[i]=g[i]=0;
for(int j=0;j<i;j++){
int l=R[i]-1,r=L[j+1];
l=max(l,j+1),r=min(r,i);
ll s=su[i]-su[j];
if(s==0) continue;
int o=(s>0?1:0);
int w=qry(o,l,r);
if(w==mod) continue;
f[i]=max(f[i],f[j]+v[i]-v[j]-w);
g[i]=(g[i]+1ll*g[j]*(deg[o][r]-deg[o][l-1]+mod))%mod;
}
}
if(L[1]==n&&su[n]==0) g[n]=(g[n]+1)%mod;
g[n]=1ll*g[n]*prod%mod;
cout<<f[n]<<" "<<g[n]<<endl;
}
}
int c,t;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c>>t;
while(t--) tymxz::subt();
return 0;
}
:::
10.21 数字树
已通过。
题目链接。
题解待补。
10.21 大雨*
已通过。
题目链接。
考虑 \(a_n=0\) 的部分分,此时如果 \(a_n\) 不被修改,则所有数都要被改为 \(0\),这是平凡情况,判一下就好。
否则 \(a_n\) 会被修改。把每一段的极高位(\(>30\))修改使得我们不需要考虑相邻段之间的贡献。
段内,考虑任意的 \(a_i,a_{i+1}\),对于这一段涉及的异或 \(x\),只需要 \(x\) 的第 \(\mathrm{highbit}(a_i\oplus a_{i+1})\) 是 \(0/1\) 即可。限制是单调的,双指针即可。
记这样求得的答案为 \(w\),我们只需要判断真实答案是不是 \(w-1\) 即可。
更进一步,我们给前 \(i\) 个数做划分,最后一个数是 \(j\),段数是 \(g_{i,j}\)。显然 \(g_{i,j}\ge f_i\)。其中 \(f_i\) 是上面那个无限制的 dp 的段数。
若 \(g_{i,j}>f_i\),则后面就算无限制,答案也只会求得 \(w\) 的答案。
所以我们只考虑 \(g_i\) 表示前 \(i\) 个数段数 \(=f_i\) 的方案中,最后一位的最小值。
考虑如何从 \(g_j\to g_i\)。先找出 \([j+1,i]\) 中对 \(x\) 的限制。随后 \(g_j\le a_{j+1}\oplus x\)。在此基础上要最小化 \(a_i\oplus x\)。
钦定 \(g_j\) 和 \(a_{j+1}\oplus x\) 在高 \(p\) 位都是相同的,第 \(p+1\) 位 \(g_j\) 是 \(0\),\(a_{j+1}\oplus x\) 是 \(1\) 即可。这显然又是一些限制。
对于被钦定的部分的合并可以用位运算。所以 \(O(n^2\log V)\) 可以写。拼上 \(a_n=0\) 有 \(76\) 分,赛时做到这就可以跑路了。
看眼题解,发现怎么直接在 trie 树上打标记就做完了。
还是从查询出发。如果总节点数足够少,查询时,如果我们走到一棵子树里,发现这棵子树里居然没有合法点,那就可以直接把这整棵子树删除了。均摊就是对的。
修改时,我们有一些子树是满的,我们把满的子树缩成一个点,这个点下方只考虑满的情况即可。
对于一个满点,记录其左端点 \(j\),并且需要知道左端点 \(j\) 对应的限制。差分后,限制可以表示成两个二进制数,表示哪些位置不能是 \(0\),哪些位置不能是 \(1\)。满点可以快速判断内部有没有合法点。如果没有就可以删去这个满点。
所以其实根本不需要打标记,其实就是维护虚树。。。
现在我们要知道 \(j\) 是否可以转移到 \(i\)。需要处理出 \([j+1,i]\) 的所有限制,把禁用 \(0\) 和禁用 \(1\) 的限制分别找出即可,就是取 or。因为 \(dp_i=dp_j+1\) 所以这部分时间复杂度 \(O(n)\)。
:::info[代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=1e6+7,LG=30;
int n;
int a[N],vs[N],L[N];
int v[30][2],cnt;
void work(int p,int x){
int j=vs[p];
if(j==-1) return;
int o=(a[p]>>j)&1;
if(v[j][0]&&v[j][1]) cnt--;
v[j][o]+=x;
if(v[j][0]&&v[j][1]) cnt++;
}
int dp[N],g[N];
int trie[N*LG][2],tot;
int c[N*LG];
void chmax(int &x,int y){
if(x<y) x=y;
}
void init(){
tot=1;
c[tot]=0;
}
void ins(int &r,int a,int b,int id){//a<=b^x
if(r==0) r=tot=1,trie[tot][0]=trie[tot][1]=c[tot]=0;
int p=1;
for(int i=LG-1;i>=0;i--){
int a1=(a>>i)&1,b1=(b>>i)&1;
if(a1==0){
if(!trie[p][b1^1]) trie[p][b1^1]=++tot,trie[tot][0]=trie[tot][1]=c[tot]=0;
chmax(c[trie[p][b1^1]],id+1);
}
if(!trie[p][a1^b1]) trie[p][a1^b1]=++tot,trie[tot][0]=trie[tot][1]=c[tot]=0;
p=trie[p][a1^b1];
}
chmax(c[p],id+1);
}
const int inf=(1<<30)+1;
int dl[N][2],dr[N][2],nw,X,ans;
int solve(int u,int v,int x,int y){
if(dp[u]+1!=dp[v]) return inf;
x|=dl[u][0]|dr[v][0];
y|=dl[u][1]|dr[v][1];
if(x&y) return inf;
return (x^X)&(x|y);
}
void qry(int &p,int dep,int x,int y){
if(p==0) return;
if(c[p]>0){
int j=c[p]-1;
int w=solve(j,nw,x,y);
if(w<inf) ans=min(ans,w);
else c[p]=0;
}
if(dep==-1){
if(c[p]==0) p=0;
return;
}
int i=(X>>dep)&1;
qry(trie[p][i],dep-1,x|((i!=0)<<dep),y|((i!=1)<<dep));
if(trie[p][i]==0){
i^=1;
qry(trie[p][i],dep-1,x|((i!=0)<<dep),y|((i!=1)<<dep));
}
if(trie[p][0]==0&&trie[p][1]==0&&c[p]==0) p=0;
return;
}
inline int turn(int p,int o){
p++;
if(vs[p]==-1) return 0;
if(o!=((a[p]>>vs[p])&1)) return 0;
return (1<<vs[p]);
}
bool chk(){
tot=0;
int rt=0;
for(int l=0;l<=n;){
int r=l;
while(r<=n&&dp[r]==dp[l]) r++;
for(int i=l;i<r;i++){
if(i>0){
nw=i,ans=inf,X=a[i];
qry(rt,LG-1,0,0);
g[i]=ans;
}
}
rt=0;
for(int i=l;i<r;i++){
if(i<n){
if(g[i]<inf) ins(rt,g[i],a[i+1],i);
}
}
l=r;
}
for(int i=n-1;i>=0;i--){
if(dp[i]<dp[n]&&g[i]<=a[i+1]) return 1;
if(a[i]>a[i+1]) return 0;
}
return 0;
}
void subt(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cnt=0;
for(int i=0;i<LG;i++) v[i][0]=v[i][1]=0;
for(int i=2;i<=n;i++){
if(a[i]!=a[i-1]) vs[i]=__lg(a[i]^a[i-1]);
else vs[i]=-1;
}
int l=1;
for(int i=1;i<=n;i++){
if(i>1) work(i,1);
while(l<i&&cnt>0) work(l+1,-1),l++;
dp[i]=dp[l-1]+1;
L[i]=l;
}
for(int i=0;i<=n;i++) for(int o=0;o<2;o++){
if(i>1&&dp[i]==dp[i-1]) dr[i][o]=dr[i-1][o]|turn(i-1,o);
else dr[i][o]=0;
}
for(int i=n;i>=0;i--) for(int o=0;o<2;o++){
if(i<n&&dp[i]==dp[i+1]) dl[i][o]=dl[i+1][o]|turn(i+1,o);
else dl[i][o]=0;
}
int ans=dp[n];
if(chk()) ans--;
cout<<ans<<endl;
}
}
int c,t;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c>>t;
while(t--) tymxz::subt();
return 0;
}
:::
10.22 传送
已通过。
题目链接。
策略显然是对于深度前 \(k\) 小的点往根走,其他点随机跳。
步数是 \(\frac{n}{k}+\frac{s}{k}\),其中 \(s\) 表示这些点的深度之和。
这显然是分数规划问题,设 \(\frac{n+s}{k}\le p\),则 \(n+s-pk\le 0\)。选的是一个前缀,而且容易发现同一个深度的点只会被一起选。
设深度 \(\le i\) 的深度和为 \(s_i\),点数为 \(a_i\),则要最优化 \(\frac{n+s_i}{a_i}\) 的值。如果取 \(i< p\le i+1\),让 \(\le i\) 的点都能被选而剩下的都不被选,且分数规划式 \(\le 0\),取最小的这样的 \(i\) 即可。
查询邻域用点分治,这样是 \(O(n\log^2 n)\)。注意到相邻两点的答案差距不超过 \(1\),先求出根的答案,贡献到儿子时,在父亲的答案左右范围内搜一下即可。
时间复杂度 \(O(n\log n)\)。
10.22 Two in the Range
题目链接。
你觉得你是题吗?
赛时做法:
首先排序之后扫一遍,假设 \([x,y] \subseteq [l,r]\),那么 \([l,x-1]\) 和 \([y+1,r]\) 全都是 \(0\)。把 \([l,r]\) 丢掉。
这样剩下的区间只有相交和相离的关系。把剩下的区间排序并重新标号,把所有已经是 \(0\) 的位置删除。
转置一下,选择位置 \(i\) 赋值为 \(1\),会导致一个区间的查询都被 \(+1\),要让每个查询都是 \(2\),也就是一个覆盖问题。
这个和 NOI D1T1 是差不多的,其实就是分成两组,每一组都是一些区间 \([x,y]\),能够不重不漏的覆盖 \([1,n]\)。连边 \(x\to y+1\),相当于找两条 \(1\) 到 \(m+1\) 的路径不经过重复的边。可以跑流。只要能够增广两次即可。
正解还不会。
10.22 信号汇聚
要让所有信号汇聚到一点,相当于找到一棵内向树。容易发现,问题其实等价于构造一棵树,使得存在一点到所有点的距离 \(\le h\)。
对其模拟广搜,第 \(i\) 层的点要到达前 \(i-1\) 层的点,在序列上,前 \(i-1\) 层的点把序列划分成了若干个区间,每个区间内是独立的,所以考虑区间 dp。
设 \(f_{i,j,k,0/1}\) 表示区间 \([i,j]\) 有 \(k\) 层,是否可以连到 \(i-1\) 或 \(j+1\)。
假设可以连到右边,找到最左的根,使其连到右边,那么在这个根右边仍然可以连到右边,两半是同级的。但是,这个根的右边分出来的小区间要可以连到这个根。
那么我们按照 \(k-1\to k\) 转移,容易发现小区间一定可以连到左右两边,大区间 \([l,r]\),\([l,c]\) 的根到左边,\([c+1,r]\) 的根到右边即可。
时间复杂度 \(O(n^4)\)。
10.24 工业系统*
已通过。
题目链接。
有性质:只接受儿子的产物即可。证明很简单。
先求出以 \(R\) 为根,所有子树从小到大的排名 \(rk_i\),用于后续的比较。
有性质:子树高度越高,排名越大。证明可以归纳证明。
按照高度分层求排名,先求出高度为 \(d\) 的子树的排名,再求出 \(d+1\) 的。相当于给字符串排序,用 stable_sort,复杂度 \(O(n\log n)\)。
后续要考虑反向的子树,那么我们以直径中点为根。如果直径中点在边上,建一个虚点。有性质:反向的子树比正向的子树要大。证明:因为反向的子树的子树高度更大。
再求出以 \(x\) 为根,\(y\) 的排名。
若以 \(R\) 为根 \(y\) 是 \(x\) 的祖先,则答案显然是 \(dep_x-dep_y+1\)。
否则,除了原本就比 \(y\) 的排名大的子树以外,还要加上 \(x\) 祖先链上,原本小于等于 \(y\) 的子树的贡献。
当 \(o_x=o_y=0\) 时,\(x\) 祖先链上的大小关系显然是单调的,倍增即可。
当 \(o_xo_y=0\) 时,先把查询拆成直链,那么 \(f(x,y)\) 也是有单调性的,可以二分。\(O(\log^2 n)\)。
当 \(o_x=o_y=1\) 时,判掉 \(y\) 是 \(x\) 的祖先和 \(x\) 是 \(y\) 的祖先的情况。具体来说,把 \(x\) 拆成到根的直链,如果 \(y\) 在直链上,直接计算贡献。
若 \(f(R,y)>r\),则一定有 \(f(x,y)>r\)。判掉这些点。
考虑观察固定 \(y\),哪些 \(f(x,y)\le r\)。所有 \(rk_o>rk_y\) 构成一个包含 \(R\) 的连通块。\(x\) 的 \(r-f(R,y)\) 级祖先 \(o\) 一定要是 \(rk_o>rk_y\)。
\(y\) 在一条直链上随时间增加从上到下滑动,\(f(R,y)\) 增大,合法的 \(x\) 一定减少。若考察包含 \(R\) 的直链,前一段时间内全部 \(x\) 都合法,后一段时间内,贡献分成 \(r-f(R,y)\) 和 \(rk_x>rk_y\) 两种贡献。前一个可以直接求,后一个是重点。
形如给你两条到根链 \(A,B\),求 \(\sum_{i\in A}\sum_{j\in B} rk_i>rk_j\)。按照 \(rk\) 的值来分块,有不同块和相同块两种贡献。不同块,只需要在树上做从根到每个点的前缀和。相同块,取出每一对点,在 dfn 上对应了关于 \(A,B\) 的两个区间,
逐块处理,容易划分等价类,二维前缀和,并对询问快速查询。
代码不想写,那咋办。
写完 \(o_xo_y=0\) 可以获得 \(60\) 分。
:::info[\(60\) 分代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=1e5+7;
int n,m,rt,xu;
vector<int> g[N];
namespace radi{
int dp1[N],dp2[N];
void dfs1(int u,int fa){
for(int v:g[u]){
if(v==fa) continue;
dfs1(v,u);
dp1[u]=max(dp1[u],dp1[v]+1);
}
}
void dfs2(int u,int fa){
int mx=dp2[u],se=dp2[u],mxp=0;
for(int v:g[u]){
if(v==fa) continue;
if(dp1[v]+1>mx) se=mx,mx=dp1[v]+1,mxp=v;
else if(dp1[v]+1>se) se=dp1[v]+1;
}
for(int v:g[u]){
if(v==fa) continue;
if(v==mxp) dp2[v]=se+1;
else dp2[v]=mx+1;
dfs2(v,u);
}
}
void del(int u,int v){
vector<int> h;
for(int t:g[u]) if(v!=t) h.push_back(t);
g[u].swap(h);
}
void q(){
dfs1(1,0);
dfs2(1,0);
int mx=0;
for(int i=1;i<=n;i++) dp1[i]=max(dp1[i],dp2[i]),mx=max(mx,dp1[i]);
//cerr<<mx<<endl;
mx=(mx+1)/2;
vector<int> h;
for(int i=1;i<=n;i++) if(dp1[i]==mx) h.push_back(i);
int s=(int)(h.size());
if(s==1) rt=h[0];
else{
int u=h[0],v=h[1];
del(u,v),del(v,u);
g[n+1].push_back(u);
g[n+1].push_back(v);
rt=n+1;
n++;
xu=1;
}
//for(int i=1;i<=n;i++) for(int j:g[i]) cerr<<i<<" "<<j<<endl;
//cerr<<rt<<endl;
cerr<<xu<<endl;
}
}
const int LG=19;
int dep[N],mxd[N],dfn[N],rnk[N],rk[N],siz[N];
int f[N][LG],fR[N];
namespace c{
int tot;
void dfs(int u,int fa){
dfn[u]=++tot;
rnk[tot]=u;
dep[u]=dep[fa]+1;
f[u][0]=fa;
siz[u]=1;
for(int j=1;j<LG;j++) f[u][j]=f[f[u][j-1]][j-1];
for(int v:g[u]){
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
mxd[u]=max(mxd[u],mxd[v]+1);
}
}
}
namespace s{
vector<int> tr[N];
vector<int> gen[N],gn[N];
int comp(int x,int y){
int nx=(int)(tr[x].size()),ny=(int)(tr[y].size());
for(int i=0;i<min(nx,ny);i++){
if(tr[x][i]<tr[y][i]) return -1;
if(tr[x][i]>tr[y][i]) return 1;
}
if(nx!=ny) return (nx<ny?-1:1);
return 0;
}
bool cmp(int x,int y){
return comp(x,y)==-1;
}
void q(){
for(int i=1;i<=n;i++) gen[mxd[i]].push_back(i);
int nw=0;
for(int i=0;i<=n;i++){
for(int j:gen[i]){
for(int k:g[j]) if(mxd[k]<mxd[j]) tr[j].push_back(rk[k]);
sort(tr[j].begin(),tr[j].end(),greater<int>());
}
stable_sort(gen[i].begin(),gen[i].end(),cmp);
int lst=0;
for(int j:gen[i]){
if(lst==0||comp(lst,j)==-1) nw++;
rk[j]=nw,lst=j;
}
}
for(int i=1;i<=n;i++) gn[rk[i]].push_back(i);
int sum=0;
for(int i=nw;i>=1;i--){
for(int j:gn[i]) fR[j]=1+sum-xu;
sum+=(int)(gn[i].size());
}
}
}
namespace work{
int qk(int u,int k){
for(int j=LG-1;j>=0;j--){
if((1<<j)<=k){
u=f[u][j];
k-=(1<<j);
}
}
return u;
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int j=LG-1;j>=0;j--){
if(dep[f[u][j]]>=dep[v]){
u=f[u][j];
}
}
if(u==v) return u;
for(int j=LG-1;j>=0;j--){
if(f[u][j]!=f[v][j]){
u=f[u][j];
v=f[v][j];
}
}
return f[u][0];
}
int chk(int x,int y,int r){
if(fR[y]>r) return 0;
int o=qk(x,r-fR[y]);
return (o==0||rk[o]>rk[y]);
}
int q00(int x,int y,int r){
if(dfn[y]<=dfn[x]&&dfn[x]<=dfn[y]+siz[y]-1) return (dep[x]-dep[y]+1)<=r;
return chk(x,y,r);
}
int q01(int x,int t,int r){
int l=lca(x,t);
int ans=min(dep[x]-dep[l]+1,r);
for(int j=LG-1;j>=0;j--){
int p=f[t][j];
if(p==0) continue;
if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
if(chk(x,p,r)) continue;
t=p;
}
if(t==l||chk(x,t,r)) ans+=dep[t]-dep[l];
else ans+=dep[t]-dep[l]-1;
if(xu&&l==rt&&q00(x,rt,r)) ans--;
return ans;
}
int q10(int s,int y,int r){
int l=lca(s,y);
int ans=(dep[y]-dep[l])*(fR[y]<=r)+1;
if(y==l){
ans=min(dep[s]-dep[l]+1,r);
return ans;
}
for(int j=LG-1;j>=0;j--){
int p=f[s][j];
if(p==0) continue;
if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
if(chk(p,y,r)) continue;
s=p;
}
if(s==l||chk(s,y,r)) ans+=dep[s]-dep[l];
else ans+=dep[s]-dep[l]-1;
if(xu&&l==rt&&fR[y]<=r) ans--;
return ans;
}
}
void subt(){
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
radi::q();
c::dfs(rt,0);
s::q();
cin>>m;
for(int i=1,s,t,ox,oy,r;i<=m;i++){
cin>>s>>t>>ox>>oy>>r;
if(ox==0&&oy==0) cout<<work::q00(s,t,r)<<endl;
else if(ox==0&&oy==1) cout<<work::q01(s,t,r)<<endl;
else if(ox==1&&oy==0) cout<<work::q10(s,t,r)<<endl;
else cout<<0<<endl;
}
}
}
int c;
signed main(){
//freopen("industry6.in","r",stdin);
//freopen("industry.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c;
tymxz::subt();
return 0;
}
:::
:::info[\(100\) 分代码]
#include <bits/stdc++.h>
using namespace std;
namespace tymxz{
const int N=1e5+7;
int n,m,rt,xu;
vector<int> g[N];
namespace radi{
int dp1[N],dp2[N];
void dfs1(int u,int fa){
for(int v:g[u]){
if(v==fa) continue;
dfs1(v,u);
dp1[u]=max(dp1[u],dp1[v]+1);
}
}
void dfs2(int u,int fa){
int mx=dp2[u],se=dp2[u],mxp=0;
for(int v:g[u]){
if(v==fa) continue;
if(dp1[v]+1>mx) se=mx,mx=dp1[v]+1,mxp=v;
else if(dp1[v]+1>se) se=dp1[v]+1;
}
for(int v:g[u]){
if(v==fa) continue;
if(v==mxp) dp2[v]=se+1;
else dp2[v]=mx+1;
dfs2(v,u);
}
}
void del(int u,int v){
vector<int> h;
for(int t:g[u]) if(v!=t) h.push_back(t);
g[u].swap(h);
}
void q(){
dfs1(1,0);
dfs2(1,0);
int mx=0;
for(int i=1;i<=n;i++) dp1[i]=max(dp1[i],dp2[i]),mx=max(mx,dp1[i]);
//cerr<<mx<<endl;
mx=(mx+1)/2;
vector<int> h;
for(int i=1;i<=n;i++) if(dp1[i]==mx) h.push_back(i);
int s=(int)(h.size());
if(s==1) rt=h[0];
else{
int u=h[0],v=h[1];
del(u,v),del(v,u);
g[n+1].push_back(u);
g[n+1].push_back(v);
rt=n+1;
n++;
xu=1;
}
//for(int i=1;i<=n;i++) for(int j:g[i]) cerr<<i<<" "<<j<<endl;
//cerr<<rt<<endl;
cerr<<xu<<endl;
}
}
const int LG=19;
int dep[N],mxd[N],dfn[N],rnk[N],siz[N];
int f[N][LG],fR[N];
namespace c{
int tot;
void dfs(int u,int fa){
dfn[u]=++tot;
rnk[tot]=u;
dep[u]=dep[fa]+1;
f[u][0]=fa;
siz[u]=1;
for(int j=1;j<LG;j++) f[u][j]=f[f[u][j-1]][j-1];
for(int v:g[u]){
if(v==fa) continue;
dfs(v,u);
siz[u]+=siz[v];
mxd[u]=max(mxd[u],mxd[v]+1);
}
}
}
namespace s{
int rk[N];
vector<int> tr[N];
vector<int> gen[N],gn[N];
int comp(int x,int y){
int nx=(int)(tr[x].size()),ny=(int)(tr[y].size());
for(int i=0;i<min(nx,ny);i++){
if(tr[x][i]<tr[y][i]) return -1;
if(tr[x][i]>tr[y][i]) return 1;
}
if(nx!=ny) return (nx<ny?-1:1);
return 0;
}
bool cmp(int x,int y){
return comp(x,y)==-1;
}
void q(){
for(int i=1;i<=n;i++) gen[mxd[i]].push_back(i);
int nw=0;
for(int i=0;i<=n;i++){
for(int j:gen[i]){
for(int k:g[j]) if(mxd[k]<mxd[j]) tr[j].push_back(rk[k]);
sort(tr[j].begin(),tr[j].end(),greater<int>());
}
stable_sort(gen[i].begin(),gen[i].end(),cmp);
int lst=0;
for(int j:gen[i]){
if(lst==0||comp(lst,j)==-1) nw++;
rk[j]=nw,lst=j;
}
}
for(int i=1;i<=n;i++) gn[rk[i]].push_back(i);
int sum=0;
for(int i=nw;i>=1;i--){
for(int j:gn[i]) fR[j]=1+sum-xu;
sum+=(int)(gn[i].size());
}
}
}
using s::rk;
using s::gn;
typedef long long ll;
ll res[N];
namespace work{
int U,V;
int qk(int u,int k){
for(int j=LG-1;j>=0;j--){
if((1<<j)<=k){
u=f[u][j];
k-=(1<<j);
}
}
return u;
}
int lca(int u,int v){
bool fl=0;
if(dep[u]<dep[v]) swap(u,v),fl=1;
for(int j=LG-1;j>=0;j--){
if(dep[f[u][j]]>=dep[v]){
u=f[u][j];
}
}
if(u==v) return u;
for(int j=LG-1;j>=0;j--){
if(f[u][j]!=f[v][j]){
u=f[u][j];
v=f[v][j];
}
}
U=u,V=v;
if(fl) swap(U,V);
return f[u][0];
}
int chk(int x,int y,int r){
if(fR[y]>r) return 0;
int o=qk(x,r-fR[y]);
return (o==0||rk[o]>rk[y]);
}
int q00(int x,int y,int r){
if(dfn[y]<=dfn[x]&&dfn[x]<=dfn[y]+siz[y]-1) return (dep[x]-dep[y]+1)<=r;
return chk(x,y,r);
}
int q01(int x,int t,int r){
int l=lca(x,t);
int ans=min(dep[x]-dep[l]+1,r);
for(int j=LG-1;j>=0;j--){
int p=f[t][j];
if(p==0) continue;
if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
if(chk(x,p,r)) continue;
t=p;
}
if(t==l||chk(x,t,r)) ans+=dep[t]-dep[l];
else ans+=dep[t]-dep[l]-1;
if(xu&&l==rt&&q00(x,rt,r)) ans--;
return ans;
}
int q10(int s,int y,int r){
int l=lca(s,y);
int ans=(dep[y]-dep[l])*(fR[y]<=r)+1;
if(y==l){
ans=min(dep[s]-dep[l]+1,r);
return ans;
}
for(int j=LG-1;j>=0;j--){
int p=f[s][j];
if(p==0) continue;
if(dfn[p]<=dfn[l]&&dfn[l]<=dfn[p]+siz[p]-1) continue;
if(chk(p,y,r)) continue;
s=p;
}
if(s==l||chk(s,y,r)) ans+=dep[s]-dep[l];
else ans+=dep[s]-dep[l]-1;
if(xu&&l==rt&&fR[y]<=r) ans--;
return ans;
}
ll chain(int a,int b,int r,int v=0,int u=0){
ll ans=0;
int len=dep[b]-dep[a]+1;
if(len<=r) ans+=1ll*len*(len+1)/2;
else ans+=1ll*r*(r+1)/2+1ll*(len-r)*r;
for(int j=LG-1;j>=0;j--){
int p=f[b][j];
if(p==0) continue;
if(fR[p]<=r) continue;
b=f[b][j];
}
if(fR[b]>r) b=f[b][0];
if(b&&dep[b]>=dep[a]){
len=dep[b]-dep[a];
ans+=1ll*len*(len+1)/2;
}
if(v&&b&&dep[b]>=dep[u]) ans-=1ll*(dep[b]-dep[u])*dep[u];
return ans;
}
struct qy{
int a,b,id,w;
}qr[N*4];
int tot;
ll we[N];
void solve(int t,int s,int l,int r,int id){
for(int j=LG-1;j>=0;j--){
int p=f[t][j];
if(p==0) continue;
if(fR[p]<=r) continue;
t=p;
}
if(t&&fR[t]>r) t=f[t][0];
if(t==0) return;
if(dfn[t]<=dfn[l]&&dfn[l]<=dfn[t]+siz[t]-1) return;
int p=t;
for(int j=LG-1;j>=0;j--){
int q=f[p][j];
if(q==0) continue;
if(dfn[q]<=dfn[l]) continue;
if(chk(s,q,r)) continue;
p=q;
}
if(!chk(s,p,r)) p=f[p][0];
//cerr<<t<<" "<<p<<" "<<l<<endl;
res[id]+=1ll*(dep[p]-dep[l])*dep[s];
res[id]-=1ll*(we[t]-we[p]);
res[id]+=1ll*(dep[t]-dep[p])*r;
qr[++tot]=(qy){s,t,id,1};
qr[++tot]=(qy){s,p,id,-1};
}
int fa[N];
void init(){
for(int i=1;i<=n;i++){
int u=rnk[i];
fa[u]=f[u][0];
we[u]=fR[u]+we[fa[u]];
}
}
void q11(int s,int t,int r,int id){
if(s==t){
res[id]=1;
return;
}
int l=lca(s,t);
if(s==l){
res[id]=chain(s,t,r);
return;
}
if(t==l){
res[id]=chain(t,s,r);
return;
}
if(xu&&l==rt) res[id]=chain(U,s,r,1,l)+chain(V,t,r,1,l);
else res[id]=chain(l,s,r,1,l)+chain(l,t,r,1,l)-1;
solve(t,s,l,r,id);
solve(s,t,l,r,id);
return;
}
const int B=400;
int s1[N],s2[N],fl[N];
int su[B*2+7][B*2+7];
void solve(int L,int R){
//cerr<<"chk: "<<L<<" "<<R<<endl;
for(int i=1;i<=n;i++) s2[i]=0;
for(int i=L;i<=R;i++) for(int u:gn[i]) s2[u]++;
for(int i=1;i<=n;i++) s2[rnk[i]]+=s2[fa[rnk[i]]];
for(int i=1;i<=tot;i++){
ll ans=1ll*s2[qr[i].a]*s1[qr[i].b];
res[qr[i].id]+=(qr[i].w==1?ans:-ans);
}
for(int i=1;i<=n;i++) s1[i]+=s2[i];
if(L==R) return;
for(int i=1;i<=n+1;i++) fl[i]=0;
fl[n+1]=1;
for(int i=L;i<=R;i++) for(int u:gn[i]){
fl[dfn[u]]=1,fl[dfn[u]+siz[u]]=1;
//cerr<<dfn[u]<<" "<<dfn[u]+siz[u]<<endl;
}
for(int i=1;i<=n+1;i++) fl[i]+=fl[i-1];
//for(int i=1;i<=n+1;i++) cerr<<i<<" "<<fl[i]<<endl;
//cerr<<"?"<<fl[n]<<endl;
for(int i=1;i<=fl[n];i++){
for(int j=1;j<=fl[n];j++){
su[i][j]=0;
}
}
for(int i=L;i<=R;i++) for(int u:gn[i]){
for(int j=i+1;j<=R;j++) for(int v:gn[j]){
//cerr<<"pair: "<<u<<" "<<v<<endl;
int a=dfn[v],b=dfn[v]+siz[v];
int c=dfn[u],d=dfn[u]+siz[u];
a=fl[a],b=fl[b],c=fl[c],d=fl[d];
su[a][c]++;
su[b][c]--;
su[a][d]--;
su[b][d]++;
}
}
for(int i=1;i<=fl[n];i++){
for(int j=1;j<=fl[n];j++){
su[i][j]=su[i][j]+su[i-1][j]+su[i][j-1]-su[i-1][j-1];
}
}
for(int i=1;i<=tot;i++){
int ans=su[fl[dfn[qr[i].a]]][fl[dfn[qr[i].b]]];
//cerr<<"qry: "<<fl[qr[i].a]<<" "<<fl[qr[i].b]<<endl;
res[qr[i].id]+=(qr[i].w==1?ans:-ans);
}
}
void train(){
int L=0,R=0;
for(int i=1;i<=n;i++){
L=R+1;
R=L;
int s=(int)gn[R].size();
while(R<n&&s+(int)gn[R+1].size()<=B) s+=(int)gn[R+1].size(),R++;
solve(L,R);
if(R==n) break;
}
}
}
void subt(){
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
radi::q();
c::dfs(rt,0);
s::q();
work::init();
cin>>m;
for(int i=1,s,t,ox,oy,r;i<=m;i++){
cin>>s>>t>>ox>>oy>>r;
if(ox==0&&oy==0) res[i]=work::q00(s,t,r);
else if(ox==0&&oy==1) res[i]=work::q01(s,t,r);
else if(ox==1&&oy==0) res[i]=work::q10(s,t,r);
else work::q11(s,t,r,i);
}
work::train();
for(int i=1;i<=m;i++) cout<<res[i]<<endl;
}
}
int c;
signed main(){
//freopen("industry7.in","r",stdin);
//freopen("industry.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>c;
tymxz::subt();
return 0;
}
:::
10.27 Equal Degree Deletion
已通过。
题目链接。
观察一会,注意到合法的树,可以把树上的每条边赋一个权值,使得每个点相邻的边构成一个 \(0\sim \mathrm{deg}-1\) 的排列。
考虑证明这是充要的,且只会有一种赋权方案。只有一种方案是肯定的,每次把所有叶子删除即可。每次删掉一条权值最大的边,也可以使得赋权合法。
从低往高树形 dp,设 \(f_{u,i}\) 表示点 \(u\) 到父亲的边的权值是 \(i\)。转移时需要写一个 \(2^d\) 的状压。
设 \(u\) 到父亲的权值最大是 \(g_u\),把儿子按照 \(g_v\) 从小到大排序,状压时复杂度可以压到 \(\sum 2^{g_v}g_v\)。容易发现 \(g_v\le \log n\)。
进一步 \(g_u\) 要想得到 \(x\),必须要有 \(0\sim x-1\) 各一个,感受到这个应该用轻子树相关知识即可证明,不过我不会。反正能过。
哦,显然是轻子树大小的复杂度,所以是 \(O(n\log^2 n)\) 的。
10.28 Develop
题目链接。
问题看成可以放球,如果 \(x-2\) 和 \(x+k\) 有球就把那里的球扔掉,显然是一样的。
首先是 \(k\) 为偶数的情况。显然奇数位和偶数位独立,分别做最后乘起来就好。现在问题变成 \(-1\),\(+k\)。
发现当每个连续段长度 \(\le k\) 时合法。证明:考虑一个长度为 \(k+1\) 的窗口,你无法操作完它。构造方案就不同的连续段从小到大操作,同个连续段从大到小操作即可。
随便 dp 一下就好了。
但是正解和 \(k\) 为偶数没什么关系。先建图 \(x\to x-2\) 和 \(x\to x+k\),设球是黑点,否则是白点,然后考虑一个环上的黑点是操作不完的。
所以这个图对于黑点的导出子图一定是个 DAG。我们按照拓扑序倒着操作就可以构造。问题变成计数无环点集。
像这样把图对齐:

从上到下 dp。成环一定需要“偶数到奇数”的边的参与,所以我们需要检查这两个点的可达性。如果两个这样的黑点可达,就会成环。

一条合法路径形如这样,发现只要能到达 \(\le x-k\) 的点就可以了。
从上往下转移,设当前转移了第 \(i\) 行的第 \(o\) 个点,第 \(1\) 列的最后一个点可以到的最远位置是 \(j\),第 \(2\) 列的最后一个点可以到的最远位置是 \(k\)。

浙公网安备 33010602011771号