csp-s2019 总结
D1
T1
不谈
T2
可以先考虑在序列上的情况,考虑对于一个 )
,设 \(f[i]\) 表示以 i 结尾的答案是多少,有:\(f[i]=f[j]+1\),其中 j 是可以与 i 匹配的前一个位置。然后可以用栈来维护,然后再拓展到树上就行了。。。然后就是记录一下操作然后回朔就行了。。。
T3
写了再写
D2
T1
其实是挺明显的容斥题目的,然后在考场上时我又想过容斥但不知道为什么不了了之(算了,就当是我右边的人的锅^^)。
考虑 dp,首先可以知道肯定只有一个食材是超了的,于是可以枚举这个食材,然后可以再以横行为阶段进行 dp。。。
然后可以设 \(f[i][j][h][k]\) 表示第 i 列超了,选到第 j 行,i 列有 h 个,其他有 k 个的方案数。。
然后可以发现我们不用管具体h,k是多少,只用关心他们的差就行了,然后就可以优化状态。。。
然后到最后一行时,k>0 的情况即是要求的,然后中间可能有负数,然后平移一下就好。。
T2
呃,首先可以发现一个结论:最优解同时一定也是所有合法解中最后一段的和最小的解。
然后就是求:\(f_i=\min\{S_i-S_j\mid S_i-S_j\geq f_j\}\) \(f_i\) 表示i结尾的最后一段的长度。
然后一看就可以单调队列优化。。。按 \(s_i+f_i\) 来维护单调队列就行了。。。
所以我当时为什么用这个性质写了个 \(n^2\) dp?
然后100分要写高精,订正时可以用__int128水,然后会炸空间,然后可以最后再求和。。
T3
树的重心的性质:
1.删除重心后所得的所有子树,节点数不超过原树的1/2,一棵树最多有两个重心;(我觉得这个性质应该是比定义要直观具体好用的)
2.树中所有节点到重心的距离之和最小,如果有两个重心,那么他们距离之和相等;
3.两个树通过一条边合并,新的重心在原树两个重心的路径上;
4.树删除或添加一个叶子节点,重心最多只移动一条边;
然后首先这题肯定是要对每个点分别求贡献的。。
首先我们可以求出整体的重心,然后以它为根。。
考虑对于一个非根的点 x,删什么它会有贡献。。
首先肯定不可以删它子树的边,这个易证。。
然后考虑如果删非它子树的边:
设 \(S\) 表示删掉一条边后的另一棵树的大小。。\(g_x\) 表示 x 的最大儿子的大小。。
然后有:
然后整理可得:
然后这就可以用树状数组区间求值了。。
然后考虑如何维护这个树状数组。。
考虑删的边和 x 的关系:
1.是 x 的儿子,这种情况即使S满足要求也不算,要再减去。。
2.是 x 的祖先,这种情况 \(S=n-sz_y\),可以 dfs 时维护。
3.和 x 没啥关系,这种情况 \(S=sz_y\),这种情况可以一开始就add进去。。
然后 dfs 的步骤:
这个dfs时我觉得看做边为主体要清晰些。。。
code:
void dfs2(int x,int fa){
add(0,n-sz[x],1);维护 2 的情况
if(x!=rt){
ans+=x*ask(0,n-2*g[x]);
ans-=x*ask(0,n-2*sz[x]-1);把 1,2,3的情况都算上去
ans+=x*ask(1,n-2*g[x]);
ans-=x*ask(1,n-2*sz[x]-1);
if(z[fa]) z[x]=1;
ans+=rt*(sz[x]<=n-2*sz[z[x]?v:u]);
}
add(0,sz[x],-1);维护1,因为接下来要dfs的就是x的儿子了,S就要变
add(1,sz[x],1);
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==fa) continue;
dfs2(y,x);
}
add(0,sz[x],1);要回溯时这条边就从2成了3。。
add(0,n-sz[x],-1);
if(x!=rt){
ans-=x*ask(1,n-2*g[x]);
ans+=x*ask(1,n-2*sz[x]-1);用可减性减去1的情况。。
}
}
然后就是考虑x=rt的情况:
设rt的儿子中子树最大的节点为u,次大的节点为v。
若割掉的边在u的子树中,则需要满足:\(2 \times s_v \le n – S\)
即:\(S \le n – 2s_v\)
否则,需要满足:\(2 \times s_u \le n – S\)
即:\(S \le n – 2s_u\)
可以在 dfs 的时候直接维护。
这题总体思路和天天爱跑步有点类似,都是推式子然后在树上维护求答案,然后都利用了有些数据在树上的可减性来求子树中的值。。
本题应该也可以算是换根吧?
做这种题一定要把所有可能情况讨论清楚,然后再去处理。。。
code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+7;
template <class I>
inline void read(I &x){
int f=1;
char c;
for(c=getchar();c<'0'||c>'9';c=getchar()) if(c=='-') f=-1;
for(x=0;c>='0'&&c<='9';x=(x<<3)+(x<<1)+(c&15),c=getchar());
x*=f;
}
int head[N],nxt[2*N],ver[2*N],tot=1,n,g[N];
int sz[N],rt,t[2][N],ans,u,v;
bool z[N];
inline void add(int x,int y){
ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
inline void add(int o,int x,int v){
x++;
while(x<=n+1){
t[o][x]+=v;
x+=x&-x;
}
}
inline int ask(int o,int x){
int res=0;
x++;
while(x){
res+=t[o][x];
x-=x&-x;
}
return res;
}
void dfs(int x,int fa){
sz[x]=1,g[x]=0;
bool fg=1;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==fa) continue;
dfs(y,x);
sz[x]+=sz[y];
g[x]=max(g[x],sz[y]);
if(g[x]>n/2) fg=0;
}
if(n-sz[x]>n/2) fg=0;
if(fg) rt=x;
}
void dfs2(int x,int fa){
add(0,n-sz[x],1);
if(x!=rt){
ans+=x*ask(0,n-2*g[x]);
ans-=x*ask(0,n-2*sz[x]-1);
ans+=x*ask(1,n-2*g[x]);
ans-=x*ask(1,n-2*sz[x]-1);
if(z[fa]) z[x]=1;
ans+=rt*(sz[x]<=n-2*sz[z[x]?v:u]);
}
add(0,sz[x],-1);
add(1,sz[x],1);
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==fa) continue;
dfs2(y,x);
}
add(0,sz[x],1);
add(0,n-sz[x],-1);
if(x!=rt){
ans-=x*ask(1,n-2*g[x]);
ans+=x*ask(1,n-2*sz[x]-1);
}
}
void solve(){
read(n);
ans=0;
for(int i=1;i<=n;i++)
head[i]=sz[i]=g[i]=0;
tot=1;
for(int i=1;i<n;i++){
int x,y;
read(x),read(y);
add(x,y),add(y,x);
}
dfs(1,0);
dfs(rt,0);
u=0,v=0;
for(int i=head[rt];i;i=nxt[i]){
int x=ver[i];
if(sz[x]>sz[v]) v=x;
if(sz[v]>sz[u]) swap(u,v);
}
for(int i=1;i<=n+1;i++) t[0][i]=t[1][i]=0;
for(int i=1;i<=n;i++) add(0,sz[i],1),z[i]=0;
z[u]=1;
dfs2(rt,0);
printf("%lld\n",ans);
}
signed main(){
int T;
read(T);
while(T--){
solve();
}
return 0;
}