# | 题目 |
---|---|
A | 【NOIP2018提高组 day1】铺设道路 |
B | 【NOIP2018提高组 day1】货币系统 |
C | 【NOIP2018提高组 day1】赛道修建 |
A. 【NOIP2018提高组 day1】铺设道路
看到这个题第一眼还不敢相信,这不跟之前做过的一道题一毛一样吗?反反复复看了好几遍,确定是原题没错了,参考【NOIP2013提高组 day2】积木大赛
B. 【NOIP2018提高组 day1】货币系统
思考了一会我就发现了这个题就是让你找所有能被其他面额表示出来的面额数量,看数据量也不大就打了个dfs,结果超时了,,,oh好伤心
正解也是这个思路,但运用了类似埃氏筛的思想,先给拥有的面额打上标记2,从1遍历到最大面额,每发现一个打上标记的面额就把这个面额分别加上拥有的面额所得到的面额打上标记2,表示这个面额可以用其他面额表示
最后遍历所有面额,如果标记为2表示这个面额无法被代替,ans++
因为面额最大为25000,n最大为100,所以中间的那层循环最多2500000次,确实挺快的
以后一定要慎用dfs!!!!!!
#include<bits/stdc++.h>
using namespace std;
int a[1001],n,ans,f[25005];
int main(){
// freopen("money.in","r",stdin);
// freopen("money.out","w",stdout);
int t;
scanf("%d",&t);
while(t--){
memset(f,0,sizeof(f));
ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),f[a[i]]=2;
sort(a+1,a+n+1);
for(int i=1;i<=a[n];i++){
if(f[i]){
for(int j=1;j<=n;j++){
if(i+a[j]>a[n])break;
f[i+a[j]]=1;
}
}
}
for(int i=1;i<=a[n];i++)if(f[i]==2)ans++;
printf("%d",ans);
if(t)printf("\n");
}
return 0;
}
C. 【NOIP2018提高组 day1】赛道修建
据说这道题暴力分很多,,,确实,数据说明中给了很多特殊构造,考试时我也是想把m==1和一条链的那几分拿到,,结果能力有限,,,
m==1时相当于求树的直径,可以用两次bfs或者树形dp,但之前我都没听过这两种做法,只能傻乎乎的从每个点出发跑一遍spfa,,妥妥的超时,不过至少以后会求树的直径了,不亏
一条链的情况我居然没想出来,,,当时居然忘了可以把权值存到数组里 ,真傻了。这部分分可以从一个点出发遍历整棵树,把边的权值存到以点为下标的数组里,然后就可以二分答案,从1到n-1遍历数组,每次sum+=a[i],如果sum>=mid,ans++,sum=0.最后判断ans与m的大小关系。
还有一部分暴力分是a[i]=1时,我会告诉你我考试时把这个理解成了所有边权值为1么?
显然,出题人在疯狂暗示我们这是菊花图,只要把所有边权值排序,每次用最小的匹配最大的,然后ans保存其中和最小的就了
瞎bb了一大堆暴力打发,终于开始讲正解了!!!
正解:一个神奇的stl容器 multiset
这玩意儿之前我也听过,但从来没用过,这道题正好让我熟悉一下它的用法。依然是二分答案,不过这里r的取值可以取树的直径,也可以直接取所有边的权值和,当然后者简单的多,不过我为了熟悉树的直径的代码就用前者打了一遍。
然后我们每次就从1号节点出发(因为整张图是连通的,从哪里出发都没关系),递归到达叶节点后每次把最大的权值传递给父节点,如果这个值大于mid,直接ans++,表明已经找到一条赛道,不然就加入到multiset,如果multiset不为空,就取出当前最小的元素(就是第一个元素)为他找另一个权值是两者相加大于mid,然后让相加得到的新权值取代两个权值重新加入multiset,如果找不到能满足条件的另一个元素,说明这条边没用,直接从multiset中删除就行了
啊,打字好累啊,不过下次遇到这种题正解还是不会打,,,只求能把暴力分拿到
#include<bits/stdc++.h>
#define inf 999999999
using namespace std;
int n,m,idx,head[100100],f[1001][1001],ans,v[100100],dis[100100],book[100100];
struct node{
int e,n,w;
}pr[100100];
void add(int u,int v,int w){
pr[++idx].e=v;
pr[idx].w=w;
pr[idx].n=head[u];
head[u]=idx;
}
int dfs1(int x,int fa){//树形dp求树的直径
int max1=0,max2=0;
for(int i=head[x];i;i=pr[i].n){
int y=pr[i].e;
if(y==fa)continue;
max2=max(max2,dfs1(y,x)+pr[i].w);
if(max2>max1)swap(max1,max2);
}
ans=max(ans,max1+max2);
return max1;
}
multiset<int>s[100100];
multiset<int>::iterator it;
int dfs(int x,int fa,int k){
s[x].clear();
int val;
for(int i=head[x];i;i=pr[i].n){
int y=pr[i].e;
if(y==fa)continue;
val=dfs(y,x,k)+pr[i].w;
if(val>=k)ans++;
else s[x].insert(val);
}
int maxn=0;
while(!s[x].empty()){
if(s[x].size()==1)return max(maxn,*s[x].begin());
it=s[x].lower_bound(k-*s[x].begin());
if(it==s[x].begin()&&s[x].count(*it)==1)it++;
if(it==s[x].end()){
maxn=max(maxn,*s[x].begin());
s[x].erase(s[x].find(*s[x].begin()));
}
else{
ans++;
s[x].erase(s[x].find(*it));
s[x].erase(s[x].find(*s[x].begin()));
}
}
return maxn;
}
int check(int sm){
ans=0;
dfs(1,0,sm);
if(ans>=m)return 1;
return 0;
}
int main(){
// freopen("track.in","r",stdin);
// freopen("track.out","w",stdout);
scanf("%d%d",&n,&m);
int sr=inf;
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dfs1(1,0);
int l=0,r=ans,re;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))re=mid,l=mid+1;
else r=mid-1;
}
printf("%d",re);
return 0;
}