NOIP2018TG题解

时间:2021-12-28 18:39:34

应该不会被禁赛吧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;
}