NOIP2018提高组题解

D1T1 铺设道路((OK))

D1T2 货币系统((OK))

D1T3 赛道修建((OK))

D2T1 旅行((OK))

D2T2 填数游戏

D2T3 保卫王国

D3T1 旅行(数据加强版)

懒得放题面了,题意也不解释了,默认都知道.

(D1T1)是原题,从第二个开始,如果这个比前面大,就产生差值的贡献,最后加上第一个的值就是答案了.拓展:考虑环形怎么做?断环为链,从值最小的那里断开,就还是上面那样做就可以了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
int main(){
	int n=read(),ans=read(),last=ans;
	for(int i=2;i<=n;++i){
		int x=read();
		if(x>last)ans+=x-last;
		last=x;
	}
	printf("%d
",ans);
    return 0;
}

(D1T2)应该是个完全背包的模板题了,把(a_i)从小到大排序,设(f[j]=0/1)表示用当前的面值能否凑出(j),因为值域最大为(25000),暴力跑(f[j]|=f[j-a[i]])就行了.如果一个面值(a_i),它在之前的(1)~(i-1)中,(f[a_i]=1),说明这个面值可以不要.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=105;
const int M=25005;
int a[N],f[M];
int main(){
	int T=read();
	while(T--){
		int n=read();
		for(int i=1;i<=n;++i)a[i]=read();
		sort(a+1,a+n+1);int ans=n;
		memset(f,0,sizeof(f));f[0]=1;
		for(int i=1;i<=n;++i){
			if(f[a[i]])--ans;
			for(int j=a[i];j<=25000;++j)f[j]|=f[j-a[i]];		
		}
		printf("%d
",ans);
	}
    return 0;
}

(D1T3)(55)分的特殊性质还是很好写的,结论也都很容易,这里不再说了,我的代码特意写了这三档特殊性质.

直接讲正解,就是题解第一篇的做法了.掌握好(multiset)的做法就很好理解了.二分答案很显然吧(求最小值最大),对于当前二分的(mid),大概就是对于一条合法的赛道,在树中无非就是两种情况:

一.一条链,对于这种情况,我们从子节点往父亲节点传递信息的时候就可以记录.

二.两条链拼在一起的,这种情况就是其中任意一条链的长度都小于(mid),所以我们用一个(multiset)来记录这种链.

对于一条长度为(x(x<mid))的链,在(multiset)找到最小的能够使得(x+y>=mid)(y),让这两个去拼在一起即可.因为我们维护的时候每次只从儿子节点向父亲节点传递一条边权最大的不超过(mid)的边,所以(multiset)中的边一定保证能够拼在一起,即这两条边的连接点就是当前的父亲节点.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define rg register
#define ll long long
#define IT multiset<int>::iterator
using namespace std;
inline int read(){
    rg int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=50005;
int n,m,maxn,sum,dis[N],he[N];
int tot,head[N],nxt[N<<1],to[N<<1],w[N<<1];
inline void add(rg int a,rg int b,rg int c){nxt[++tot]=head[a];head[a]=tot;to[tot]=b;w[tot]=c;}
inline void dfs1(rg int u,rg int fa){
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];if(v==fa)continue;
		dis[v]=dis[u]+w[i];dfs1(v,u);
	}
}
inline bool check2(rg int mid){
	dfs1(1,0);rg int last=0,cnt=0;
	for(rg int i=1;i<=n;++i){
		if(dis[i]-dis[last]>=mid){
			++cnt;last=i;
		}
	}
	return cnt>=m;
}
multiset<int>s[N];
inline int dfs(rg int u,rg int fa,rg int mid){
	s[u].clear();
	for(int i=head[u];i;i=nxt[i]){
		rg int v=to[i];if(v==fa)continue;
		rg int val=dfs(v,u,mid)+w[i];
		if(val>=mid)++sum;//如果这条链能单独构成一条路径,就自己去玩
		if(val<mid)s[u].insert(val);//如果不能就插入当前节点的multiset中
	}
	rg int maxx=0;//找到一个最大的不超过mid的链向上传给父亲节点
	while(s[u].size()){
		if(s[u].size()==1)return max(maxx,(*s[u].begin()));//multiset中只有一条链	
		rg IT it=s[u].lower_bound(mid-*s[u].begin());//找到最小的与当前这条链(*s[u].begin())相加超过mid的链
		if(it==s[u].begin()&&s[u].count(*it)==1)++it;//找到了自己,且自己只有一条,那么就选下一条与自己匹配
		if(it==s[u].end()){//如果没找到,就删掉这条链
			maxx=max(maxx,*s[u].begin());
			s[u].erase(*s[u].begin());
		}
		else{//如果找到了,就更新答案,并删掉这两条匹配的链
			++sum;
			s[u].erase(s[u].find(*s[u].begin()));
			s[u].erase(s[u].find(*it));
		}
	}
	return maxx;//向父亲节点回溯最大的一条链
}
inline bool check(rg int mid){sum=0;dfs(1,0,mid);return sum>=m;}
int main(){
	n=read();m=read();rg int bj1=1,bj2=1;
	for(rg int i=1;i<n;++i){
		rg int a=read(),b=read(),c=read();
		add(a,b,c);add(b,a,c);maxn+=c;he[i]=c;
		if(a!=1)bj1=0;if(b!=a+1)bj2=0;
	}
	if(m==1){//只用找一条最长的路,那么就是求树的直径(两次dfs/bfs,dp三种求法)
		dfs1(1,0);rg int maxx=0,pos=1;
		for(rg int i=1;i<=n;++i)if(dis[i]>maxx)maxx=dis[i],pos=i;
		dis[pos]=0;dfs1(pos,0);maxx=0;for(rg int i=1;i<=n;++i)maxx=max(maxx,dis[i]);
		printf("%d
",maxx);return 0;
	}
	if(bj1){//以节点1为中心的菊花图,2m条边中,最小的边配最大的边即可,少了先补上边权为0的边.
		rg int sum=n-1,minn=1<<30;
		while(sum<2*m)he[++sum]=0;
		sort(he+1,he+sum+1);
		for(rg int i=sum-2*m+1;i<=sum-m;++i)
			minn=min(minn,he[i]+he[2*sum-2*m+1-i]);
		printf("%d
",minn);return 0;
	}
	if(bj2){//一条链的情况,就相当于一个序列,让你去划分成m段,二分答案的模板题?
		rg int l=0,r=maxn,Ans,mid;
		while(l<=r){
			mid=(l+r)>>1;
			if(check2(mid))Ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d
",Ans);return 0;
	}
	rg int l=0,r=maxn,Ans,mid;
	while(l<=r){//正解
		mid=(l+r)>>1;		
		if(check(mid))Ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d
",Ans);return 0;
}

(D2T1)好暴力啊,对于(m=n-1)的情况就是一棵树,那么直接贪心每次拓展能拓展的节点里面编号最小的即可.对于(m=n)的情况,先找出环,然后暴力枚举环上的边,删边之后就是一棵树了,还是像上面那样贪心去找即可.

之前没有找环,而是直接暴力删(m)条边,不开(O_2)差一点点就卡过了.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define rg register
using namespace std;
inline int read(){
    rg int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return x*o;
}
const int N=5005;
int n,m,now,visit[N],he[N],ans[N];
int tim,cnt,dfn[N],cir[N],fa[N];
vector<int>g[N];
inline void dfs(rg int u){
	for(rg int i=0;i<(int)g[u].size();++i){
		rg int v=g[u][i];if(visit[v])continue;
		visit[v]=1;printf(" %d",v);dfs(v);
	}
}
inline void DFS(rg int u,rg int del1,rg int del2){
	for(rg int i=0;i<(int)g[u].size();++i){
		rg int v=g[u][i];
		if(visit[v])continue;
		if((u==del1&&v==del2)||(u==del2&&v==del1))continue;
		visit[v]=1;he[++now]=v;DFS(g[u][i],del1,del2);
	}
}
inline bool check(){
	for(rg int i=1;i<=n;++i)
		if(he[i]^ans[i])return ans[i]>he[i];
	return 0;
}
inline void dfs_circle(int u,int father){
	dfn[u]=++tim;fa[u]=father;
	for(int i=0;i<(int)g[u].size();++i){
		int v=g[u][i];
		if(!dfn[v])dfs_circle(v,u);
		else if(dfn[v]>dfn[u]){
			for(int now=v;now!=u;now=fa[now])cir[++cnt]=now;
			cir[++cnt]=u;
		}
	}
}
int main(){
	n=read();m=read();
	for(rg int i=1;i<=m;++i){
		rg int a=read(),b=read();
		g[a].push_back(b);g[b].push_back(a);
	}
	for(rg int i=1;i<=n;++i)
		if((int)g[i].size())sort(g[i].begin(),g[i].end());
	if(m==n-1){
		visit[1]=1;printf("1");
		dfs(1);printf("
");return 0;
	}
	if(m==n){
		dfs_circle(1,0);
		for(rg int i=1;i<=n;++i)ans[i]=n+1;
		for(rg int i=1;i<cnt;++i){
			for(rg int j=1;j<=n;++j)visit[j]=he[j]=0;
			he[1]=1;visit[1]=1;now=1;DFS(1,cir[i],cir[i+1]);
			if(now<n)continue;
			if(check())for(rg int j=1;j<=n;++j)ans[j]=he[j];
		}
		for(rg int i=1;i<n;++i)printf("%d ",ans[i]);
		printf("%d
",ans[n]);
	}
    return 0;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11762000.html