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] o dp[i+x][j+frac{x(x-1)}2][x-(k+2)]) ,(x-(k+2)) 是因为在用新的数填了之前的坑和整个序列两端之后少了 (k+2) 个,剩下的形成了新的坑。根据隔板法不难得到要乘上组合数 (displaystyleinom{x-1}{k+1}) .
统计答案时,按照 (0) 层分开,把地上和地下的合并即可,也就是 (dp[x][y][z] imes 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
",ans );
return 0;
}
F - Gateau
- (forall 0leq i<n,a_ileq sum_{j=0}^{n-1}b_{i+j})
- (forall nleq i<2n,a_ileqsum_{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_jleq X-a_i)
令 (s_i=sum_{j=0}^{i-1}b_j) ,那么 (a_ileq s_{i+n}-s_ileq X-a_{i+n}) ,且 (s_ileq 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
",l );
return 0;
}