2019.12.14日常总结

洛谷P2015

【题意】:
有一棵苹果树,如果树枝有分叉,一定是分 2 2 叉(就是说没有只有 1 1 个儿子的结点,特别的,这样的二叉树即完满二叉树)。

这棵树共有 N N 个结点(叶子点或者树枝分叉点),编号为 1 N 1-N ,树根编号一定是 1 1

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 4 个树枝的树。
在这里插入图片描述
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。
【输入格式】: 1 1 2 2 个数, N N Q ( 1 Q N , 1 < N 100 ) Q(1\leq Q\leq N,1<N\leq100)

N N 表示树的结点数, Q Q 表示要保留的树枝数量。接下来 N 1 N-1 行描述树枝的信息。

每行 3 3 个整数,前两个是它连接的结点的编号。第 3 3 个数是这根树枝上苹果的数量。

每根树枝上的苹果不超过 30000 30000 个。
【思路】: 树形dp的模板题。

根据输入,我们可以很简单的求出每个点 i i 的左二子 l s i ls_i 和右儿子 r s i rs_i

f i , j f_{i,j} 表示考虑到点 i i 最多保留 j j 条边时的答案。我们把边权下放至儿子点上,所以把边权转化为了点权,记 v a l i val_i i i 的点权。

我们可以得到如下的转移方程:

f u , s = f l s u , j + f r s u , s j 1 + v a l u f_{u,s}=f_{ls_u,j}+f_{rs_u,s-j-1}+val_u

换言之,就是把依次考虑左右儿子可以保留多少个边。

【代码】:

int f[110][110],n,q;
int ls[110],rs[110];
int a[110][110],val[110];
int dp(int u,int s){
//	printf("dp(%d,%d)\n",u,s);
	if (s==0) return 0;//注意必须先写这句话
	if (!ls[u]&&!rs[u])
		return val[u];
	if (f[u][s]!=-1)
		return f[u][s];//记忆化搜索
	f[u][s]=0;
	for(int i=0;i<s;i++)
		f[u][s]=max(f[u][s],dp(ls[u],i)+dp(rs[u],s-i-1)+val[u]);
	return f[u][s];
}
void get_children(int u){
	for(int i=1;i<=n;i++)
		if (a[u][i]!=-1){
			val[i]=a[u][i];ls[u]=i;
			a[u][i]=a[i][u]=-1;
			get_children(i);
			break;
		}
	for(int i=1;i<=n;i++)
		if (a[u][i]!=-1){
			val[i]=a[u][i];rs[u]=i;
			a[u][i]=a[i][u]=-1;
			get_children(i);
			break;
		}
}
int main(){
	freopen("t1.in","r",stdin);
	memset(f,-1,sizeof(f));
	memset(a,-1,sizeof(a));
	scanf("%d%d",&n,&q);
	for(int i=1;i<n;i++){
		register int u,v;
		scanf("%d%d",&u,&v);
		scanf("%d",&a[u][v]);
		a[v][u]=a[u][v];
	}
	get_children(1);
//	for(int i=1;i<=n;i++){
//		printf("ls[%d]=%d,",i,ls[i]);
//		printf("rs[%d]=%d\n",i,rs[i]);
//	}
	cout<<dp(1,q+1);
	return 0;
}

洛谷P5542

【题意】: 农夫约翰不擅长多任务处理。他经常分心,很难完成长的项目。目前,他正试图在谷仓的一侧上漆,但他一直在画小矩形区域,然后由于照料奶牛的需要而偏离了方向,使得谷仓的某些部分上漆的涂料比其他部分多。

我们可以将谷仓的一侧描述为一个二维x-y平面,农夫约翰在该平面上绘制 n n 个矩形,每个矩形的边都与坐标轴平行,每个矩形由谷仓的左下角和右上角点的坐标描述。

农夫约翰想在谷仓上涂几层油漆,这样在不久的将来就不需要再重新粉刷了。但是,他不想浪费时间涂太多的油漆。结果表明, K K 涂层是最佳用量。请在他画完所有的长方形后,帮他确定谷仓有多少面积被 K K 层油漆覆盖。
【数据范围】: 1 k n 1 × 1 0 5 , 1 x , y 1 × 1 0 3 1\leq k\leq n\leq 1\times 10^5,1\leq x,y \leq 1 \times 10^3
【思路】: 如此恐怖的数据肯定不可以暴力啦……

我们考虑差分。当然,这题肯定是二维差分

与一维差分类似,我们直接在这里给出转移式。

for(int i=a;i<=c;i++)
	for(int j=b;j<=d;j++)
		a[i][j]++;//暴力写法
-----------------------------
vis[a+1][b+1]++;
vis[c+1][d+1]++;
vis[a+1][d+1]--;
vis[c+1][b+1]--;
//差分写法

当然,光有差分是不行的,肯定有二维前缀和。同理,我们直接给出代码。

for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		vis[i][j]+=vis[i-1][j]+vis[i][j-1]-vis[i-1][j-1];

其实,理解差分和前缀和的方法是,在纸上画出转移的情况,而不是凭空想象。在此,笔者十分想画出图,只是用电脑真的不好画,怕画出来反而妨碍读者理解。

【代码】:

int vis[1010][1010];
int ans,n,k,a,b,c,d;
#define gc getchar()
#define g(c) isdigit(c)
int read(){
	char c=0;int x=0;bool f=0;
	while (!g(c)) f=c=='-',c=gc;
	while (g(c)) x=x*10+c-48,c=gc;
	return f?-x:x;
}
int main(){
	n=read();k=read();
	for(int i=1;i<=n;i++){
		a=read();b=read();
		c=read();d=read();
		vis[a+1][b+1]++;
		vis[c+1][d+1]++;
		vis[a+1][d+1]--;
		vis[c+1][b+1]--; 
	}
	for(int i=1;i<1001;i++)
		for(int j=1;j<1001;j++){
			vis[i][j]+=vis[i-1][j]+vis[i][j-1]-vis[i-1][j-1];
			if (vis[i][j]==k) ans++;
		}
	printf("%d",ans);
	return 0;
}
发布了82 篇原创文章 · 获赞 4 · 访问量 1776

猜你喜欢

转载自blog.csdn.net/ZHUYINGYE_123456/article/details/103543246