D - Miracle Tree
由于是无根树,所以找一个根,设 \(E_{rt}=1\) .
对于两条叶子到根的链,它们受到根的距离的约束必然没有互相之间距离的约束大,所以标法必然是维护一个时间,DFS 整棵树,在一个节点进栈的时候时间 \(+1\) ,出栈的时候时间 \(+1\) ,然后一个节点的 \(E_u\) 为进栈时间。
然后会发现最大的标号的节点(终止节点) \(v\) 的标号是 \(2n−dep[v]\) 。所以根和终止节点取直径的两端即可。
void Adde( int u,int v ) { g[u].push_back(v); g[v].push_back(u); }
bool cmp( int x,int y ) { return height[x]<height[y]; }
void DFS( int u,int fat )
{
for ( int v : g[u] )
if ( v^fat ) dep[v]=dep[u]+1,DFS(v,u);
}
void DFS2( int u,int fat )
{
height[u]=0;
for ( int v : g[u] )
if ( v^fat ) DFS2(v,u),bmax(height[u],height[v]);
height[u]++;
}
void GetAns( int u,int fat )
{
ans[u]=++cnt;
for ( int v : g[u] )
if ( v^fat ) GetAns(v,u);
cnt++;
}
int main()
{
//freopen( "exam.in","r",stdin );
n=read();
for ( int i=1,u,v; i<n; i++ ) u=read(),v=read(),Adde(u,v);
DFS(1,0); int tmp=0;
for ( int i=1; i<=n; i++ ) if ( dep[i]>tmp ) tmp=dep[i],rt=i;
DFS2(rt,0);
for ( int i=1; i<=n; i++ ) sort(g[i].begin(),g[i].end(),cmp);
GetAns(rt,0);
for ( int i=1; i<=n; i++ ) printf("%d ",ans[i] );
return 0;
}
E - Zero-Sum Ranges 2
AtCoder 常见毒瘤 DP。
首先显然转化为前缀和,某个区间 \((l,r]\) 和为 \(0\) 就是 \(s[r]=s[l]\) ,也就是统计相等的对数。
由于元素只有 \(\pm 1\) ,所以画成图像一定是类似这种东西:
那么考虑按层从上往下 DP。注意到两个 \(s[i]\) 相等的位置必然不相邻,并且两个位置中间都是一个峰或者坑。设 \(dp[i][j][k]\) 表示当前有 \(i\) 个数,形成的相等对(就是 \(s[i]=s[j]\) )有 \(j\) 个,相邻两个 \(s[i]\) 相等的位置有 \(k\) 个(这些由于不能相邻,中间一定要填更小的数,下面称之为要填的坑)。
考虑转移,\(dp[i][j][k]\to dp[i+x][j+\frac{x(x-1)}2][x-(k+2)]\) ,\(x-(k+2)\) 是因为在用新的数填了之前的坑和整个序列两端之后少了 \(k+2\) 个,剩下的形成了新的坑。根据隔板法不难得到要乘上组合数 \(\displaystyle\binom{x-1}{k+1}\) .
统计答案时,按照 \(0\) 层分开,把地上和地下的合并即可,也就是 \(dp[x][y][z]\times dp[(2n+1)-x][k-y][z-1]\) . ( \(2n+1\) 是补了 \(s[0]\) )
//Author: RingweEH
signed main()
{
//freopen( "exam.in","r",stdin );
int n=read(),k=read(); C_Init();
for ( int i=1; i<=n+1 && i*(i-1)/2<=k; i++ ) dp[i][i*(i-1)/2][i-1]=1;
for ( int i=1; i<=2*n+1; i++ )
for ( int j=0; j<=k; j++ )
for ( int l=0; l<=n; l++ )
{
if ( dp[i][j][l]==0 ) continue;
for ( int x=l+2; i+x<=2*n+1 && x<=n+1; x++ )
{
int nxt=j+x*(x-1)/2; if ( nxt>k ) break;
dp[i+x][nxt][x-(l+2)]+=dp[i][j][l]*C[x-1][l+1];
}
}
int ans=dp[2*n+1][k][0];
for ( int i=0; i<=2*n+1; i++ )
for ( int j=0; j<=k; j++ )
for ( int l=1; l<=n; l++ )
ans+=dp[i][j][l]*dp[(2*n+1)-i][k-j][l-1];
printf("%lld\n",ans );
return 0;
}
F - Gateau
- \(\forall 0\leq i<n,a_i\leq \sum_{j=0}^{n-1}b_{i+j}\)
- \(\forall n\leq i<2n,a_i\leq\sum_{j=i}^{2n-1}b_j+\sum_{j=0}^{i-n-1}b_j\) ,设 \(X=\sum b_i\) ,就是 \(\sum_{j=i-n}^{i-1}b_j\leq X-a_i\)
令 \(s_i=\sum_{j=0}^{i-1}b_j\) ,那么 \(a_i\leq s_{i+n}-s_i\leq X-a_{i+n}\) ,且 \(s_i\leq s_{i+1}\) ,问题转化为判定形式。显然可以写成差分约束的形式,建有向边 \((i,j,x)\) 从 \(0\) 开始求最长路即可,如果有正环或者 \(d_{2n-1}>X\) 误解。
直接 SPFA 会被卡,考虑特殊性质。如果删去 \((n-1,n,0)\) ,那么图为两行 \([0,n),[n,2n)\) 。如果把两个对应点的最长路一起算,没有后效性,可以 \(O(n)\) 求。加入这条边之后,用 \(s_{n-1}\) 更新 \(s_n\) 并再求一次最长路,如果 \(s_{n-1}\) 变化就有正环,否则求出了最长路。
//Author: RingweEH
void Calc( ll x )
{
for ( int i=1; i<n; i++ )
dis[i]=max(dis[i-1],dis[i+n-1]+a[i+n]-x),
dis[i+n]=max(dis[i+n-1],dis[i-1]+a[i]);
}
bool Check( ll x )
{
for ( int i=0; i<n; i++ ) if ( a[i]+a[i+n]>x ) return 0;
dis[n]=a[0]; Calc(x);
ll las=dis[n-1]; dis[n]=max(dis[n],dis[n-1]);
if ( dis[n]>x-a[n] ) return 0;
Calc(x);
if ( las^dis[n-1] ) return 0;
return dis[2*n-1]<=x;
}
int main()
{
//freopen( "exam.in","r",stdin );
n=read();
for ( int i=0; i<2*n; i++ ) a[i]=read();
ll l=0,r=1e15;
while ( l<r )
{
ll mid=(l+r)>>1;
if ( Check(mid) ) r=mid;
else l=mid+1;
}
printf("%lld\n",l );
return 0;
}