NOIP2018TG题解

应该不会被禁赛吧qwq

Day1

T1 铺设道路

€€£:我 抄 我 自 己

我考场上写的是一个类似于差分的做法,就是开个数组表示某个位置是否贡献答案,从小到大枚举高度,那么一个位置能贡献答案当且仅当这个位置没有被当前高度盖过去,并且上一个位置被当前高度盖过去了,如果某一时刻某个位置被当前高度盖过去了,那么如果这个位置有贡献,就要把这个位置贡献的扣掉,同时看一下能否使后一个位置产生贡献

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#define il inline
#define db double
#define LL long long

using namespace std;
const int N=100000+10;
il LL rd()
{
  LL x=0,w=1;char ch=0;
  while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
  return x*w;
}
struct node
{
  int x,d;
  node(){}
  node(int nx,int nd){x=nx,d=nd;}
  bool operator < (const node &bb) const {return d<bb.d;}
}b[N];
int n,a[N];
LL ans;
int s[N],nw;

int main()
{
  //freopen("road.in","r",stdin);
  //freopen("road.out","w",stdout);
  n=rd();
  for(int i=1;i<=n;i++) a[i]=rd(),b[i]=node(i,a[i]);
  a[0]=a[n+1]=-1,b[0]=node(0,-1);
  sort(b,b+n+1);
  b[n+1].d=2333333;
  s[1]=nw=1;
  for(int h=0,i=1;h<=10000;h++)
    {
      if(!nw) break;
      if(h) ans+=nw;
      while(b[i].d==h)
		{
		  int x=b[i].x;
		  if(s[x]) --s[x],--nw;
		  if(a[x+1]>h) ++s[x+1],++nw; 
		  ++i;
		  //printf("%d %d\n",h,nw);
		} 
    }
  printf("%lld\n",ans);
  return 0;
}

T2 货币系统

这题的答案就是所有不能被其他数加起来表示的数的个数.原因是首先最小的那个数要选,然后这个数的倍数就可以删掉不选了.接下来没被删掉的数中最小的数也要选,同样可以把其他能被加起来表示的数删掉,,,以此类推.所以这题就是个完全背包,只需要知道给出的数中的每个数是否只有一种表示方法就好了

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#define il inline
#define db double
#define LL long long

using namespace std;
const int N=100+10,M=25000+10;
il LL rd()
{
  LL x=0,w=1;char ch=0;
  while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
  return x*w;
}
int n,m,a[N],f[M];

int main()
{
  //freopen("money.in","r",stdin);
  //freopen("money.out","w",stdout);
  int T=rd();
  while(T--)
    {
      memset(f,0,sizeof(f));
      n=rd(),m=0;
      for(int i=1;i<=n;i++) m=max(m,a[i]=rd());
      f[0]=1;
      for(int i=1;i<=m;i++)
		{
		  for(int j=1;j<=n;j++)
			if(i-a[j]>=0) f[i]+=f[i-a[j]];
		  f[i]=min(f[i],2);
		}
      int ans=0;
      for(int i=1;i<=n;i++) ans+=(f[a[i]]==1);
      printf("%d\n",ans);
    }
  return 0;
}

T3 赛道修建

首先如果某个答案可行,那么比这个答案更小的也可行,所以可以二分答案.check的话就需要找出\(m\)条长度\(\ge mid\)的链.可以对整棵树进行Dfs.每个点的儿子都有往下的链,我们就把这些链取出来统计一下,方法是用当前最短的链匹配加起来长度\(\ge mid\)的,同时尽量小的链,然后计入链的个数,或者是本来长度就合法的可以直接计入链的个数,最后没匹配上的不能计入答案的就放到父亲处匹配

实现可以sort之后使用二分+并查集,我比较懒就用了\(multiset\) 依旧能艹过去

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define il inline
#define db double
#define LL long long
#define re register

using namespace std;
const int N=50000+10;
il LL rd()
{
  LL x=0,w=1;char ch=0;
  while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
  return x*w;#
}
int to[N<<1],nt[N<<1],w[N<<1],hd[N],tot=1;
il void add(int x,int y,int z)
{
  ++tot,to[tot]=y,nt[tot]=hd[x],w[tot]=z,hd[x]=tot;
  ++tot,to[tot]=x,nt[tot]=hd[y],w[tot]=z,hd[y]=tot;
}
int n,m,an,mid;
int f[N],tp;
multiset<int> s;
multiset<int>::iterator it,rbq;
void dfs(int x,int ffa)
{
  f[x]=0;
  for(int i=hd[x];i;i=nt[i])
    {
      int y=to[i];
      if(y==ffa) continue;
      dfs(y,x);
    }
  s.clear(),tp=0;
  for(int i=hd[x];i;i=nt[i])
    {
      int y=to[i];
      if(y==ffa) continue;
      s.insert(f[y]+w[i]);
      ++tp;
    }
  it=s.begin();
  for(int i=2;i<=tp;i++) ++it;
  while(tp&&(int)(*it)>=mid)
    {
      rbq=it;
      ++an;
      --it,--tp;
      s.erase(rbq);
    }
  int ma=*it;
  it=s.begin();
  while(tp>1&&(int)(*it)+ma<mid) f[x]=max(f[x],(int)(*it)),--tp,rbq=it,++it,s.erase(rbq);
  //printf("--%d\n",tp);
  //it=s.begin();
  while(tp>1)
    {
      rbq=s.lower_bound(mid-(*it));
      if(rbq==s.end()) f[x]=max(f[x],(int)(*it)),--tp,rbq=it,++it,s.erase(rbq);
      else
		{
		  ++an;
		  if(rbq==it) ++rbq;
		  --tp,s.erase(rbq);
		  //printf("%d\n",(int)(*rbq));
		  --tp,rbq=it,++it,s.erase(rbq);
		  //printf("qqc\n");
		}
    }
  if(tp) f[x]=max(f[x],*s.begin());
  //printf("%d %d\n",x,f[x]);
}
il bool check()
{
  an=0;
  dfs(1,0);
  return an>=m;
}
int zz=0,de[N];
void dd(int x,int ffa)
{
  for(int i=hd[x];i;i=nt[i])
    {
      int y=to[i];
      if(y==ffa) continue;
      de[y]=de[x]+w[i],dd(y,x);
    }
  if(de[x]>de[zz]) zz=x;
}

int main()
{
  //freopen("track.in","r",stdin);
  //freopen("track.out","w",stdout);
  n=rd(),m=rd();
  int l=0,r=0;
  for(re int i=1;i<n;++i)
    {
      int x=rd(),y=rd(),z=rd();
      add(x,y,z);
    }
  de[0]=-1,de[1]=0,dd(1,0);
  de[r=zz]=0,zz=0,dd(r,0),r=de[zz];
  int ans=0;
  while(l<=r)
    {
      mid=(l+r)>>1;
      if(check()) ans=mid,l=mid+1;
      else r=mid-1;
    }
  printf("%d\n",ans);
  return 0;
}

Day2

T1 旅行

首先如果是棵树,那么从1开始,贪心从小往大遍历所有儿子,答案就是按从前往后遍历到的点的序列.如果是基环树,那么枚举环上的边,断掉,然后当成树做,最后取字典序最小的答案就好了

结果做这个##题花我1.5h qwq

不过这题有线性做法,自己可以去找

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#define il inline
#define LL long long
#define db double
#define re register

using namespace std;
const int N=5000+10;
il LL rd()
{
  LL x=0,w=1;char ch=0;
  while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
  return x*w;
}
int to[N<<1],nt[N<<1],hd[N],tot=1;
il void add(int x,int y)
{
  ++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot;
  ++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot;
}
struct edgee
{
  int x,y;
  edgee(){}
  edgee(int nx,int ny){x=nx,y=ny;if(x>y) swap(x,y);}
  bool operator < (const edgee &bb) const {return x!=bb.x?x>bb.x:y>bb.y;}
}e[N];
int n,m,an[N],ans[N],tt;
int st[N],s2[N],tp,se[N],te,zhushu;
bool ban[N<<1],v[N];
void dfs(int x)
{
  v[x]=true,st[++tp]=x;
  for(int i=hd[x];i;i=nt[i])
	{
	  if(ban[i]) continue;
	  s2[tp]=i;
	  ban[i]=ban[i^1]=true;
	  if(v[to[i]])
		{
		  int ttp=tp;
		  while(ttp)
			{
			  int y=st[ttp],ii=s2[ttp--];
			  se[++te]=ii;
			  if(to[i]==y) break;
			}
		  zhushu=i;
		  continue;
		}
	  else dfs(to[i]);
	  ban[i]=ban[i^1]=false;
	}
  st[tp]=s2[tp]=0,--tp;
}
void dd(int x)
{
  an[++tt]=x;
  for(int i=hd[x];i;i=nt[i])
	{
	  if(ban[i]) continue;
	  ban[i]=ban[i^1]=true;
	  dd(to[i]);
	  ban[i]=ban[i^1]=false;
	}
}

int main()
{
  //freopen("travel.in","r",stdin);
  //freopen("travel.out","w",stdout);
  n=rd(),m=rd();
  for(int i=1;i<=m;i++) e[i]=edgee(rd(),rd());
  sort(e+1,e+m+1);
  for(int i=1;i<=m;i++) add(e[i].x,e[i].y);
  if(m==n-1)
	{
	  dd(1);
	  for(int i=1;i<=n;i++) printf("%d ",an[i]);
	}
  else
	{
	  dfs(1);
	  ban[zhushu]=ban[zhushu^1]=false;
	  memset(ans,63,sizeof(ans));
	  while(te)
		{
		  int i=se[te--];
		  ban[i]=ban[i^1]=true;
		  tt=0,dd(1);
		  for(int j=1;j<=n;j++)
			{
			  if(an[j]>ans[j]) break;
			  else if(an[j]<ans[j])
				{
				  for(int k=1;k<=n;k++) ans[k]=an[k];
				  break;
				}
			}
		  ban[i]=ban[i^1]=false;
		}
	  for(int i=1;i<=n;i++) printf("%d ",ans[i]);
	}
  return 0;
}

T2 填数游戏

骗分过样例,暴力出奇迹

下面是考场10'代码

......
int n,m,ans;
int a[5][5],tt;
string s[20];
void dd(int x,int y,string nw)
{
  if(x==n&&y==m) {s[++tt]=nw;return;}
  if(x!=n) dd(x+1,y,nw+(char)(48+a[x+1][y]));
}
il bool check()
{
  tt=0;
  //没写完好气哦
}
void dfs(int x,int y)
{
  if(x>n) {ans+=check();return;}
  a[x][y]=0,dfs(y==m?x+1:x,y==m?1:y+1);
  a[x][y]=1,dfs(y==m?x+1:x,y==m?1:y+1);
}

int main()
{
  //freopen("game.in","r",stdin);
  //freopen("game.out","w",stdout);
  n=rd(),m=rd();
  /*......*/
  if(n==2&&m==2) printf("12");
  if(n==3&&m==3) printf("112");
  return 0;
}

这题的打表题解自己去网上找qwq

下面是靠谱的数学做法
咕咕咕咕咕咕咕

T3 保卫王国

不会打暴力,身败名裂

做法之一是动态dp,这里不做过多赘述

还有可以直接倍增做,也就是需要知道某个点子树内的dp值,子树以外的dp值(也可以说是以这个点为整棵树的根的dp值),以及一个点的\(2^k\)次方级祖先到这个点父亲所构成的链以及链旁边的部分的dp值,每次询问分类讨论一下就好了 就4.7kb,太好写了

有两种情况,如果某个点是lca,那么答案由下面那个点的子树+两点之间的链(这里的链均不包含端点)+lca扣除下面那个点所在子树的贡献的dp值构成;否则就是由两个点的子树+两点分别到lca之间的链+lca扣除两个点所在子树的贡献的dp值构成

注意无解的判断,两个询问点相邻,且都为0

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#include<map>
#define il inline
#define LL long long
#define db double
#define re register

using namespace std;
const int N=100000+10;
il LL rd()
{
  LL x=0,w=1;char ch=0;
  while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
  return x*w;
}
int to[N<<1],nt[N<<1],hd[N],tot=1;
il void add(int x,int y)
{
  ++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot;
  ++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot;
}
int n,nn,m,p[N],fa[N][20],sz[N],de[N],son[N],top[N];
LL f[N][2],f0[N],f1[N],g[N][2],h[N][20][2][2],a1[2][2],a2[2][2],ff[2];
void dfs1(int x)
{
  sz[x]=1;
  for(int i=1;i<=nn;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
  for(int i=hd[x];i;i=nt[i])
	{
	  int y=to[i];
	  if(y==fa[x][0]) continue;
	  fa[y][0]=x,de[y]=de[x]+1,dfs1(y),sz[x]+=sz[y];
	  if(sz[y]>sz[son[x]]) son[x]=y;
	  f[x][0]+=f[y][1],f[x][1]+=min(f[y][0],f[y][1]);
	}
  f[x][1]+=p[x];
  for(int i=hd[x];i;i=nt[i])
	{
	  int y=to[i];
	  if(y==fa[x][0]) continue;
	  f0[y]=f[x][0]-f[y][1],f1[y]=f[x][1]-min(f[y][0],f[y][1]);
	}
}
void dfs2(int x,int ntp)
{
  top[x]=ntp;
  if(x>1) g[x][0]=g[fa[x][0]][1]+f1[x],g[x][1]=min(g[fa[x][0]][0]+f0[x],g[fa[x][0]][1]+f1[x]);
  h[x][0][0][0]=f[fa[x][0]][0]-f[x][1],h[x][0][1][1]=f[fa[x][0]][1]-min(f[x][0],f[x][1]),h[x][0][0][1]=h[x][0][1][0]=1ll<<50;
  for(int i=1;i<=nn;i++)
	{
	  for(int j=0;j<=1;j++)
		for(int k=0;k<=1;k++)
		  {
			h[x][i][j][k]=1ll<<50;
			for(int l=0;l<=1;l++)
			  for(int o=0;o<=1;o++)
				if(o|l) h[x][i][j][k]=min(h[x][i][j][k],h[x][i-1][j][l]+h[fa[x][i-1]][i-1][o][k]);
		  }
	}
  if(son[x])
	{
	  dfs2(son[x],ntp);
	}
  for(int i=hd[x];i;i=nt[i])
	{
	  int y=to[i];
	  if(y==fa[x][0]||y==son[x]) continue;
	  dfs2(y,y);
	}
}
il int getlca(int x,int y)
{
  while(top[x]!=top[y])
	{
	  if(de[top[x]]<de[top[y]]) swap(x,y);
	  x=fa[top[x]][0];
	}
  return de[x]<de[y]?x:y;
}

int main()
{
  //freopen("defense.in","r",stdin);
  //freopen("defense.out","w",stdout);
  n=rd(),m=rd();
  nn=log2(n);
  char cc[3];
  scanf("%s",cc);
  for(int i=1;i<=n;i++) p[i]=rd();
  for(int i=1;i<n;i++) add(rd(),rd());
  dfs1(1),dfs2(1,1);
  while(m--)
	{
	  a1[0][0]=a1[0][1]=a1[1][0]=a1[1][1]=0;
	  a2[0][0]=a2[0][1]=a2[1][0]=a2[1][1]=0;
	  int x=rd(),a=rd(),y=rd(),b=rd(),lca=getlca(x,y);
	  if(y==lca) swap(x,y),swap(a,b);
	  if(x==lca)
		{
		  if(fa[y][0]==x&&!(a|b)) {puts("-1");continue;}
		  int yy=y;
		  for(int i=nn;i>=0;i--)
			if(de[fa[yy][i]]>=de[x]+1)
			  {
				if(a1[0][0])
				  {
					LL nw[2][2];
					nw[0][0]=nw[0][1]=nw[1][0]=nw[1][1]=1ll<<50;
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						for(int l=0;l<=1;l++)
						  for(int o=0;o<=1;o++)
							if(l|o) nw[j][k]=min(nw[j][k],a1[j][l]+h[yy][i][o][k]);
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						a1[j][k]=nw[j][k];
				  }
				else
				  {
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						a1[j][k]=h[yy][i][j][k];
				  }
				yy=fa[yy][i];
			  }
		  ff[0]=f[x][0]+g[x][0]-f[yy][1],ff[1]=f[x][1]+g[x][1]-min(f[yy][0],f[yy][1]);
		  LL ans=1ll<<50;
		  for(int j=0;j<=1;j++)
			for(int k=0;k<=1;k++)
			  if((b|j)&(a|k)) ans=min(ans,ff[a]+f[y][b]+a1[j][k]);
		  printf("%lld\n",ans<(1ll<<50)?ans:-1);
		}
	  else
		{
		  int xx=x,yy=y;
		  for(int i=nn;i>=0;i--)
			if(de[fa[xx][i]]>=de[lca]+1)
			  {
				if(a1[0][0])
				  {
					LL nw[2][2];
					nw[0][0]=nw[0][1]=nw[1][0]=nw[1][1]=1ll<<50;
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						for(int l=0;l<=1;l++)
						  for(int o=0;o<=1;o++)
							if(l|o) nw[j][k]=min(nw[j][k],a1[j][l]+h[xx][i][o][k]);
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						a1[j][k]=nw[j][k];
				  }
				else
				  {
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						a1[j][k]=h[xx][i][j][k];
				  }
				xx=fa[xx][i];
			  }
		  for(int i=nn;i>=0;i--)
			if(de[fa[yy][i]]>=de[lca]+1)
			  {
				if(a2[0][0])
				  {
					LL nw[2][2];
					nw[0][0]=nw[0][1]=nw[1][0]=nw[1][1]=1ll<<50;
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						for(int l=0;l<=1;l++)
						  for(int o=0;o<=1;o++)
							if(l|o) nw[j][k]=min(nw[j][k],a2[j][l]+h[yy][i][o][k]);
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						a2[j][k]=nw[j][k];
				  }
				else
				  {
					for(int j=0;j<=1;j++)
					  for(int k=0;k<=1;k++)
						a2[j][k]=h[yy][i][j][k];
				  }
				yy=fa[yy][i];
			  }
		  ff[0]=f[lca][0]+g[lca][0]-f[xx][1]-f[yy][1],ff[1]=f[lca][1]+g[lca][1]-min(f[xx][0],f[xx][1])-min(f[yy][0],f[yy][1]);
		  LL ans=1ll<<50;
		  for(int j=0;j<=1;j++)
			for(int k=0;k<=1;k++)
			  for(int l=0;l<=1;l++)
				for(int o=0;o<=1;o++)
				  for(int p=0;p<=1;p++)
					if((x!=xx?(a|j)&&(l|p):(a|p))&(y!=yy?(b|k)&&(o|p):(b|p))) ans=min(ans,f[x][a]+f[y][b]+a1[j][l]+a2[k][o]+ff[p]);
		  printf("%lld\n",ans<(1ll<<50)?ans:-1);
		}
	}
  return 0;
}

posted @ 2018-11-23 19:59  ✡smy✡  阅读(194)  评论(0编辑  收藏  举报