【解题报告】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 货币系统
思路
题意就是你有 n n n 个数字,然后你要将这些数字加起来,看有没有不能表示的数字
然后看能不能用 m ≤ n m \le n m ≤ n 个数字使这些数字的不能表示的数字和原来用 n n n 个数字不能表示数字的集合是同一个集合
我们考虑枚举
自己肯定是有的
我们对已有的钱数量进行排序,然后我们对它们进行枚举
枚举某个钱数是否能搞到,前提是这个 i i i 的这个钱有
然后我们就依次枚举这个钱和其他钱的组合
有人说了,那不得枚举到正无穷去?
不对,如果有不同的话,我们已经在一个最大的钱 m o n e y n money_n m o n e y 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
简要题意就是
给定一棵树的信息,求这棵树 m m m 条互不相交的链中的最小值的最大值可以是多少
求最小值最大 ,显然的二分
我们可以知道,如果一条链,经过了一个结点 i i i 的父节点,同时包含了 i i i 到子节点的某条边,那么它就只能包含 i i i 到所有子节点中边的一条
于是我们推出来,对于任意结点 i i i 和它的子节点 j , k j,k j , k 以及其中的一条链 i → j i \rightarrow j i → j ,我们有三种情况需要讨论
不要
我们用 j → i → k j \rightarrow i \rightarrow k j → i → k 的链
我们用 f i → i → j f_i \rightarrow i \rightarrow j f i → i → j 的链
所以,我们对于 i i i 尝试连边,然后如果连一个的话,我们就把新的边长向上传递,和父节点并成同一个链
我们是在二分最小值,所以按照上面的步骤连边之后,如果链长大于 l e n len l e n 的话,我们就重新开一个新的链,如果链数不够的话,说明我们最小的还要更小
我们每次传回的父节点一定要足够长
所以有 l e n p r e + l e n n o w ≥ b i n a r y l e n len_{pre}+len_{now} \ge binary_{len} l e n p r e + l e n n o w ≥ b i n a r y l e n
然后用 m u l t i s e t multiset m u l t i s e t 维护一下就好了
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 ( n 2 ) O(n^2) 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 保卫王国
思路
不会不会,是真的不会
哭哭