【解题报告】NOIP2018

【解题报告】NOIP2018

她开始变了……

Day1

T1 铺设道路

思路

CCF狠起来连自己都抄

就是2013年的积木大赛么

甚至都是春春幼儿园……

#include <iostream>
using namespace std;
int main()
{
    int n,a,last=0,ans=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a;
        if(a>last)ans+=(a-last);
        last=a;
    }
    cout<<ans<<endl;
}

T2 货币系统

思路

题意就是你有 \(n\) 个数字,然后你要将这些数字加起来,看有没有不能表示的数字

然后看能不能用 \(m \le n\) 个数字使这些数字的不能表示的数字和原来用 \(n\) 个数字不能表示数字的集合是同一个集合

我们考虑枚举

自己肯定是有的

我们对已有的钱数量进行排序,然后我们对它们进行枚举

枚举某个钱数是否能搞到,前提是这个 \(i\) 的这个钱有

然后我们就依次枚举这个钱和其他钱的组合

有人说了,那不得枚举到正无穷去?

不对,如果有不同的话,我们已经在一个最大的钱 \(money_n\) 的这个范围之内找出来的,而其他的都是和他属于大概是同余的关系了,所以我们只用枚举到最后一个钱那么大行了

如果两个钱加起来超过最大的钱的话,我们就直接 break 掉就好了

所以最后的代码就是枚举

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
int T;
int ex[50005]={0},money[1005]={0};
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
	return x*f;
}
int main()
{
	T=read();
	while(T--)
	{
		int n=read();
		memset(ex,0,sizeof(ex));
		memset(money,0,sizeof(money));
		for(int i=1;i<=n;i++)
		{
			money[i]=read();
			ex[money[i]]=2;
		}
		sort(money+1,money+1+n);
		for(int i=1;i<=money[n];i++)
		{
			if(ex[i]>0)
			{
				for(int j=1;j<=n;j++)
				{
					if(i+money[j]<=money[n])
					ex[i+money[j]]=1;
					else 
					break;
				}
			}
		}
		int ans=0;
		for(int i=1;i<=money[n];i++)
		if(ex[i]==2)
		ans++;
		cout<<ans<<endl;
	}
	return 0;
}

T3 赛道修建

思路

思路沿用自 @first_fan

简要题意就是

给定一棵树的信息,求这棵树 \(m\) 条互不相交的链中的最小值的最大值可以是多少

最小值最大,显然的二分

我们可以知道,如果一条链,经过了一个结点 \(i\) 的父节点,同时包含了 \(i\) 到子节点的某条边,那么它就只能包含 \(i\) 到所有子节点中边的一条

于是我们推出来,对于任意结点 \(i\) 和它的子节点 \(j,k\) 以及其中的一条链 \(i \rightarrow j\) ,我们有三种情况需要讨论

  • 不要
  • 我们用 \(j \rightarrow i \rightarrow k\) 的链
  • 我们用 \(f_i \rightarrow i \rightarrow j\) 的链

所以,我们对于 \(i\) 尝试连边,然后如果连一个的话,我们就把新的边长向上传递,和父节点并成同一个链

我们是在二分最小值,所以按照上面的步骤连边之后,如果链长大于 \(len\) 的话,我们就重新开一个新的链,如果链数不够的话,说明我们最小的还要更小

我们每次传回的父节点一定要足够长

所以有 \(len_{pre}+len_{now} \ge binary_{len}\)

然后用 \(multiset\) 维护一下就好了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <set>
#define int long long
using namespace std;
const int maxn=50005;
struct edge{
	int e,next,val;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e,int val)
{
	en++;
	ed[en].next=first[s];
	first[s]=en;
	ed[en].e=e;
	ed[en].val=val;
}
int n,m,mid,ans;
int f[maxn],g[maxn];
multiset <int> s;
multiset <int>::iterator it;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void init()
{
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
}
void dfs(int x,int fa)
{
	for(int i=first[x];i;i=ed[i].next)
	{
		int e=ed[i].e;
		if(e==fa) continue;
		dfs(e,x);
		f[x]+=f[e];
	}
	for(int i=first[x];i;i=ed[i].next)
	{
		int e=ed[i].e;
		int val=ed[i].val;
		if(e==fa) continue;
		s.insert(g[e]+val);
	}
	while(s.size())
	{
		int now=*s.rbegin();
		if(now>=mid)
		{
			it=s.find(now);
			f[x]++;
			s.erase(it);
		}
		else break;
	}
	while(s.size())
	{
		int now=*s.begin();
		s.erase(s.begin());
		it=s.lower_bound(mid-now);
		if(it==s.end())
		g[x]=now;
		else 
		{
			f[x]++;
			s.erase(it);
		}
	}
}
signed main()
{
	int l=0,r=1e8;
	n=read(),m=read();
	for(int i=1;i<=n-1;i++)
	{
		int x=read(),y=read(),z=read();
		add_edge(x,y,z);
		add_edge(y,x,z);
		
		l=min(l,z);
		r+=z;
	}
	while(l<=r)
	{
		init();
		mid=(l+r)>>1;
		dfs(1,0);
		if(f[1]>=m) ans=max(ans,mid),l=mid+1;
		else r=mid-1;
	}
	cout<<ans<<'\n';
	return 0;
}

Day2

T1 旅行

思路

贪心的部分分有40pts,我们可以在10min内拿到

我们考虑直接dfs,对于每个点找出它编号最小的儿子先遍历,这样就有40pts了

但是没有考虑可以往回走,然后走到一个更小结点的情况,能有四十分已经很人性了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
const int maxn=2005;
struct edge{
	int e,next;
}ed[maxn<<1];
int en,first[maxn];
void add_edge(int s,int e)
{
	en++;
	ed[en].next=first[s];
	first[s]=en;
	ed[en].e=e;
}
int n,m;
int ans[maxn],now[maxn],tot;
bool vis[maxn];
void dfs(int x,int fa)
{
	priority_queue <int> q;
	if(!vis[x])
	{
		ans[++tot]=x;
		vis[x]=true;
	}
	
	for(int i=first[x];i;i=ed[i].next)
	{
		int e=ed[i].e;
		if(e==fa) continue;
		q.push(-e);
	}
	while(q.size())
	{
		int e=-q.top();
		q.pop();
		dfs(e,x);
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	for(int i=1;i<=tot;i++)
	cout<<ans[i]<<" ";
	cout<<'\n';
	return 0;
}

vector 储存然后提前排好序甚至可以60pts

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=5005;
int n,m;
int ans[maxn],tot;
bool vis[maxn];
vector <int> g[maxn];
void dfs(int x,int fa)
{
	if(vis[x]) return ;
	vis[x]=true;
	ans[++tot]=x;
	for(int i=0;i<g[x].size();i++)
	{
		int e=g[x][i];
		if(e==fa) continue;
		dfs(e,x);
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);`
	}
	for(int i=1;i<=n;i++)
	sort(g[i].begin(),g[i].end());
	dfs(1,0);
	for(int i=1;i<=tot;i++)
	cout<<ans[i]<<" ";
	cout<<'\n';
	return 0;
}

然后暴力枚举删哪一条边,可以 \(O(n^2)\) 通过此题

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#define int long long
using namespace std;
const int maxn=5005;
int n,m;
int ru,rv;
int ans[maxn],tot,res[maxn];
bool vis[maxn];
bool v[maxn];
vector <int> g[maxn];
struct edge{
	int p,q;
}a[maxn<<1];

void dfs(int x,int fa)
{
	if(vis[x]) return ;
	vis[x]=true;
	ans[++tot]=x;
	for(int i=0;i<g[x].size();i++)
	{
		int e=g[x][i];
		if(e==fa) continue;
		dfs(e,x);
	}
}

void dfs1(int x,int fa)
{
	if(vis[x]) return ;
	vis[x]=true;
	res[++tot]=x;
	for(int i=0;i<g[x].size();i++)
	{
		int e=g[x][i];
		if(e==fa) continue;
		if((x==ru&&e==rv)||(x==rv&&e==ru))
		continue;
		dfs1(e,x);
	}
}

bool check()
{
	for(int i=1;i<=n;i++)
	{
		if(res[i]<ans[i]) 
		return true;
		else if(res[i]>ans[i])
		return false;
	}
	return false;
}
void cp()
{
	for(int i=1;i<=n;i++)
	ans[i]=res[i];
}
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
		a[i].p=u;
		a[i].q=v;
	}
	
	for(int i=1;i<=n;i++)
	sort(g[i].begin(),g[i].end());
	
	if(n==m)
	{
		bool flag=true;
		for(int i=1;i<=m;i++)
		{
			ru=a[i].p,rv=a[i].q;
			memset(vis,false,sizeof(vis));
			tot=0;
			dfs1(1,0);
			if(tot<n) continue;
			if(flag)
			{
				cp();
				flag=false;
			}
			if(check())
			cp();
		}
	}
	else 
	dfs(1,0);
	
	for(int i=1;i<=n;i++)
		cout<<ans[i]<<" ";
	cout<<'\n';
	
	return 0;
}

其实我们可以找到环,然后再暴力断环上的边的,但我不想写了

T2 填数游戏

思路

有人说用搜索,有人说用状压

我说我啥都不会,那就打表找规律吧

规律蕴含在代码之中

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#define int long long 
using namespace std;
const int mod=1e9+7;
int n,m;
inline int ksm(int a,int b,int p)
{
	int res=1%p;
	while(b)
	{
		if(b&1)
		res=1ll*res%p*a%p;
		a=1ll*a%p*a%p;
		b>>=1;
	}
	return res%p;
}
signed main()
{
	cin>>n>>m;
	if(n>m)
	swap(n,m);
	if(n==1)
		cout<<ksm(2,m,mod)%mod<<'\n';
	else if(n==2)
		cout<<4*ksm(3,m-1,mod)%mod<<'\n';
	else if(n==3)
		cout<<112*ksm(3,m-3,mod)%mod<<'\n';
	else
	{
		if(m==n)
		cout<<((83*ksm(8,n,mod)%mod+5*ksm(2,n+7,mod)%mod)%mod*190104168%mod)%mod<<'\n';
		else
		cout<<((83*ksm(8,n,mod)%mod+ksm(2,n+8,mod))*ksm(3,m-n-1,mod)%mod*570312504%mod)%mod<<'\n'; 
	}
	return 0;
}

T3 保卫王国

思路

不会不会,是真的不会

哭哭

上一篇:P5020 [NOIP2018 提高组] 货币系统


下一篇:P5020 [NOIP2018 提高组] 货币系统 题解