练习二题目记录

A.[POI2004] SZP

Description

给你一个有向有环图,每个节点只能也必须发出一条非自环的边,要求选择尽可能多的点,使得选中的每一个点都至少有一个指向它的节点没有被选中,求可以选择的最多的点数。

Solution

易得,在每一个联通块中,有且仅有一个环。考虑到每个节点只能发出一条边,那么我们可以将所有边倒过来建,这样每个联通块就形成了一棵基环树,整张图就形成了一个基环树森林。

关注每一个联通块,我们可以先删掉环上的一条边,这样就形成了一个正常的树。然后我们以删掉的边的一个节点为根跑树形 DP,然后再将删除的这个边连上,进行一次换根。

先看 DP,设 \(dp_{x,0/1}\) 表示在以 \(x\) 为根的子树中,选或不选 \(x\) 节点所能选择的最多的节点数量,易证:\(dp_{x,1}\) 一定大于 \(dp_{x,0}\)。设 \(y\)\(x\) 的子节点,转移方程如下:

\[dp_{x,0}=\sum dp_{y,1} \]

\[dp_{x,1}=\sum dp_{y,1}+\max(dp_{y,0}-dp_{y,1}) \]

最后根据题意进行一次换根即可,换根的时候需要关注根节点通过被断开的边到达的点的状态,所以我们需要求出当根节点不选择时的每个节点的状态。因为当一个节点选择时,只有一个子节点不选,所以设 \(pre_i\) 表示在选择这个节点时不选的那个子节点,然后再遍历一遍处理出状态即可。因为这题的空间卡得很紧,没法建边,所以我们记录每个节点的父节点,然后通过拓扑排序处理出转移的顺序,最后按照这个顺序,从 \(x\) 转移到 \(fa_x\),处理状态时反过来即可。

Code

#include<bits/stdc++.h>
#define N 1000005
using namespace std;
int n,f[N],fa[N],dp[N][2],ot[N],ans,otn[N],pre[N],nm[N];
vector<int>rt;
int findf(int x){
	if(x==f[x])return x;
	return f[x]=findf(f[x]);
}
void tuopu(){
	memset(f,0,sizeof f);
	for(int i=1;i<=n;i++)if(!ot[i])f[++f[0]]=i;
	for(int i=1;i<=f[0];i++){
		if(!fa[f[i]])continue;
		--ot[fa[f[i]]];
		if(!ot[fa[f[i]]])f[++f[0]]=fa[f[i]];
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)f[i]=i;
	for(int i=1;i<=n;i++){
		cin>>fa[i];
		if(findf(i)==findf(fa[i])){
			rt.push_back(i);
			nm[fa[i]]=1;
		}
		else{
			f[i]=fa[i];
			++ot[fa[i]];
			++otn[fa[i]];
		}
	}
	tuopu();
	memset(ot,0x3f,sizeof ot);
	for(int i=1;i<=n;i++){
		int x=f[i];
		if(otn[x]>0)dp[x][1]-=ot[x]-1;
		dp[fa[x]][0]+=dp[x][1];
		dp[fa[x]][1]+=dp[x][1];
		if(ot[fa[x]]>=dp[x][1]-dp[x][0]){
			if(ot[fa[x]]==dp[x][1]-dp[x][0]){
				if(!nm[x])pre[fa[x]]=x;
			}
			else{
				ot[fa[x]]=dp[x][1]-dp[x][0];
				pre[fa[x]]=x;
			}
		}
	}
	memset(nm,0,sizeof nm);
	for(int i=n;i;i--){
		int x=f[i];
		if(count(rt.begin(),rt.end(),x)||!otn[x]){
			nm[x]=0;
			continue;
		}
		if(!nm[fa[x]]||(nm[fa[x]]&&pre[fa[x]]!=x))nm[x]=1;
	}
	for(int p:rt){
		ans+=max(dp[p][1],dp[p][0]+(pre[fa[fa[p]]]!=fa[p]&&!nm[fa[p]]));
	}
	cout<<ans;
	return 0;
}

B.元旦晚会

Description

给你一个长为 \(n\) 的数组和 \(m\) 个区间,让你在数组中选一些数,使得区间 \(i\) 中至少有 \(c_i\) 个元素被选中,求最少需要被选中多少个数。

Solution

贪心。将这 \(m\) 个区间以左端点为第一关键字,以右端点为第二关键字排序,然后从后往前贪即可。选择的时候尽可能去找重复的地方,即从前往后找。

Code

#include<bits/stdc++.h>
#define N 30005
#define M 5005
using namespace std;
int n,m,ans,vis[N];
struct node{
	int l,r,x;
	bool operator<(const node x){
		if(l!=x.l)return l<x.l;
		return r<x.r;
	}
}a[M];
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++)cin>>a[i].l>>a[i].r>>a[i].x;
	sort(a+1,a+1+m);
	for(int i=m;i;i--){
		for(int j=a[i].r;j>=a[i].l&&a[i].x;j--){
			if(vis[j])--a[i].x;
		}
		ans+=a[i].x;
		for(int j=a[i].l;j<=a[i].r&&a[i].x;j++){
			if(!vis[j])vis[j]=1,--a[i].x;
		}
	}
	cout<<ans;
	return 0;
}

C.田忌赛马

Description

给你长度为 \(n\) 的一个 \(a\) 数组和一个 \(b\) 数组,让你选择一种方案,使 \(a_i\)\(b_j\) 一一对应,当 \(a_i>b_i\) 时获得 200 银币,\(a_i=b_i\) 时不获得银币,\(a_i<b_i\) 时减去 200 银币。

Solution

\(a\)\(b\) 按从大到小排序,考虑贪心。从前往后遍历 \(b\),对于 \(b_i\) 有两种选择,一是用当前最大的 \(a_i\) 赢他然后获得 200 银币,二是用当前最小的 \(a_i\) 耗死他然后减去 200 银币。所以到 \(b_i\) 时,\(a\) 一定被分成了三部分,第一和第三部分被选择了,中间的部分空着。

\(dp_{i,j}\) 表示选择到 \(b_i\) 时,\(a\) 前面那一部分被选择了 \(j\) 个。转移具体看代码。

Code

#include<bits/stdc++.h>
#define N 2005
using namespace std;
int n,a[N],b[N],dp[N][N],ans;
int cmp(int a,int b){
	return a>b;
} 
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)cin>>b[i];
	sort(a+1,a+1+n,cmp);
	sort(b+1,b+1+n,cmp);
	memset(dp,128,sizeof dp);
	dp[0][0]=0;
	for(int i=0;i<n;i++){
		for(int j=0;j<=i;j++){
			int k=i-j;
			if(a[j+1]>b[i+1])dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+200);
			else if(a[j+1]<b[i+1])dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]-200);
			else dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]);
			/*------------------------------------------*/
			if(a[n-k]>b[i+1])dp[i+1][j]=max(dp[i+1][j],dp[i][j]+200);
			else if(a[n-k]<b[i+1])dp[i+1][j]=max(dp[i+1][j],dp[i][j]-200);
			else dp[i+1][j]=max(dp[i+1][j],dp[i][j]);
		}
	}
	for(int i=0;i<=n;i++)ans=max(ans,dp[n][i]);
	cout<<ans;
	return 0;
} 

D.[APIO/CTSC2007] 数据备份

Description

现在在一条数轴上有 \(n\) 个楼房,你可以将两座楼房连接,其代价就是两楼之间的距离。现在你需要连接 \(K\) 次,被连接的楼房只能与别的节点连一次,求最小的代价。

Solution

首先我们需要知道一个东西,就是最终选择连接的两座楼房一定是相邻的,所以我们先预处理出来 \(a_i\),表示 \(i\)\(i-1\) 座楼房的距离,即选择 \(i\) 的代价,问题也就转换成了选择 \(a_i\)。因为每个楼房最多只能连接一次,所以选择的每一个 \(a_i\) 不能相邻。

考虑 DP。设 \(dp_{i,k}\) 表示在选择第 \(i\) 个并在此之前包括 \(i\) 一共选择了 \(k\) 个。转移方程即为:

\[dp_{i,k}=\min(dp_{j,k-1})+a_i,(j<i-1) \]

我们关注到 \(k\) 只能由第 \(k-1\) 转移,所以我们可以滚动数组,然后再用前缀最小值优化一下即可。

Code

#include<bits/stdc++.h>
#define N 100005 
using namespace std;
int n,m,a[N],dp[N][2],ans=0x3f3f3f3f;
signed main(){
	cin>>n>>m;
	for(int i=2;i<=n+1;i++)cin>>a[i],a[i-1]=a[i]-a[i-1];
	memset(dp,0x3f,sizeof dp);
	dp[0][0]=dp[0][1]=0;
	for(int k=1;k<=m;k++){
		int num=0x3f3f3f3f;
		for(int i=2*k;i<=n-(m-k)*2;i++){
			num=min(num,dp[i-2][!(k&1)]);
			dp[i][k&1]=num+a[i];
		}
	}
	for(int i=2*m;i<=n;i++)ans=min(ans,dp[i][m&1]);
	cout<<ans;
	return 0;
}

E.[TJOI2013] 拯救小矮人

Description

一群小矮人掉进了一个很深的陷阱里,由于太矮爬不上来,于是他们决定搭一个人梯。即:一个小矮人站在另一小矮人的 肩膀上,直到最顶端的小矮人伸直胳膊可以碰到陷阱口。

对于每一个小矮人,我们知道他从脚到肩膀的高度 \(A_i\),并且他的胳膊长度为 \(B_i\)。陷阱深度为 \(H\)

如果我们利用矮人 \(1\),矮人 \(2\),矮人 \(3\),……,矮人 \(k\) 搭一个梯子,满足 \(A_1+A_2+A_3+\dots+A_k+B_k \geq H\),那么矮人 \(k\) 就可以离开陷阱逃跑了,一旦一个矮人逃跑了,他就不能再搭人梯了。求最多能有几个小矮人跑出来。

Solution

这道题是一道贪心+DP。按照 \(a_i+b_i\) 从小往大排序,然后从前往后跑背包 DP。为什么这么贪心呢?因为 \(a_i+b_i\) 越小,他要出来底下的人梯就要越高,也就要越先出来。

\(dp_{i,j}\) 表示到 \(i\) 时剩下的人梯高度为 \(j\) (不包括手臂长度)的最多出来的小矮人数量。转移方程如下:

\[dp_{i,j}=\max(dp_{i-1,j},dp_{i-1,j+a_i}+1) \]

最后滚动数组即可。

Code

#include<bits/stdc++.h>
#define N 2005
#define M 400005
using namespace std;
int n,m,sum,ans,dp[2][M*2];
struct node{
	int a,b;
	bool operator<(const node x)const{
		if(a+b!=x.a+x.b)return (a+b)<(x.a+x.b);
		if(a!=x.a)return a<x.a;
		return b<x.b;
	}
}x[N];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>x[i].a>>x[i].b,sum+=x[i].a;
	sort(x+1,x+1+n);
	cin>>m;
	for(int i=1;i<=n;i++){
		if(x[i].a+x[i].b>=m){
			ans+=n-i+1;
			cout<<ans;
			return 0;
		}
		memcpy(dp[i&1],dp[!(i&1)],sizeof dp[!(i&1)]);
		for(int j=sum-x[i].a+M;j>=m-x[i].b-x[i].a+M;j--){
			dp[i&1][j]=max(dp[!(i&1)][j],dp[!(i&1)][j+x[i].a]+1);
			ans=max(ans,dp[i&1][j]);
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2025-03-08 17:45  WDY_Hodur  阅读(18)  评论(0)    收藏  举报