题目整理-3(练习2)

T1[POI2004] SZP

题面描述:

\(n\) 个人,每个人会监视除自己外的一个人,求选择尽量多的同学去搬卷子和答题卡,且使得对于这些同学中的每一名同学,至少有一位监视她的同学没有被选中。问最多可以选择多少同学?

思路:

首先,易想到建图,每个人向他所监视的人连有向边。

通过思考或被提醒手搓数据观察可得,每一个联通的图中,必然有且仅有一个环,且易证。

因此,这是一个基环树我们可以尝试断环为链,再进行处理(肯定可以找到一个树根)。在第一次的断环为链中,我们使用树形 \(dp\) 计算树根处的最大值,具体 \(dp\) 式如下:

\[f_{0,i}=\sum_{}^{} f_{1,j} \\ f_{1,i}=f_{0,j}+min(f_{1,j}-f_{0,j}) \]

注:\(f_{0,i}\) 表示以 \(i\) 为根的子树不选 \(i\) 的最大值,\(f_{1,i}\) 则相反。

所以,我们第一步的结果就是选树根的那个值。但是,我们有可能这个点不选,因此,我们以他监视的同学为根,再跑一遍就没问题了。

标签:

树形dp
基环树
拓扑排序

代码实现

this
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000005
using namespace std;
int n,f[2][N],g[2][N],a[N];//5n
int q[N],ru[N];//2n
int had=1,til,ans;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		ru[a[i]]++;
	}
	memset(f[1],0x3f,sizeof(f[1]));
	for(int i=1;i<=n;i++) if(!ru[i]) q[++til]=i;
	while(had<=til){//在拓扑时计算dp,减少时间
		int x=q[had];
		if(f[1][x]==INF) f[1][x]=0;
		else f[1][x]=f[0][x]-f[1][x]+1;
		if(ru[a[x]]){
			ru[a[x]]--;
			f[0][a[x]]+=f[1][x];
			f[1][a[x]]=min(f[1][a[x]],f[1][x]-f[0][x]);
			if(!ru[a[x]]) q[++til]=a[x];
		}
		had++;
	}
	memset(q,0,sizeof(q));
	memcpy(g,f,sizeof(g));
	for(int i=1;i<=n;i++){
		if(ru[i]){
			had=a[i];
			while(had!=i){
				if(f[1][had]==INF) f[1][had]=0;
				else f[1][had]=f[0][had]-f[1][had]+1;
				f[0][a[had]]+=f[1][had];
				f[1][a[had]]=min(f[1][a[had]],f[1][had]-f[0][had]);
				had=a[had];
			}
			if(f[1][i]==INF) f[1][i]=0;
			else f[1][i]=f[0][i]-f[1][i]+1;
			had=a[a[i]];
			while(had!=a[i]){
				if(g[1][had]==INF) g[1][had]=0;
				else g[1][had]=g[0][had]-g[1][had]+1;
				g[0][a[had]]+=g[1][had];
				g[1][a[had]]=min(g[1][a[had]],g[1][had]-g[0][had]);
				ru[had]=0;
				had=a[had];
			}
			ru[a[i]]=0;
			if(g[1][a[i]]==INF) g[1][a[i]]=0;
			else g[1][a[i]]=g[0][a[i]]-g[1][a[i]]+1;
			ans+=max(f[1][i],g[1][a[i]]);
		}
	}
	cout<<ans;
	return 0;
}

T2元旦晚会

题面描述:

给你 \(m\) 个区间形如 \([a_i,b_i]\) (保证 \(a_i,b_i\le n\) ),第 \(i\) 个区间需要满足有 \(c_i\) 个话筒,求满足条件的最少话筒数量。

思路:

贪心。

先按照右端点进行降序排序,再遍历每一个区间,从最右边开始填,知道满足要求为止。

标签:

贪心

代码实现

this
#include<bits/stdc++.h>
#define pr3 pair<pair<int,int>,int>
#define l first.second
#define r first.first
#define num second
#define N 30005
#define M 5005
using namespace std;
int n,m,vis[N],ans;
pr3 a[M];
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++) cin>>a[i].l>>a[i].r>>a[i].num;
	sort(a+1,a+m+1);
	for(int i=1;i<=m;i++){
		for(int j=a[i].l;j<=a[i].r;j++) a[i].num-=vis[j];
		if(a[i].num<=0) continue;
		while(a[i].r>=a[i].l&&a[i].num){
			a[i].num-=(!vis[a[i].r]);
			vis[a[i].r]=1;
			a[i].r--;
		}
	}
	for(int i=1;i<=n;i++) ans+=vis[i];
	cout<<ans;
	return 0;
}

T3田忌赛马

题面描述:

\(a\)\(b\) 两组数各 \(n\) 个,现在你需要将这两组数两两匹配,若匹配后 \(a_i>b_i\),则贡献 \(+200\),若 \(a_i=b_i\),则无贡献,若\(a_i<b_i\),则贡献 \(-200\)。求匹配后的最大贡献值。

思路:

先将数组 \(a,b\) 递减排序,接下来使用 \(dp\)。设 \(f_{i,j}\) 表示前 \(j\) 个数匹配后有 \(i\) 个没有减贡献且最后一个没有减贡献的匹配的数为 \(a_j\),则可以得到 \(dp\) 式为:

\[f_{i,j}=f_{i-1,k}+[a_i>b_k]\times 200 \]

再通过前缀最小优化即可通过。

标签:

dp
排序

代码实现

this
#include<bits/stdc++.h>
#define N 2005
using namespace std;
int n,a[N],b[N],f[N][N];
int vis[N],ans;
int cmp(int x,int y){ return x>y; }
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	sort(b+1,b+n+1,cmp);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i]<b[j]) continue;
			f[i][j]=f[i-1][j-1]+(a[i]>b[j])*200;
		}
		ans=max(ans,f[i][n]-200*(n-i));
		for(int j=2;j<=n;j++) f[i][j]=max(f[i][j],f[i][j-1]);
	}
	cout<<ans;
	return 0;
}

T4[APIO/CTSC2007] 数据备份

题目描述

已知办公楼都位于同一条街上。你需要选择 \(K\) 对办公楼,使得每一对办公楼之间的距离之和尽可能小(要求匹配中的楼是相异的)。

思路:

贪心易得:这些线路不相交且肯定是两个相邻办公室的。所以先使用差分计算出每相邻的两个办公楼之间的距离。

接下来就是 \(dp\)。设 \(f_{i,j}\) 表示前 \(i\) 个电缆的最后一个是 \(j\) 时的电缆最短长度,则 \(dp\) 式为:

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

易优化得:

f[0]=INF;
for(int i=1;i<=n;i++) f[i]=min(f[i-1],a[i]);
for(int i=2;i<=m;i++){
	for(int j=n;j>=i+i-1;j--) f[j]=f[j-2]+a[j];
	for(int j=i+i;j<=n;j++) f[j-1]<f[j]?f[j]=f[j-1]:0;
}

可继续优化为:

f[0]=INF;
for(int i=1;i<=n;i++) f[i-1]<a[i]?f[i]=f[i-1]:f[i]=a[i];
for(int i=2;i<=m;i++){
	f[1]+=a[i+i-1];
	for(int j=2;j<=n-i-i+2;j++) f[j-1]<f[j]+a[j+i+i-2]?f[j]=f[j-1]:f[j]+=a[j+i+i-2];
}
cout<<f[n-m-m+2];

因为最后求得为 \(f_{n-m-m-2}\) 且求解 \(f_i\) 与其后的无关,则可将二层循环的终点设置为 \(n-m-m-2\)

标签:

dp
贪心

代码实现

this
#include<bits/stdc++.h>
#define N 100005
#define INF 0x3f3f3f3f
using namespace std;
int n,m,a[N],f[N];
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	n--;
	for(int i=0;i<=n;i++) cin>>a[i];
	for(int i=n;i;i--) a[i]=a[i]-a[i-1];
	f[0]=INF;
	for(int i=1;i<=n;i++) f[i-1]<a[i]?f[i]=f[i-1]:f[i]=a[i];
    n-=m*2-2;
	for(int i=2;i<=m;i++){
		f[1]+=a[i+i-1];
		for(int j=2;j<=n;j++) f[j-1]<f[j]+a[j+i+i-2]?f[j]=f[j-1]:f[j]+=a[j+i+i-2];
	}
	cout<<f[n];
	return 0;
}

T5[TJOI]拯救小矮人

题面描述:

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

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

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

问最多可以使多少个小矮人逃跑。

思路:

第一步,排序。按照 \(a_i+b_i\) 升序排序
第二步,\(dp\) 。设 \(f_{i,j}\) 表示前 \(i\) 个矮人中,有 \(j\) 个矮人逃出。
结束

标签:

dp
贪心

代码实现

this
#include<bits/stdc++.h>
#define N 2005
#define INF 0x3f3f3f3f
using namespace std;
struct node{	
	int a,b;
}a[N];
int n,h,mx,ans;
int f[N];
int cmp(node x,node y){
	return x.a+x.b<y.a+y.b;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].a>>a[i].b,mx+=a[i].a;
	cin>>h;
	sort(a+1,a+n+1,cmp);
	f[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=n;j>=i;j--){
			f[j]=INF;
			for(int k=i-1;k<j;k++){
				if(mx-h+a[j].b>=f[k]) f[j]=min(f[j],f[k]);
			}
			if(f[j]!=INF) f[j]+=a[j].a,ans=i;
		}
		if(ans!=i) break;
	}
	cout<<ans;
	return 0;
}

注:

  1. 题号为本校OJ上的链接,题名为原出处链接。

$$The\ end$$

posted @ 2025-03-13 20:49  skx_515  阅读(9)  评论(0)    收藏  举报