省队集训-0423模拟赛
allenchoi
·
·
个人记录
A.树
题意:
CF1958I
有两棵树,大小为 n,以 1 为根。定义一次操作,为选择某棵树上的一个非根节点,把它删除,并把它的儿子全部连到它的父亲上。求最少几次操作,使得两棵树完全相同。
#### Sol:
显然两棵树保留的点集是一样的,要最大化这个电集。
新树等价于删点之后按祖先关系连边。也就是说,如果 $(x,y)$ 在两棵树上的祖先关系不一样,则不能都保留。
考虑折半搜索,右边的点集会对左边的点集产生限制,要求不能包含某些点。因此先把左边的点集跑出来,做个高维前缀和即可,总复杂度 $O(2^{\frac{n}{2}}n)$。
#### Code:
~~~cpp
#include <bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define pcnt __builtin_popcount
using namespace std;
typedef long long ll;
const int N = 45;
int n,U,ans,step[2],siz[N][2],dfn[N][2],f[1 << 21];
ll anc[N][2],bnd[N];
vector <int> v[N][2];
void dfs1(int x,int lst,int t)
{
siz[x][t] = 1,dfn[x][t] = ++step[t];
for(int y : v[x][t]) if(y != lst) dfs1(y,x,t),siz[x][t] += siz[y][t];
}
void dfs2(int x,int c,ll s)
{
if(x == n / 2)
{
f[U ^ (s & U)] = max(f[U ^ (s & U)],c);
return ;
}
dfs2(x - 1,c,s);
if(!(s & (1LL << x))) dfs2(x - 1,c + 1,s | bnd[x]);
}
void dfs3(int x,int s1,ll s2)
{
if(x == n / 2 + 1)
{
ans = max(ans,f[s1] + pcnt(s1));
return ;
}
dfs3(x + 1,s1,s2);
if(!(s2 & (1 << x))) dfs3(x + 1,s1 | (1 << x),s2 | bnd[x]);
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d",&n);
for(int t = 0;t < 2;t++)
for(int i = 1,a,b;i < n;i++)
{
scanf("%d%d",&a,&b);
v[a][t].pb(b),v[b][t].pb(a);
}
dfs1(1,0,0),dfs1(1,0,1);
for(int t = 0;t < 2;t++)
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
if(dfn[j][t] <= dfn[i][t] && dfn[i][t] <= dfn[j][t] + siz[j][t] - 1)
anc[i][t] |= (1LL << j);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
if((anc[i][0] & (1LL << j)) != (anc[i][1] & (1LL << j)))
bnd[i] |= (1LL << j),bnd[j] |= (1LL << i);
for(int i = 1;i <= n / 2;i++) U |= (1 << i);
dfs2(n,0,0);
for(int i = 1;i <= n / 2;i++)
for(int j = 2;j <= (1 << (n / 2 + 1));j += 2)
if(j & (1 << i))
f[j ^ (1 << i)] = max(f[j ^ (1 << i)],f[j]);
dfs3(1,0,0);
printf("%d\n",(n - ans) * 2);
return 0;
}
~~~
### B.序列
#### 题意:
ARC066D
给定一个长为 $n$ 的序列和一个常数 $C$,定义序列代价为:
$$\max\limits_{S\subseteq\set{1,2,...,n}}C\times\sum\limits_{1\le l\le r\le n}[\set{l,l+1,...,r}\subseteq S]-\sum\limits_{i\in S}a_i$$
先查询原序列权值,然后 $m$ 次:单点修改 $a_x\leftarrow y$,查询序列价值,然后撤销修改。
$n,m\le3\times10^5,1\le C\le10^5,a_i,y\le10^9$。
#### Sol:
DP 式子为 $f_i=\max\limits_jmaxn_{j-1}+\frac{(i-j+1)(i-j+2)}{2}C-s_i+s_{j-1}$,$maxn$ 为 $f$ 前缀 $\max$,$s$ 为前缀和。可以斜率优化做到线性。
然后考虑怎么求每次单点修改后的答案。先正着、反着各 $DP$ 一次。如果 $x\notin S$ 则不受影响,这一部分的答案等于前缀 $\max$ 加后缀 $\max$;如果 $x\in S$,考虑分治,钦定 $x$ 所在的极长连续段 $\subseteq[l,r]$ 并跨过 $mid$,在区间内再跑两次斜优,加上两侧前后缀 $\max$ 的贡献即可。
总复杂度 $O(n\log n)$。
#### Code:
~~~cpp
#include <bits/stdc++.h>
#define pii pair <int,int>
#define fi first
#define se second
#define mkp make_pair
#define pb push_back
using namespace std;
typedef long long ll;
const int N = 3e5 + 5,M = 3e7 + 5;
int n,m,C,top,a[N],st[N];
ll s[N],X[N],Y[N],maxn1[N],maxn2[N],ans[N],val[N];
vector <pii> v[N];
bool K(int a,int b,int c){return (__int128)(Y[b] - Y[a]) * (X[c] - X[b]) < (__int128)(Y[c] - Y[b]) * (X[b] - X[a]);}
ll calc(int f,int x){return Y[f] - 1LL * X[f] * x;}
void solve(int l,int r)
{
if(l == r)
{
for(pii p : v[l]) ans[p.se] = max(ans[p.se],maxn1[l - 1] + maxn2[l + 1] + 2 * (C - p.fi));
return ;
}
int mid = (l + r) / 2;
ll maxn = -1e18;
top = 0;
for(int i = r;i > mid;i--)
{
X[i] = -1LL * C * i,Y[i] = 1LL * C * i * i + maxn2[i + 1] - 2 * s[i];
while(top > 1 && K(st[top - 1],st[top],i)) top--;
st[++top] = i;
}
for(int i = mid;i >= l;i--)
{
while(top > 1 && calc(st[top - 1],-2 * i + 3) > calc(st[top],-2 * i + 3)) top--;
val[i] = calc(st[top],-2 * i + 3) + 1LL * C * (i - 1) * (i - 2) + 2 * s[i - 1];
}
for(int i = l;i <= mid;i++)
{
maxn = max(maxn,maxn1[i - 1] + val[i]);
for(pii p : v[i]) ans[p.se] = max(ans[p.se],maxn + 2 * (a[i] - p.fi));
}
maxn = -1e18;
top = 0;
for(int i = l;i <= mid;i++)
{
X[i] = 1LL * C * i,Y[i] = 1LL * C * i * i + maxn1[i - 1] + 2 * s[i - 1];
while(top > 1 && K(st[top - 1],st[top],i)) top--;
st[++top] = i;
}
for(int i = mid + 1;i <= r;i++)
{
while(top > 1 && calc(st[top - 1],2 * i + 3) > calc(st[top],2 * i + 3)) top--;
val[i] = calc(st[top],2 * i + 3) + 1LL * C * (i + 1) * (i + 2) - 2 * s[i];
}
for(int i = r;i > mid;i--)
{
maxn = max(maxn,maxn2[i + 1] + val[i]);
for(pii p : v[i]) ans[p.se] = max(ans[p.se],maxn + 2 * (a[i] - p.fi));
}
solve(l,mid),solve(mid + 1,r);
}
int main()
{
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%d%d%d",&n,&m,&C);
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
for(int i = 1;i <= n;i++) s[i] = s[i - 1] + a[i];
for(int i = 1;i <= n;i++)
{
X[i] = 1LL * C * i,Y[i] = 1LL * C * i * i + maxn1[i - 1] + 2 * s[i - 1];
while(top > 1 && K(st[top - 1],st[top],i)) top--;
st[++top] = i;
while(top > 1 && calc(st[top - 1],2 * i + 3) > calc(st[top],2 * i + 3)) top--;
maxn1[i] = max(maxn1[i - 1],calc(st[top],2 * i + 3) + 1LL * C * (i + 1) * (i + 2) - 2 * s[i]);
}
top = 0;
for(int i = n;i >= 1;i--)
{
X[i] = -1LL * C * i,Y[i] = 1LL * C * i * i + maxn2[i + 1] - 2 * s[i];
while(top > 1 && K(st[top - 1],st[top],i)) top--;
st[++top] = i;
while(top > 1 && calc(st[top - 1],-2 * i + 3) > calc(st[top],-2 * i + 3)) top--;
maxn2[i] = max(maxn2[i + 1],calc(st[top],-2 * i + 3) + 1LL * C * (i - 1) * (i - 2) + 2 * s[i - 1]);
}
printf("%lld\n",maxn1[n] / 2);
for(int i = 1,x,y;i <= m;i++)
{
scanf("%d%d",&x,&y);
v[x].pb(mkp(y,i));
ans[i] = maxn1[x - 1] + maxn2[x + 1];
}
solve(1,n);
for(int i = 1;i <= m;i++) printf("%lld\n",ans[i] / 2);
return 0;
}
~~~
### C.栈
#### 题意:
有三个栈 $s1,s2,s3$,初始时 $s1$ 从栈顶到栈底依次是 $p_1,p_2...p_n$,$p$ 为一个排列。给定 $A,B$,每一次操作,可以选择将 $s1$ 顶部的 $x\le A$ 个元素整体平移到 $s2$ 栈顶,或将 $s2$ 顶部的 $x\le B$ 个元素整体平移到 $s3$ 栈顶,要求最后 $s3$ 从顶到底依次是 $1,2,...,n$。问是否有解,有解则构造方案。
多测,$\sum n\le10^6$。
#### Sol:
大力模拟。
如果 $s2$ 中出现了 $p_i+1<p_{i+1}$ 的情况则一定无解。
所以,对于 $s1$ 中 $p_i+1<p_{i+1}$ 的位置,显然不能一次移到 $s2$ 中,那么依此分段考虑。
假设某一段中形成若干极长连续上升段 $[l_1,r_1],[l_2,r_2],...,[l_m,r_m]$。这些段的值域显然是递减的。
如果 $[l_1,r_1]$ 顶到了剩下的数的最大值,则把 $[l_1,r_1]$ 一个一个地放入 $s2$,再一个一个地放入 $s3$,一定最优。然后去掉 $[l_1,r_1]$ 归约到子问题。
如果这些段值域不连续,则只能一次性移过去。
否则,如果每一段长度都不超过 $A$,且加起来不超过 $B$,则可以把 $[l_1,r_1]...[l_m,r_m]$ 依次丢进 $s2$ 中形成一个大的连续段,这样可以尽量避免非法。
否则,如果 $m>2$ 那么只能一次全移到 $s2$ 里面。
然后只剩 $m=1,m=2$ 两种情况。
若 $m=1$,则将这一段一个一个地塞进 $s2$,后面再一个一个地塞进 $s3$,因为如果 $s2$ 中会出现非法情况则一定是无法避免的,这样做不会劣。
然后是 $m=2$ 的情况,最多分两步移走。考虑枚举第一步移动的长度 $k$。
设第一段长为 $x$,第二段长为 $y$。
如果 $k<x$,则 $s2$ 中会形成 $x-k,y+k$ 两段,所以要求 $\max(k,x-k+y)\le A,\max(x-k,y+k)\le B$;
如果 $k>x$,则 $s2$ 中会形成 $2x+y-k,k-x$ 两段,所以要求 $\max(k,x+y-k)\le A,\max(2x+y-k,k-x)\le B$。
由于两段在 $s2$ 中无法拼在一起,所以非法情况无法避免,所以随便找个满足上述条件的 $p$ 即可。
往 $s2$ 里加东西时判一下和栈顶的关系是否非法就做完了,线性。
#### Code:
~~~cpp
#include <bits/stdc++.h>
#define pii pair <int,int>
#define fi first
#define se second
#define mkp make_pair
#define pb push_back
using namespace std;
const int N = 1e6 + 5;
int T,n,A,B,val,p[N];
vector <pii> v,opt;
stack <pii> st;
void work1(vector <pii> a)
{
//s1->s2
int sum = 0;
for(pii p : a) sum += p.se - p.fi + 1;
opt.pb(mkp(1,sum));
for(int i = a.size() - 1;i >= 0;i--)
if(!st.empty() && st.top().fi == a[i].se + 1) st.top().fi = a[i].fi;
else st.push(a[i]);
}
bool work2()
{
//s2->s3
while(!st.empty() && st.top().se == val - 1)
{
if(st.top().se - st.top().fi + 1 > B) return false;
opt.pb(mkp(2,st.top().se - st.top().fi + 1));
val = st.top().fi,st.pop();
}
return true;
}
bool solve()
{
scanf("%d%d%d",&n,&A,&B);
val = n + 1;
for(int i = 1;i <= n;i++) scanf("%d",&p[i]);
for(int l = 1,r;l <= n;l = r + 1)
{
for(r = l;r < n && p[r] + 1 >= p[r + 1];r++);
v.clear();
for(int i = l,j;i <= r;i = j + 1)
{
for(j = i;j < r && p[j + 1] == p[j] + 1;j++);
v.pb(mkp(p[i],p[j]));
}
int siz = v.size(),cur = 0,sum = 0;
while(cur < siz && v[cur].se == val - 1)
{
for(int i = 1;i <= v[cur].se - v[cur].fi + 1;i++) opt.pb(mkp(1,1));
for(int i = 1;i <= v[cur].se - v[cur].fi + 1;i++) opt.pb(mkp(2,1));
val = v[cur].fi,cur++;
if(!work2()) return false;
}
v.erase(v.begin(),v.begin() + cur);
if(!(siz -= cur)) continue;
bool flag = false,h = true;
for(pii p : v) sum += p.se - p.fi + 1;
for(int i = 1;i < siz;i++) flag |= (v[i - 1].fi > v[i].se + 1);
for(pii p : v) h &= (p.se - p.fi + 1 <= A);
if(flag)
{
if(sum > A) return false;
if(!st.empty() && v.back().se + 1 < st.top().fi) return false;
for(pii p : v) if(p.se - p.fi + 1 > B) return false;
work1(v);
continue;
}
if(h && sum <= B)
{
if(!st.empty() && v[0].se + 1 < st.top().fi) return false;
for(pii p : v) work1(vector <pii> {p});
continue;
}
if(siz == 1)
{
if(!st.empty() && v[0].se + 1 < st.top().fi) return false;
if(v[0].se - v[0].fi + 1 <= min(A,B)) work1(v);
else if(!st.empty() && v[0].fi + 1 < st.top().fi) return false;
else for(int i = v[0].fi;i <= v[0].se;i++) work1(vector <pii> {mkp(i,i)});
continue;
}
if(siz == 2)
{
if(!st.empty() && v[0].se + 1 <= st.top().fi) return false;
int x = v[0].se - v[0].fi + 1,y = v[1].se - v[1].fi + 1,k = 0;
for(int i = 1;i < x;i++)
if(max(i,x + y - i) <= A && max(i + y,x - i) <= B)
{
k = i;
break;
}
if(k)
{
work1(vector <pii> {mkp(v[0].fi,v[0].fi + k - 1)});
work1(vector <pii> {mkp(v[0].fi + k,v[0].se),mkp(v[1].fi,v[1].se)});
continue;
}
for(int i = x + 1;i < x + y;i++)
if(max(i,x + y - i) <= A && max(i - x,2 * x + y - i) <= B)
{
k = i;
break;
}
if(k)
{
work1(vector <pii> {mkp(v[0].fi,v[0].se),mkp(v[1].fi,v[1].fi + k - x - 1)});
work1(vector <pii> {mkp(v[1].fi + k - x,v[1].se)});
continue;
}
if(sum <= A && max(v[0].se - v[0].fi + 1,v[1].se - v[1].fi + 1) <= B)
{
work1(v);
continue;
}
return false;
}
if(sum > A) return false;
if(!st.empty() && v.back().se + 1 < st.top().fi) return false;
for(pii p : v) if(p.se - p.fi + 1 > B) return false;
work1(v);
}
return true;
}
int main()
{
freopen("stack.in","r",stdin);
freopen("stack.out","w",stdout);
scanf("%d",&T);
while(T--)
{
opt.clear();
while(!st.empty()) st.pop();
if(!solve()) puts("-1");
else
{
printf("%d\n",(int)opt.size());
for(pii p : opt) printf("%d %d\n",p.fi,p.se);
}
}
return 0;
}
~~~