[NOIP模拟赛Day1]轰炸

【题目描述】
A国和B国开战。B国国王痴迷于树,所以B国的城市道路是一棵树的形状。A国情报局通过B国地下组织获取了该消息……给力的地下党还窃取了B国每一个城市的炸药储备量m[i],即轰炸城市i可以同时炸掉距离城市i在m[i]之内的其他城市。但爆炸不是连续的。
由于A国国力有限,A国情报局局长想知道至少需要炸几次才能把B国所有城市都炸掉……
【输入格式】
第一行输入一个整数n,表示B国的城市数量。
第二行输入n个整数,第i个整数m[i]表示城市i的炸药储备量。
接下来的n-1行,每一行输入两个整数u、v,表示一条连接城市u、v长度为的城市道路。
【输出格式】
输出一行一个整数,即最少轰炸次数。
【样例输入】
5
1 1 1 1 1
1 2
2 3
3 4
4 5
【样例输出】
2
【数据范围】
对于10%的数据,n≤10。
对于30%的数据,n≤1000。

对于100%的数据,n≤10^5,m[i]≤100。



题解:一道典型的树型DP。

f[r][i]表示以r为根的子树被完全破坏,同时还能向上延伸i的最小值。

g[r][i]表示以r为根的子树未被完全破坏,还应向下延伸i的最小值。

转移方程式:

①不轰炸r节点

 f[r][i]=min( f[son][i+1]+Σ min( f[otherson][0~i+1], g[otherson][0~i-1] ) )

 g[r][i]=min( g[son][i-1]+Σ min( f[otherson][0~i], g[otherson][0~i-1] ) )

②轰炸r节点

f[r][ m[r] ]=min( f[r][ m[r] ], 1+Σ min( f[son][ 0~m[r]+1 ], g[son][ 0~m[r]-1 ] ) )


很显然直接这样做是会TLE的。


minf[r][i]表示min( f[r][0~i] ),同理ming[r][i]表示min( g[r][0~i] );

再用s1[r][i]表示Σ min( f[son][0~i+1], g[son][0~i-1] ),

s2[r][i]表示Σ min( f[son][0~i], g[son][0~i-1] ) ;

那么上述转移方程式可以写成:

f[r][i]=min( s1[r][i]+f[son][i+1]-min( f[son][i+1], g[son][i-1] ) )

g[r][i]=min( s2[r][i]+g[son][i-1]-min( f[son][i], g[son][i-1] ) )


这样时间复杂度为O( 100*n )。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e5+10;
const int M=105;

void Getin( int &shu ) {
	char c; int f=1; shu=0;
	for( c=getchar(); c<'0' || c>'9'; c=getchar() ) if( c=='-' ) f=-1;
	for( ; c>='0' && c<='9'; c=getchar() ) shu=shu*10+c-'0';
	shu*=f;
}

int fir[N], ecnt;
struct node{ int e, next; }edge[N<<1];
void Link( int s, int e ) {
	edge[++ecnt].e=e; edge[ecnt].next=fir[s]; fir[s]=ecnt;
	edge[++ecnt].e=s; edge[ecnt].next=fir[e]; fir[e]=ecnt;
}

int n, m[N], maxm, s, e;
int f[N][M], g[N][M], minf[N][M], ming[N][M];
int s1[N][M], s2[N][M];

void DP( int r, int fa ) {
	for( int i=fir[r]; i; i=edge[i].next )
		if( edge[i].e!=fa ) {
			DP( edge[i].e, r );
			s1[r][0]+=minf[ edge[i].e ][1];
			s2[r][0]+=minf[ edge[i].e ][0];
			for( int j=1; j<=maxm; j++ )
				s1[r][j]+=min( minf[ edge[i].e ][j+1], ming[ edge[i].e ][j-1] ),
				s2[r][j]+=min( ming[ edge[i].e ][j-1], minf[ edge[i].e ][j] );
		}
	
	for( int i=maxm+1; i>=0; i-- )
		f[r][i]=g[r][i]=minf[r][i]=ming[r][i]=n;	
	g[r][0]=s2[r][0];
	
	for( int i=fir[r]; i; i=edge[i].next )
		if( edge[i].e!=fa ) {
			f[r][0]=min( f[r][0], s1[r][0]+f[ edge[i].e ][1]-minf[ edge[i].e ][1] );
			for( int j=1; j<=maxm; j++ )
				f[r][j]=min( f[r][j], s1[r][j]+f[ edge[i].e ][j+1]-min( minf[ edge[i].e ][j+1], ming[ edge[i].e ][j-1] ) ),
				g[r][j]=min( g[r][j], s2[r][j]+g[ edge[i].e ][j-1]-min( ming[ edge[i].e ][j-1], minf[ edge[i].e ][j] ) );
		}
	
	f[r][ m[r] ]=min( f[r][ m[r] ], s1[r][ m[r] ]+1 );
	minf[r][0]=f[r][0]; ming[r][0]=g[r][0];
	
	for( int i=1; i<=maxm+1; i++ )
		minf[r][i]=min( f[r][i], minf[r][i-1] ),
		ming[r][i]=min( g[r][i], ming[r][i-1] );
}

int main() {
	Getin(n);
	for( int i=1; i<=n; i++ ) 
		Getin( m[i] ), maxm=max( maxm, m[i] );
	for( int i=1; i<n; i++ )
		Getin(s), Getin(e), Link( s, e );
	DP( 1, -1 );
	printf( "%d\n", minf[1][maxm] );
	return 0;
}




NOI(全青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值