AtCoder Regular Contest 117

D - Miracle Tree

由于是无根树,所以找一个根,设 \(E_{rt}=1\) .

对于两条叶子到根的链,它们受到根的距离的约束必然没有互相之间距离的约束大,所以标法必然是维护一个时间,DFS 整棵树,在一个节点进栈的时候时间 \(+1\) ,出栈的时候时间 \(+1\) ,然后一个节点的 \(E_u\) 为进栈时间。

然后会发现最大的标号的节点(终止节点) \(v\) 的标号是 \(2n−dep[v]\) 。所以根和终止节点取直径的两端即可。

From George1123

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\) ,所以画成图像一定是类似这种东西:

AtCoder Regular Contest 117

那么考虑按层从上往下 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;
}
上一篇:61-A


下一篇:由HashMap哈希算法引出的求余%和与运算&转换问题