NOIP2018提高组题解

day1:

T1:
题目:luogu5019.
题目大意:给定一个长度为 n n n的数组 A [ i ] A[i] A[i],要求每次可以将一段连续正数区间减 1 1 1,要求用最少的操作次数将 A [ i ] A[i] A[i]全部变为 0 0 0.
1 ≤ n ≤ 1 0 5 , 1 ≤ A [ i ] ≤ 1 0 4 1\leq n\leq 10^5,1\leq A[i]\leq 10^4 1n105,1A[i]104.

这貌似是NOIP原题,显然当一个数比它前面的数小的时候,这个数不会有贡献,否则这个数会产生与前面的数的差的贡献.

所以我们只需要枚举一下 i i i就可以了,时间复杂度 O ( n ) O(n) O(n).

代码如下:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=100000;
 
int a[N+9],n;
LL sum; 
 
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i)
    scanf("%d",&a[i]);
}
 
Abigail work(){
  for (int i=1;i<=n;++i)
    if (a[i]>a[i-1]) sum+=a[i]-a[i-1];
}
 
Abigail outo(){
  printf("%lld\n",sum);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}



T2:
题目:luogu5020.
题目大意:给定一个系统 ( n , a ) (n,a) (n,a)表示数组 a a a n n n个数,现在定义两个系统 ( n , a ) (n,a) (n,a) ( m , b ) (m,b) (m,b)相同是指这个数组 a a a的数能够组成的所有数 b b b也能组成,而 a a a不能组成的数 b b b也不能组成.现在给定一个系统 ( n , a ) (n,a) (n,a),要求 m m m最小的系统 ( b , m ) (b,m) (b,m)满足 ( b , m ) (b,m) (b,m) ( a , n ) (a,n) (a,n)相同.
1 ≤ n ≤ 100 , 1 ≤ a [ i ] ≤ 2.5 ∗ 1 0 4 1\leq n\leq 100,1\leq a[i]\leq 2.5*10^4 1n100,1a[i]2.5104.

考场先是想到ex_gcd,然后突然发现解不能是负数.

考场上想到应该就是要将在 ( n , a ) (n,a) (n,a)所有能用 a a a数组内其他数组成的数给去掉,想了很久也没有反例.

然后就想到用一个背包来完成这个事情,时间复杂度 O ( n max ⁡ { a [ i ] } ) O(n\max\left \{ a[i] \right \}) O(nmax{a[i]}),可过.

代码如下:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=100,M=25000;
 
int n,ma,a[N+9];
int f[M+9],ans;
 
void add(int &x,int y){
  x+=y;
  if (x>2) x=2;
}
 
Abigail start(){
  for (int i=1;i<=ma;++i)
    f[i]=0;
  ans=0;
}
 
Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i){
    scanf("%d",&a[i]);
    ma=max(ma,a[i]);
    f[a[i]]=1;
  }
}
 
Abigail work(){
  n=0;
  for (int i=1;i<=ma;++i)
    if (f[i]) a[++n]=i;
  for (int i=1;i<=n;++i)
    for (int j=a[i];j<=ma;++j)
      add(f[j],f[j-a[i]]);
}
 
Abigail outo(){
  for (int i=1;i<=n;++i)
    if (f[a[i]]==1) ++ans;
  printf("%d\n",ans);
}
 
int main(){
  int T=0;
  scanf("%d",&T);
  while (T--){
    start();
    into();
    work();
    outo();
  }
  return 0;
}



T3:
题目:luogu5021.
题目大意:给定一棵无根树,现在让你找 m m m条没有边重叠的路径,使得这些路径中边权和最小的路径边权和最大.
1 ≤ n ≤ 5 ∗ 1 0 4 1\leq n\leq 5*10^4 1n5104.

考场上想到二分,但因为这是最后一题,认为自己是做不出来的就写了55分的部分分,现在想想这道题就是道水题啊.

首先二分最小的路径和 m i d mid mid,将问题转化为判定是否有 m m m条不重叠的路径每条的边权和都大于或等于 m i d mid mid.

然后考虑菊花图的部分分的做法,我们可以发现就是将所有边按照边权排序,最大值与最小值配对就可以了,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

再来考虑分叉不超过 3 3 3的做法,我们肯定能将树转化成一棵二叉树.考虑在这棵二叉树上解决这个问题,考虑记录一个 f [ i ] f[i] f[i]表示点i字数中剩下的(也就是子树中所有满足条件的链抽走后)以 i i i为一端的最长链.考虑计算每一棵以k为根的子树中最多可以抽走的链 c n t [ k ] cnt[k] cnt[k],发现 c n t [ k ] cnt[k] cnt[k] k k k的所有儿子的 c n t cnt cnt值之和,加上 0 0 0~ 2 2 2条链,加上的链的数量是很好求的, f [ k ] f[k] f[k]也可以很容易的求出.

对于一般情况,考虑将上面两个算法结合起来.在每一棵子树计算最多可以抽走的链时,按照菊花图的方式求出,这样你就能够得到80分的高分了,剩下20分WA了.

为什么会WA呢?考虑对于若每一个节点 k k k的所有儿子的值计算出来都是正确的,那么求 c n t [ k ] cnt[k] cnt[k]的时候按照菊花图的方式是正确的,但是 f [ k ] f[k] f[k]的计算就会出错了.因为我们发现, c n t [ k ] cnt[k] cnt[k]按照菊花图的方式求出一定最优,但是当 c n t [ k ] cnt[k] cnt[k]最优时 f [ k ] f[k] f[k]会有多种情况,而菊花图的处理方式是会使 f [ k ] f[k] f[k]最差而不是最好的方式.

那么如何处理这个问题呢?考虑可以二分,也可以考虑直接使用multiset来解决这个问题.用multiset的时候,考虑从大往小枚举,每次抽走最小的与当前枚举到的值满足条件的值,就可以保证是最好的方式了.时间复杂度 O ( n log ⁡ n log ⁡ ∑ l i ) O(n\log n\log\sum l_i) O(nlognlogli).

代码如下:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=50000;
 
struct side{
  int y,next,v;
}e[N*2+9];
int lin[N+9],n,m,top,ans;
 
void ins(int x,int y,int v){
  e[++top].y=y;e[top].v=v;
  e[top].next=lin[x];
  lin[x]=top;
}
 
int cnt[N+9],f[N+9];
int tmp[N+9],tt,u[N+9];
 
multiset<int> s;
 
void calc(int k,int fa,int mid){
  s.clear();
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^fa) s.insert(e[i].v+f[e[i].y]);
  multiset<int>::iterator l,r;
  multiset<int>::reverse_iterator ii;
  while (!s.empty()){
    ii=s.rbegin();
    if (*ii>=mid) s.erase(s.find(*ii)),++cnt[k];
    else break;
  }
  while (!s.empty()){
    l=s.begin();
    s.erase(l);
    r=s.lower_bound(mid-*l);
    if (r==s.end()) f[k]=*l;
    else s.erase(r),++cnt[k];
  }
}
 
void dfs(int k,int fa,int mid){
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^fa){
      dfs(e[i].y,k,mid);
      cnt[k]+=cnt[e[i].y];
    }
  calc(k,fa,mid);
}
 
bool check(int mid){
  for (int i=1;i<=n;++i)
    cnt[i]=f[i]=0;
  dfs(1,0,mid);
  return cnt[1]>=m;
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  int x,y,v;
  for (int i=1;i<n;++i){
    scanf("%d%d%d",&x,&y,&v);
    ins(x,y,v);ins(y,x,v);
  }
}
 
Abigail work(){
  for (int i=29;i>=0;--i)
    if (check(ans+(1<<i))) ans+=1<<i;
}
 
Abigail outo(){
  printf("%d\n",ans);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}



day2:

T1:
题目:luogu5022.
题目大意:给定一张图有 n n n个点 m m m条边,求它字典序最小的dfs序.
1 ≤ n ≤ 5000 , n − 1 ≤ m ≤ n 1\leq n\leq 5000,n-1\leq m\leq n 1n5000,n1mn.

考场直接每个点建一个堆以为常数小机子快就能过…然后只拿了88分…

对于树的数据,直接在每一个点建一个堆,在深度优先遍历的时候先遍历编号最小的点即可,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

对于基环树,在树的基础上,暴力枚举删除一条边,将所有得到的字典序中最小的字典序当做答案,时间复杂度 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn),然后你就可以拿到88分的高分.

然后考场下来的时候,听到zjc说 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)会被卡,直接把每一个点的出边排个序就可以做到 O ( n 2 ) O(n^2) O(n2)了,但是我写出来的代码还是优秀的88分,看起来是被卡常了…

我们再考虑一个更优秀的做法,对原算法进行常数优化,考虑使用邻接表来储存,直接排完序逆向加边.

AC代码如下:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const int N=5000;
 
struct side{
  int y,next;
}e[N*2+9];
int lin[N+9],top,n,m;
 
void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}
 
struct sside{
  int x,y;
  bool operator < (const sside &p)const{return x<p.x||x==p.x&&y<p.y;}
}s[N*2+9];
int ts;
int ord[N+9],tmp[N+9],tt;
int nx,ny,u[N+9];
int xx[N+9],yy[N+9];
 
void dfs(int k){
  u[k]=1;
  tmp[++tt]=k;
  for (int i=lin[k];i;i=e[i].next)
    if (!u[e[i].y]&&(e[i].y^nx||k^ny)&&(e[i].y^ny||k^nx)) dfs(e[i].y);
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
  int x,y;
  for (int i=1;i<=m;++i){
    scanf("%d%d",&x,&y);
    xx[i]=x;yy[i]=y;
    s[++ts].x=x;s[ts].y=y;
    s[++ts].y=x;s[ts].x=y;
  }
}
 
Abigail work(){
  sort(s+1,s+1+ts);
  for (int i=ts;i>=1;--i)
    ins(s[i].x,s[i].y);
  for (int i=1;i<=n;++i) ord[i]=n;
  if (n==m+1){
    dfs(1);
    for (int i=1;i<=n;++i)
      ord[i]=tmp[i];
    return;
  }
  int flag;
  for (int i=1;i<=m;++i){
    tt=0;
    nx=xx[i],ny=yy[i];
    for (int i=1;i<=n;++i) u[i]=0;
    dfs(1);
    if (tt<n) continue;
    flag=0;
    for (int i=1;i<=n;++i)
      if (tmp[i]^ord[i]){
      	flag=tmp[i]<ord[i];
      	break;
      }
    if (!flag) continue;
    for (int i=1;i<=n;++i)
      ord[i]=tmp[i];
  }
}
 
Abigail outo(){
  for (int i=1;i<n;++i)
    printf("%d ",ord[i]);
  printf("%d\n",ord[n]);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}

T2:
题目:luogu5023.
题目大意:大概就是让你求有多少个 n n n m m m列的 01 01 01矩阵,使得对于任意两条路径 p 1 p1 p1 p 2 p2 p2,若 w ( p 1 ) > w ( p 2 ) w(p1)>w(p2) w(p1)>w(p2),则 s ( p 1 ) ≤ s ( p 2 ) s(p1)\leq s(p2) s(p1)s(p2).其中函数 w ( p ) w(p) w(p)表示这个路径的行走方向依次排列, 1 1 1为向右, 0 0 0为向下; s ( p ) s(p) s(p)表示路径行走到的格子内的数依次排列.
1 ≤ n ≤ 8 , 1 ≤ m ≤ 1 0 6 1\leq n\leq 8,1\leq m\leq 10^6 1n8,1m106.

这道题其实打表打完之后瞪眼法找规律就可以了(虽然我考场表都没打,以为是个状压).

下面给出规律:
我们设 ( a , b ) (a,b) (a,b)表示 a = n , b = m a=n,b=m a=n,b=m时的答案.
( 1 , m ) = 2 m (1,m)=2^m (1,m)=2m
( n , m ) = ( m , n ) (n,m)=(m,n) (n,m)=(m,n)
( 2 , 2 ) = 12 , ( 3 , 3 ) = 112 , ( 4 , 4 ) = 912 (2,2)=12,(3,3)=112,(4,4)=912 (2,2)=12,(3,3)=112,(4,4)=912
m = n + 1 m=n+1 m=n+1 n ≤ 3 n\leq 3 n3,则 ( n , m ) = 3 ( n , m − 1 ) (n,m)=3(n,m-1) (n,m)=3(n,m1).若 m = n + 1 m=n+1 m=n+1 n > 3 n>3 n>3,则 ( n , m ) = 3 ( n , m − 1 ) − 3 n (n,m)=3(n,m-1)-3^n (n,m)=3(n,m1)3n.
m > n + 1 m>n+1 m>n+1,则 ( n , m ) = 3 ( n , m − 1 ) (n,m)=3(n,m-1) (n,m)=3(n,m1).

那么代码如下:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
const LL mod=1000000007;
 
int n,m;
LL ans;
 
LL power(LL a,int k){
  LL s=1;
  for (;k;k>>=1,a=a*a%mod)
    if (k&1) s=s*a%mod;
  return s;
}
 
Abigail into(){
  scanf("%d%d",&n,&m);
}
 
Abigail work(){
  if (n>m) swap(n,m);
  switch (n){
    case 1:
      ans=power(2,m);
      return;
    case 2:
      ans=12;
      break;
    case 3:
      ans=112;
      break;
    case 4:
      ans=912;
      break;
    default:
      ans=912;
      for (int i=5;i<=n;++i)
        ans=ans*8-(5<<i);
  }
  if (m>=n+1) ans=n>3?ans*3-(3<<n):ans*3;
  if (m>n+1) ans=ans*power(3,m-n-1)%mod;
}
 
Abigail outo(){
  printf("%lld\n",ans);
}
 
int main(){
  into();
  work();
  outo();
  return 0;
}



T3:
题目:luogu5024.

题目大意:给定一棵树,要求对树染色,且一条边上的两点必须有一点染色,现在给定 m m m个询问,每个询问强制两点的染色状态,求最小染色点权和.

这道题让我在考场以为可以用线段树来维护链的情况,然后直接用树链剖分维护树上的情况就行了,然而这样并不好处理.

看完题解后突然感觉很容易用倍增预处理来处理这些问题啊.

首先我们预处理出一个f数组,其中 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示在以点i为根的子树不选 / / /选根时的最小点权和.

然后考虑确定了两个点 u u u v v v的染色状态时,会改变的链其实是 u u u L C A LCA LCA v v v L C A LCA LCA L C A LCA LCA到根这三条链.

那么我们先不考虑这三条链之外的树节点,我们发现只要用倍增预处理 f [ u ] [ i ] [ 0 / 1 ] [ 0 / 1 ] f[u][i][0/1][0/1] f[u][i][0/1][0/1]表示点 u u u u u u的第 2 i 2^i 2i级祖先时, u u u的状态为不选/选, u u u 2 i 2^i 2i级祖先的状态为选 / / /不选,且不包括子树 u u u u u u的点权也不包括)时,这条链的最小染色点权和,预处理时空复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

然后,我们发现还有其它的树节点的影响,但是这样并不影响,我们只需要把状态 f [ u ] [ i ] [ 0 / 1 ] [ 0 / 1 ] f[u][i][0/1][0/1] f[u][i][0/1][0/1]表示的链上其它连接的子树的值顺便加上就可以了,时空复杂度依然是 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码中 f f f数组的预处理什么的,可能细节比较多,注意一点就可以了.

代码如下:

#include
  using namespace std;
 
#define Abigail inline void
typedef long long LL;
 
int ri(){
  int x=0;
  char c;
  for (c=getchar();c<'0'||c>'9';c=getchar());
  for (;c<='9'&&c>='0';c=getchar()) x=x*10+c-'0';
  return x;
}
 
const int N=100000,C=19;
const LL INF=(1LL<<50)-1LL;
 
int n,m;
LL p[N+9];
 
struct side{
  int y,next;
}e[N*2+9];
int lin[N+9],top;
 
void ins(int x,int y){
  e[++top].y=y;
  e[top].next=lin[x];
  lin[x]=top;
}
 
LL dp[N+9][2];
 
void dfs_dp(int k,int fa){
  dp[k][0]=0;dp[k][1]=p[k];
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^fa){
      dfs_dp(e[i].y,k);
      dp[k][0]+=dp[e[i].y][1];
      dp[k][1]+=min(dp[e[i].y][0],dp[e[i].y][1]);
    }
}
 
int gr[N+9][C+1],deep[N+9];
LL f[N+9][C+1][2][2];
 
void dfs_lca(int k,int fa){
  deep[k]=deep[fa]+1;
  gr[k][0]=fa;
  f[k][0][0][0]=INF;
  f[k][0][0][1]=f[k][0][1][1]=dp[fa][1]-min(dp[k][0],dp[k][1]);
  f[k][0][1][0]=dp[fa][0]-dp[k][1];
  int F;
  for (int i=1;i<=C;++i){
    F=gr[k][i-1];
    gr[k][i]=gr[F][i-1];
    f[k][i][0][0]=min(f[k][i-1][0][0]+f[F][i-1][0][0],f[k][i-1][0][1]+f[F][i-1][1][0]);
    f[k][i][0][1]=min(f[k][i-1][0][0]+f[F][i-1][0][1],f[k][i-1][0][1]+f[F][i-1][1][1]);
    f[k][i][1][0]=min(f[k][i-1][1][0]+f[F][i-1][0][0],f[k][i-1][1][1]+f[F][i-1][1][0]);
    f[k][i][1][1]=min(f[k][i-1][1][0]+f[F][i-1][0][1],f[k][i-1][1][1]+f[F][i-1][1][1]);
  }
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^fa) dfs_lca(e[i].y,k);
}
 
LL get_ans(int u,int a,int v,int b){
  if (deep[u]<deep[v]) swap(u,v),swap(a,b);
  LL u0,u1,v0,v1,l0,l1,t0,t1,ans;
  int l;
  u0=u1=v0=v1=l0=l1=INF;
  a?u1=dp[u][1]:u0=dp[u][0];b?v1=dp[v][1]:v0=dp[v][0];
  for (int i=C;i>=0;--i)
    if (deep[gr[u][i]]>=deep[v]){
      t0=u0;t1=u1;
      u0=min(t0+f[u][i][0][0],t1+f[u][i][1][0]);
      u1=min(t0+f[u][i][0][1],t1+f[u][i][1][1]);
      u=gr[u][i];
    }
  if (u^v){
    for (int i=C;i>=0;--i)
      if (gr[u][i]^gr[v][i]){
        t0=u0;t1=u1;
        u0=min(t0+f[u][i][0][0],t1+f[u][i][1][0]);
        u1=min(t0+f[u][i][0][1],t1+f[u][i][1][1]);
        t0=v0;t1=v1;
        v0=min(t0+f[v][i][0][0],t1+f[v][i][1][0]);
        v1=min(t0+f[v][i][0][1],t1+f[v][i][1][1]);
        u=gr[u][i];
        v=gr[v][i];
      }
    l=gr[u][0];
    l0=u1+v1+dp[l][0]-dp[u][1]-dp[v][1];
    l1=min(u0,u1)+min(v0,v1)+dp[l][1]-min(dp[u][0],dp[u][1])-min(dp[v][0],dp[v][1]);
  }else{
    l=u;
    b?l1=u1:l0=u0;
  }
  if (l==1){
    ans=min(l0,l1);
    return ans<INF?ans:-1;
  }
  for (int i=C;i>=0;--i)
    if (gr[l][i]>1){
      t0=l0;t1=l1;
      l0=min(t0+f[l][i][0][0],t1+f[l][i][1][0]);
      l1=min(t0+f[l][i][0][1],t1+f[l][i][1][1]);
      l=gr[l][i];
    }
  ans=min(dp[1][0]-dp[l][1]+l1,dp[1][1]-min(dp[l][0],dp[l][1])+min(l1,l0));
  return ans<INF?ans:-1LL; 
}
 
Abigail into(){
  n=ri();m=ri();
  int x=ri(),y;
  for (int i=1;i<=n;++i) p[i]=LL(ri());
  for (int i=1;i<n;++i){
    x=ri();y=ri();
    ins(x,y);ins(y,x);
  }
}
 
Abigail work(){
  dfs_dp(1,0);
  dfs_lca(1,0);
}
 
Abigail getans(){
  int u,a,v,b;
  for (int i=1;i<=m;++i){
    u=ri();a=ri();v=ri();b=ri();
    printf("%lld\n",get_ans(u,a,v,b));
  }
} 
 
int main(){
  into();
  work();
  getans();
  return 0;
}

你可能感兴趣的:(NOIP2018提高组题解)