【解题报告】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 保卫王国
思路
不会不会,是真的不会
哭哭