题目整理-3(练习2)
T1、[POI2004] SZP
题面描述:
有 \(n\) 个人,每个人会监视除自己外的一个人,求选择尽量多的同学去搬卷子和答题卡,且使得对于这些同学中的每一名同学,至少有一位监视她的同学没有被选中。问最多可以选择多少同学?
思路:
首先,易想到建图,每个人向他所监视的人连有向边。
通过思考或被提醒手搓数据观察可得,每一个联通的图中,必然有且仅有一个环,且易证。
因此,这是一个基环树我们可以尝试断环为链,再进行处理(肯定可以找到一个树根)。在第一次的断环为链中,我们使用树形 \(dp\) 计算树根处的最大值,具体 \(dp\) 式如下:
注:\(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\) 式为:
再通过前缀最小优化即可通过。
标签:
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[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;
}
注:
- 题号为本校OJ上的链接,题名为原出处链接。

浙公网安备 33010602011771号