【解题报告】NOIP2018

她开始变了……

Day1

T1 铺设道路

思路

CCF狠起来连自己都抄

就是2013年的积木大赛么

甚至都是春春幼儿园……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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 货币系统

思路

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

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

我们考虑枚举

自己肯定是有的

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

枚举某个钱数是否能搞到,前提是这个 ii 的这个钱有

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

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

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

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

所以最后的代码就是枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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

简要题意就是

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

最小值最大,显然的二分

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

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

  • 不要
  • 我们用 jikj \rightarrow i \rightarrow k 的链
  • 我们用 fiijf_i \rightarrow i \rightarrow j 的链

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

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

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

所以有 lenpre+lennowbinarylenlen_{pre}+len_{now} \ge binary_{len}

然后用 multisetmultiset 维护一下就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#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了

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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(n2)O(n^2) 通过此题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#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 填数游戏

思路

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

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

规律蕴含在代码之中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#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 保卫王国

思路

不会不会,是真的不会

哭哭