NOIP难题汇总
一下是本蒟蒻认为近几年来NOIP提高组比较难的题目,神犇觉得太水请手动关闭:-)
TOP10 NOIP 2010 关押罪犯
主要考点:并查集
开两倍并查集,f[i],f[j+n]表示i和j在不同的监狱。利
用a与b不在一个监狱,b与c不在一个监狱,则a与c必定
在一个监狱这一性质合并集合。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<set> 2 #include<map> 3 #include<queue> 4 #include<stack> 5 #include<ctime> 6 #include<cmath> 7 #include<string> 8 #include<vector> 9 #include<cstdio> 10 #include<cstdlib> 11 #include<cstring> 12 #include<iostream> 13 #include<algorithm> 14 using namespace std; 15 struct data{ 16 int a,b,w; 17 }e[100010]; 18 int k[40010]; 19 int cmp(const data &A,const data &B) 20 { 21 return A.w>B.w; 22 } 23 int find(int x) 24 { 25 if(k[x]==x) return x; 26 else {k[x]=find(k[x]);return k[x];} 27 } 28 int main() 29 { 30 int n,m; 31 scanf("%d%d",&n,&m); 32 for(int i=1;i<=m;i++) 33 scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].w); 34 for(int i=1;i<=2*n;i++) 35 k[i]=i; 36 sort(e+1,e+m+1,cmp); 37 for(int i=1;i<=m;i++) 38 { 39 int p=find(e[i].a),q=find(e[i].b); 40 if(p==q) {printf("%d",e[i].w);return 0;} 41 k[p]=find(e[i].b+n),k[q]=find(e[i].a+n); 42 } 43 printf("0"); 44 return 0; 45 }
TOP9 NOIP 2015 运输计划
主要考点:二分,树上差分,lca.
二分一个答案now.
那些距离小于now的点对就不用管了.
先统计那些距离大于now的点的个数和最大的距离.
然后现在要将一条边的边权赋为0,那么就肯定要找到一条所有未排除点对都经过的边,且这条边的边权最大.
假设已经找到了这条边,那么就只用判断减去这条边的边权之后最大的点对距离是否<=now
现在问题是怎么找这条边.
直接用线段树区间加法应该是会TLE的,考虑只用最后一次询问操作,可以使用树上差分.
注意递归版的树上差分常数较大,可以记一个BFS序写成非递归版.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 #define maxn 300010 15 #define RG register 16 using namespace std; 17 struct data{ 18 int nex,to,w; 19 }e[maxn*2]; 20 struct note{ 21 int x,y,lca,dis; 22 }q[maxn]; 23 int head[maxn],edge=0,n,m; 24 int que[maxn],rk[maxn],size[maxn],top[maxn],dad[maxn],son[maxn],deep[maxn],tmp[maxn],dis[maxn],up[maxn]; 25 bool vis[maxn]; 26 inline void add(int from,int to,int w){ 27 e[++edge].nex=head[from]; 28 e[edge].to=to; 29 e[edge].w=w; 30 head[from]=edge; 31 } 32 void dfs1(int x,int fa){ 33 size[x]=1; 34 for(int i=head[x];i;i=e[i].nex){ 35 RG int v=e[i].to; 36 if(v==fa) continue; 37 dad[v]=x;deep[v]=deep[x]+1; 38 dis[v]=dis[x]+e[i].w; 39 up[v]=e[i].w; 40 dfs1(v,x); 41 size[x]+=size[v]; 42 if(size[v]>size[son[x]])son[x]=v; 43 } 44 } 45 void dfs2(int x,int fa){ 46 if(son[x]) top[son[x]]=top[x],dfs2(son[x],x); 47 for(RG int i=head[x];i;i=e[i].nex){ 48 RG int v=e[i].to; 49 if(v==fa || v==son[x]) continue; 50 top[v]=v; 51 dfs2(v,x); 52 } 53 } 54 inline int lca(int x,int y){ 55 while(top[x]!=top[y]){ 56 if(deep[top[x]]>deep[top[y]]) x=dad[top[x]]; 57 else y=dad[top[y]]; 58 } 59 return deep[x]<deep[y]?x:y; 60 } 61 inline void bfs(){ 62 RG int h=1,t=1,tot=1; 63 que[h]=1,rk[tot]=1; 64 while(h<=t){ 65 RG int u=que[h++];vis[u]=1; 66 for(RG int i=head[u];i;i=e[i].nex){ 67 RG int v=e[i].to; 68 if(!vis[v])rk[++tot]=v,que[++t]=v; 69 } 70 } 71 } 72 inline bool check(int now){ 73 RG int tot=0,zd=0,zd1=0,sp=0; 74 for(RG int i=1;i<=m;i++) 75 if(q[i].dis>now) tmp[q[i].x]++,tmp[q[i].y]++,tmp[q[i].lca]-=2,tot++,zd=max(zd,q[i].dis-now); 76 if(tot==0) return 1; 77 for(RG int i=n;i>1;i--){ 78 tmp[dad[rk[i]]]+=tmp[rk[i]]; 79 if(tmp[rk[i]]>zd1) zd1=tmp[rk[i]],sp=rk[i]; 80 else if(tmp[rk[i]]==zd1 && up[rk[i]]>up[sp]) sp=rk[i]; 81 tmp[rk[i]]=0; 82 } 83 if(zd1<tot || zd-up[sp]>0)return 0; 84 return 1; 85 } 86 int main(){ 87 RG int l=0,r=0,x,y,z; 88 scanf("%d%d",&n,&m); 89 for(RG int i=1;i<n;i++) 90 scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z); 91 top[1]=deep[1]=dad[1]=1; 92 dfs1(1,0),dfs2(1,0),bfs(); 93 for(RG int i=1;i<=m;i++){ 94 scanf("%d%d",&q[i].x,&q[i].y); 95 q[i].lca=lca(q[i].x,q[i].y); 96 q[i].dis=dis[q[i].x]+dis[q[i].y]-2*dis[q[i].lca]; 97 r=max(r,q[i].dis); 98 } 99 while(l<=r){ 100 RG int mid=(l+r)>>1; 101 check(mid)?r=mid-1:l=mid+1; 102 } 103 printf("%d",l); 104 return 0; 105 }
TOP8 NOIP 2008 双栈排序
主要考点:二分图模型.
考虑把不能放在一个栈里面的两个点连边.
那么能否进行双栈排序,取决于这是否是一个二分图.
考虑满足什么条件的两个点不能在一个栈里面.
设i<j
若a[i]>a[j],这两个点肯定是可以在一个栈里面的.
若a[i]<a[j],且存在k>j,a[k]<a[i],这两个点不能在一个栈里面.
因为一个栈中的最小值只能是栈顶,又因为k还没有入栈,所以i还不能出栈,所以j还不能入栈.
二分图染色之后就判断了每个点是在哪个栈里面.
然后开一个now标记表示下一个出栈的是什么.
模拟即可.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 #define maxn 1010 15 using namespace std; 16 int a[maxn],co[maxn],w[maxn][maxn],f[maxn],s1[maxn],s2[maxn],top1=0,top2=0,n; 17 void dfs(int x){ 18 for(int i=1;i<=n;i++) 19 if(w[x][i]){ 20 if(co[i]==-1) co[i]=co[x]^1,dfs(i); 21 else if(co[i]==co[x]) printf("0"),exit(0); 22 } 23 } 24 int main(){ 25 scanf("%d",&n); 26 for(int i=1;i<=n;i++) 27 scanf("%d",&a[i]); 28 f[n+1]=2000000000; 29 for(int i=n;i>=1;i--) 30 f[i]=min(f[i+1],a[i]); 31 for(int i=1;i<n;i++) 32 for(int j=i+1;j<=n;j++) 33 if(a[i]<a[j] && f[j+1]<a[i]) w[i][j]=1,w[j][i]=1; 34 memset(co,-1,sizeof(co)); 35 for(int i=1;i<=n;i++) 36 if(co[i]==-1) co[i]=0,dfs(i); 37 s1[0]=s2[0]=10000; 38 int now=1; 39 for(int i=1;i<=n;i++){ 40 if(co[i]==0) printf("a "),s1[++top1]=a[i]; 41 else printf("c "),s2[++top2]=a[i]; 42 while(s1[top1]==now || s2[top2]==now){ 43 if(s1[top1]==now) printf("b "),top1--; 44 else printf("d "),top2--; 45 now++; 46 } 47 } 48 while(top1>0 || top2>0 ){ 49 if(s1[top1]<s2[top2]) printf("b "),top1--; 50 else printf("d "),top2--; 51 } 52 return 0; 53 }
TOP7 NOIP 2015 斗地主(增强版)
题目和斗地主一样,加了hack数据.
然后发现原来的那个贪心算法完全就是个错的.
所以这道题的正解本来不是这个,然后因为数据水被认为这道题的正解是搜索+贪心...
贪心策略出错的原因:贪心是爆搜顺子之后,把其他的牌每次尽可能大的打出.
但是平常打牌的时候并不是这样.
有可能你把一个炸弹拆掉去打会更优.
那怎么搞?
还是先爆搜顺子.然后求剩余的牌最少要打几次.
题目里说每组数据的n都相等,这提示我们用动态规划.
设dp[i][j][k][l]表示还有i种4张的牌,j种3张的牌,k种2张的牌,l种单牌.
转移有很多,除了题面上的那几种外,还要考虑把某种牌拆开.
写的时候一定要细心.
边界要注意判,上边界没有判会导致很多状态的值为0.
若数据不水的话,这应该算是是一道比较难的题了吧.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 using namespace std; 15 int a[20],ans=1000000000,n,dp[25][50][75][100]; 16 void updata(int &x,int y){x=x>y?y:x;} 17 inline int win(){ 18 int w[5]={0,0,0,0,0}; 19 for(int i=1;i<=15;i++) 20 if(a[i]) w[a[i]]++; 21 if(a[14] && a[15]) return min(dp[w[4]][w[3]][w[2]][w[1]-2]+1,dp[w[4]][w[3]][w[2]][w[1]]); 22 else return dp[w[4]][w[3]][w[2]][w[1]]; 23 } 24 void dfs(int w,int las){ 25 if(las==0) return; 26 if(w>ans) return; 27 ans=min(ans,w+win()); 28 for(int i=1;i<=11;i++) 29 if(a[i]>=3){//三顺子. 30 a[i]-=3;int j=i+1; 31 while(j<=12 && a[j]>=3) a[j]-=3,dfs(w+1,las-(j-i+1)*3),j++; 32 j--; 33 for(int k=i;k<=j;k++) a[k]+=3; 34 } 35 for(int i=1;i<=10;i++) 36 if(a[i]>=2 && a[i+1]>=2){//双顺子. 37 a[i]-=2;a[i+1]-=2;int j=i+2; 38 while(j<=12 && a[j]>=2) a[j]-=2,dfs(w+1,las-(j-i+1)*2),j++; 39 j--; 40 for(int k=i;k<=j;k++) a[k]+=2; 41 } 42 for(int i=1;i<=8;i++) 43 if(a[i] && a[i+1] && a[i+2] && a[i+3]){//单顺子. 44 a[i]--;a[i+1]--;a[i+2]--;a[i+3]--;int j=i+4; 45 while(j<=12 && a[j]>=1) a[j]--,dfs(w+1,las-(j-i+1)),j++; 46 j--; 47 for(int k=i;k<=j;k++) a[k]++; 48 } 49 } 50 inline void prepare(){ 51 memset(dp,127/3,sizeof(dp)); 52 dp[0][0][0][0]=0; 53 for(int i=0;i<=n;i++) 54 for(int j=0;j<=n*2;j++) 55 for(int k=0;k<=n*3;k++) 56 for(int l=0;l<=n*4;l++)if(i*4+j*3+k*2+l<=n){ 57 if(i){ 58 updata(dp[i][j][k][l],dp[i-1][j][k][l]+1);//直接炸 59 if(k>=2) updata(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);//4带两对 60 if(l>=2) updata(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);//4带两张 61 if(k>=1) updata(dp[i][j][k][l],dp[i-1][j][k-1][l]+1);//4带一对 62 updata(dp[i][j][k][l],dp[i-1][j+1][k][l+1]);//4拆成3和1 63 updata(dp[i][j][k][l],dp[i-1][j][k+2][l]);//4拆成2和2 64 updata(dp[i][j][k][l],dp[i-1][j][k][l+4]);//4拆成1,1,1,1 65 } 66 if(j){ 67 updata(dp[i][j][k][l],dp[i][j-1][k][l]+1);//直接打 68 if(k>=1) updata(dp[i][j][k][l],dp[i][j-1][k-1][l]+1);//3带一对 69 if(l>=1) updata(dp[i][j][k][l],dp[i][j-1][k][l-1]+1);//3带1 70 updata(dp[i][j][k][l],dp[i][j-1][k+1][l+1]);//拆成1和2 71 updata(dp[i][j][k][l],dp[i][j-1][k][l+3]);//拆成1,1,1 72 } 73 if(k){ 74 updata(dp[i][j][k][l],dp[i][j][k-1][l]+1); 75 updata(dp[i][j][k][l],dp[i][j][k-1][l+2]); 76 } 77 if(l) updata(dp[i][j][k][l],dp[i][j][k][l-1]+1); 78 } 79 } 80 int main(){ 81 int T,x,y; 82 scanf("%d%d",&T,&n); 83 prepare(); 84 while(T){ 85 T--;ans=1000000000; 86 memset(a,0,sizeof(a)); 87 for(int i=1;i<=n;i++){ 88 scanf("%d%d",&x,&y); 89 if(x==0 && y==1) a[14]++; 90 if(x==0 && y==2) a[15]++; 91 if(x==1) a[12]++; 92 else if(x==2) a[13]++; 93 else a[x-2]++; 94 } 95 dfs(0,n); 96 printf("%d\n",ans); 97 } 98 return 0; 99 }
TOP6 NOIP 2016 愤怒的小鸟
主要考点:状压DP
看到猪只有18头,那么把猪状压.
设g[i][j]代表i和j这条抛物线能打掉的猪的集合是什么.
设f[i]代表打掉i这个状态的猪最少需要多少次.
那么就很容易推出一个DP方程:
f[zt]=min(f[zt],f[(zt&g[i][j])^zt]+1).
这样的复杂度是(2^n)*(n^2).
两秒可以跑过,但一秒有点困难.
考虑进一步的优化.
发现每次枚举i和j有很多重复的状态都被枚举了.
考虑有哪些状态是重复的,假设枚举了一个j1和k1,j2和k1,现在的状态是zt
1.若j1,j2,k1在一条抛物线上,那么j2和k1是不用枚举的.
2.若不在一条抛物线上,假设去掉g[j2][ki]后的状态为zt'.(ki!=k1)
那么zt'又可以通过枚举j1和k1更新.
总的来说,j是不用枚举的,每次用任意一个合法的点做j,取枚举k.
不管选的那个点做j,最终的结果都会是一样的.
然而数据足够水,最快的算法居然是爆搜+剪枝?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 #define eps 1e-8 15 using namespace std; 16 struct data{double x,y;}t[20]; 17 int g[20][20],f[262200]; 18 int main(){ 19 int T,n,m; 20 scanf("%d",&T); 21 while(T){ 22 T--;memset(g,0,sizeof(g)); 23 scanf("%d%d",&n,&m); 24 for(int i=0;i<n;i++) 25 scanf("%lf%lf",&t[i].x,&t[i].y); 26 for(int i=0;i<n;i++) 27 for(int j=i+1;j<n;j++){ 28 double a=(t[i].y*t[j].x-t[j].y*t[i].x)/(t[i].x*t[j].x*(t[i].x-t[j].x)); 29 double b=t[i].y/t[i].x-a*t[i].x; 30 if(a+eps>0) continue; 31 for(int k=0;k<n;k++){ 32 double xx=a*t[k].x*t[k].x+b*t[k].x; 33 if(xx-eps<=t[k].y && xx+eps>=t[k].y)g[i][j]|=(1<<k); 34 } 35 } 36 memset(f,127/3,sizeof(f));f[0]=0; 37 for(int zt=1;zt<(1<<n);zt++){ 38 int i=0; 39 while(((1<<i)&zt)==0) i++; 40 f[zt]=f[zt^(1<<i)]+1; 41 for(int j=i+1;j<n;j++) 42 f[zt]=min(f[zt],f[(zt&g[i][j])^zt]+1); 43 } 44 printf("%d\n",f[(1<<n)-1]); 45 } 46 return 0; 47 }
TOP5 NOIP 2015 子串
主要考点:序列动态规划.
设状态f[i][j][k]为a串到i,b串到j,分了k组的方案数.
那么只有a[i]==b[j]时才能转移.
若a[i-1]!=b[i-1],这时只能新建一组,f[i][j][k]+=Σf[p][j-1][k-1](p<i)
若a[i-1]==b[i-1],这时还可以和前面的接起来,f[i][j][k]+=f[i-1][j-1][k].
这个DP肯定是过不去的,还需要优化,不过这个优化很显然了.
空间上把k那一维滚动.
时间上在第一个转移的时候记一个前缀和.
这道题看大佬的博客有说20分钟切掉的..
根本想不出来..DP没入门..
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define mod 1000000007 4 using namespace std; 5 LL f[2][1010][210],sum[2][1010][210]; 6 char a[1010],b[210]; 7 int main(){ 8 freopen("!.in","r",stdin); 9 freopen("!.out","w",stdout); 10 int n,m,k; 11 scanf("%d%d%d",&n,&m,&k); 12 scanf("%s%s",a+1,b+1); 13 int t=1,tt=0; 14 for(int i=0;i<=n;i++) 15 sum[0][i][0]=1; 16 for(int l=1;l<=k;l++){ 17 memset(f[t],0,sizeof(f[t])); 18 memset(sum[t],0,sizeof(sum[t])); 19 for(int i=1;i<=n;i++) 20 for(int j=1;j<=m;j++){ 21 if(a[i]==b[j]){ 22 f[t][i][j]+=sum[tt][i-1][j-1]; 23 f[t][i][j]%=mod; 24 if(a[i-1]==b[j-1]) 25 f[t][i][j]=(f[t][i][j]+f[t][i-1][j-1])%mod; 26 } 27 sum[t][i][j]=sum[t][i-1][j]+f[t][i][j]; 28 sum[t][i][j]%=mod; 29 } 30 swap(t,tt); 31 } 32 LL ans=0; 33 for(int i=1;i<=n;i++) 34 ans+=f[tt][i][m],ans%=mod; 35 printf("%lld",ans%mod); 36 return 0; 37 }
TOP4 NOIP 2011 观光公交
主要考点:贪心,递推.
可以看出每个人对答案的贡献只跟公交车到他的终点的时间有关.
先可以求出不用氮气加速时的答案.递推即可.
考虑对于每一条路使用氮气加速会对哪些车站有影响.
若在i到i+1使用氮气加速,那么到i+1的时间会-1.
若在i+1处还需要等人的话,那么i+1之后的车站是不会受到影响的.
所以就要求出每个点最大影响到哪里,记为g,这个也是递推求.
若i+1处要等,那么g[i]=i+1.否则g[i]=g[i+1].
然后现在就可以贪心了,每次选择一条能加速的道路,且这条道路影响的人最多.
答案减去这个值,并更新g数组.
这道题的思维难度比较大,主要是要想到每条路会对哪些点有影响.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 #define maxn 1010 3 using namespace std; 4 int d[maxn],t[maxn*10],a[maxn*10],b[maxn*10],sum[maxn],s[maxn],arr[maxn],g[maxn]; 5 int main(){ 6 int n,m,k,ans=0; 7 scanf("%d%d%d",&n,&m,&k); 8 for(int i=1;i<n;i++) 9 scanf("%d",&d[i]); 10 for(int i=1;i<=m;i++) 11 scanf("%d%d%d",&t[i],&a[i],&b[i]),s[a[i]]=max(s[a[i]],t[i]),sum[b[i]]++; 12 for(int i=1;i<=n+1;i++) sum[i]+=sum[i-1]; 13 for(int i=1;i<=n;i++) 14 arr[i+1]=max(arr[i],s[i])+d[i]; 15 for(int i=1;i<=m;i++) 16 ans+=arr[b[i]]-t[i]; 17 g[n]=n+1; 18 for(int i=n-1;i>=1;i--) 19 g[i]=arr[i+1]>s[i+1]?g[i+1]:i+1; 20 while(k){ 21 k--; 22 int zd=0,sp=0; 23 for(int i=1;i<n;i++) 24 if(sum[g[i]]-sum[i]>zd && d[i]>0) zd=sum[g[i]]-sum[i],sp=i; 25 ans-=zd;d[sp]--; 26 for(int i=1;i<=n;i++) 27 arr[i+1]=max(arr[i],s[i])+d[i]; 28 for(int i=n-1;i>=1;i--) 29 g[i]=arr[i+1]>s[i+1]?g[i+1]:i+1; 30 } 31 printf("%d",ans); 32 return 0; 33 }
TOP3 NOIP 2011 疫情控制
主要考点:二分,贪心.
首先直接求应该是不可做的,二分一个答案lim.
考虑怎么check.
对于那些不能走到根的军队,就让他尽量往上走.因为越往上控制得越多.
然后现在就在根有一些军队,每个军队还能走wi.
还有一些根的子节点需要被控制.
那么就要判断是否存在一种方案使得每个没被控制的子节点都被控制.
把这两个数组从小到大排序.
然后尽量用能移动距离小的军队去控制较近的点.
这样不一定是对的,因为某个军队还可以去控制他来的那个点.
那么就要加一些特判:若某个军队走到根之后走不回他来的那个点了,那么就让他停在他来的那个点.因为他能去的点一定比这个点小,那么让别的人去那个点肯定要优一些.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 #define maxn 50010 15 #define LL long long 16 using namespace std; 17 struct data{ 18 int nex,to,w; 19 }e[maxn*2]; 20 struct qaq{ 21 LL w;int from; 22 bool operator < (qaq h) const{ 23 return w<h.w; 24 } 25 }; 26 int head[maxn],edge=0,top[maxn],n,m,arm[maxn]; 27 LL dis[maxn]; 28 bool bj[maxn]; 29 int f[maxn][20]; 30 inline void add(int from,int to,int w){ 31 e[++edge].nex=head[from]; 32 e[edge].to=to; 33 e[edge].w=w; 34 head[from]=edge; 35 } 36 void dfs(int x,int fa){ 37 if(fa==1) top[x]=x; 38 else top[x]=top[fa]; 39 for(int i=head[x];i;i=e[i].nex){ 40 int v=e[i].to; 41 if(v==fa) continue; 42 f[v][0]=x;dis[v]=dis[x]+e[i].w; 43 dfs(v,x); 44 } 45 } 46 inline void prepare(){ 47 dfs(1,0);f[1][0]=1; 48 for(int j=1;j<=18;j++) 49 for(int i=1;i<=n;i++) 50 f[i][j]=f[f[i][j-1]][j-1]; 51 } 52 void dfs1(int x,int fa){ 53 bool fg=1,fg1=0; 54 for(int i=head[x];i;i=e[i].nex){ 55 int v=e[i].to; 56 if(v==fa) continue; 57 fg1=1; 58 dfs1(v,x); 59 if(!bj[v]) fg=0; 60 } 61 if(fg && fg1) bj[x]=1; 62 } 63 inline bool check(LL lim){ 64 memset(bj,0,sizeof(bj)); 65 vector<qaq>vec,vec1; 66 for(int i=1;i<=m;i++){ 67 int k=arm[i];LL po=lim; 68 for(int j=18;j>=0;j--) 69 if(dis[k]-dis[f[k][j]]<=po) po-=(dis[k]-dis[f[k][j]]),k=f[k][j]; 70 if(k!=1) bj[k]=1; 71 else vec.push_back((qaq){lim-dis[arm[i]],top[arm[i]]}); 72 } 73 dfs1(1,0); 74 for(int i=0;i<vec.size();i++) 75 if(vec[i].w<dis[vec[i].from] && !bj[vec[i].from])bj[vec[i].from]=1,vec[i].w=0; 76 if(bj[1]) return 1; 77 for(int i=head[1];i;i=e[i].nex) 78 if(!bj[e[i].to]) vec1.push_back((qaq){e[i].w,e[i].to}); 79 sort(vec.begin(),vec.end()); 80 sort(vec1.begin(),vec1.end()); 81 unsigned int i=0,j=0; 82 for(;i<vec1.size() && j<vec.size();){ 83 if(bj[vec1[i].from]) {i++;continue;} 84 //if(vec1[i].w<dis[vec[j].from] && !bj[vec[j].from]) bj[vec[j].from]=1,j++; 85 else if(vec1[i].w<=vec[j].w) j++,i++; 86 else j++; 87 } 88 if(i==vec1.size()) return 1; 89 else return 0; 90 } 91 int main(){ 92 int x,y,z; 93 scanf("%d",&n); 94 for(int i=1;i<n;i++) 95 scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z); 96 scanf("%d",&m); 97 for(int i=1;i<=m;i++) 98 scanf("%d",&x),arm[i]=x; 99 prepare(); 100 LL l=0,r=1e10; 101 while(l<=r){ 102 LL mid=(l+r)>>1; 103 if(check(mid))r=mid-1; 104 else l=mid+1; 105 } 106 printf("%lld",l); 107 return 0; 108 }
TOP2 NOIP 2014 华容道
主要考点:搜索,最短路
暴力:BFS. 搜索空白的格子往四周走. 队列里面记录空格的位置和目标块的位置和步数,并且标记这个状态不能重复走. 这样搞好像有80分.
正解:BFS+SPFA. 要使某个块走到特定的点,首先要使空格移到这个块的边上. 然后再不断地移这个块. 所以,先要BFS求出空白块移到这个块需要多少步. 然后空白块就在这个块边上了. 考虑到要不停的移这个块,那么空白块就要一直在这个块的边上.
定义mov数组,mov[i][j][k][l]代表i,j这个点,空白在k方向,要往l方向移一格的步数. 这个可以BFS求出来. 这样,把每个状态看成一个点,然后问题就转化为最短路问题,跑SPFA就可以了.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 #define inf 1000000000 15 using namespace std; 16 struct data{ 17 int x,y; 18 bool operator == (data h) const{ 19 return h.x==x&&h.y==y; 20 } 21 }; 22 struct node{int x,y,k;}; 23 bool bj[32][32],vis[32][32][5]; 24 int mp[32][32],mov[32][32][5][5],dx[5]={0,1,-1,0,0},dy[5]={0,0,0,1,-1},dis[32][32],n,m,ds[32][32][5]; 25 int bfs(data s,data t){ 26 if(s.x<=0||s.x>n||s.y<=0||s.y>m||t.x<=0||t.x>n||t.y<=0||t.y>m||mp[s.x][s.y]||mp[t.x][t.y]) return inf; 27 if(s==t) return 0; 28 queue<data>q;q.push(s); 29 memset(bj,0,sizeof(bj)); 30 memset(dis,0,sizeof(dis)); 31 bj[s.x][s.y]=1; 32 while(!q.empty()){ 33 data u=q.front();q.pop(); 34 for(int i=1;i<=4;i++){ 35 int xx=u.x+dx[i],yy=u.y+dy[i]; 36 if(xx<=0 || xx>n || yy<=0 || yy>m || mp[xx][yy] || bj[xx][yy]) continue; 37 bj[xx][yy]=1;dis[xx][yy]=dis[u.x][u.y]+1; 38 if((data){xx,yy}==t) return dis[xx][yy]; 39 q.push((data){xx,yy}); 40 } 41 } 42 return inf; 43 } 44 inline void prepare(){ 45 memset(mov,127/3,sizeof(mov)); 46 for(int i=1;i<=n;i++) 47 for(int j=1;j<=m;j++) 48 if(!mp[i][j]) 49 for(int k=1;k<=4;k++) 50 for(int l=1;l<=4;l++){ 51 mp[i][j]=1; 52 mov[i][j][k][l]=bfs((data){i+dx[k],j+dy[k]},(data){i+dx[l],j+dy[l]})+1; 53 mp[i][j]=0; 54 } 55 } 56 inline void work(){ 57 data e,s,t; 58 scanf("%d%d%d%d%d%d",&e.x,&e.y,&s.x,&s.y,&t.x,&t.y); 59 if(s==t){printf("0\n");return;} 60 queue<node>q; 61 memset(ds,127/3,sizeof(ds)); 62 memset(vis,0,sizeof(vis)); 63 mp[s.x][s.y]=1; 64 for(int i=1;i<=4;i++){ 65 int step=bfs(e,(data){s.x+dx[i],s.y+dy[i]}); 66 if(step!=inf) ds[s.x][s.y][i]=step,vis[s.x][s.y][i]=1,q.push((node){s.x,s.y,i}); 67 } 68 mp[s.x][s.y]=0; 69 while(!q.empty()){ 70 node u=q.front();q.pop();vis[u.x][u.y][u.k]=0; 71 for(int i=1;i<=4;i++){ 72 int xx=u.x+dx[i],yy=u.y+dy[i]; 73 if(xx<=0 || xx>n || yy<=0 || yy>m || mp[xx][yy]) continue; 74 int w; 75 if(i%2)w=i+1; 76 else w=i-1; 77 if(ds[xx][yy][w]>ds[u.x][u.y][u.k]+mov[u.x][u.y][u.k][i]){ 78 ds[xx][yy][w]=ds[u.x][u.y][u.k]+mov[u.x][u.y][u.k][i]; 79 if(!vis[xx][yy][w]) q.push((node){xx,yy,w}),vis[xx][yy][w]=1; 80 } 81 } 82 } 83 int zx=inf; 84 for(int i=1;i<=4;i++)zx=min(zx,ds[t.x][t.y][i]); 85 if(zx>1e8) printf("-1\n"); 86 else printf("%d\n",zx); 87 } 88 int main(){ 89 int q; 90 scanf("%d%d%d",&n,&m,&q); 91 for(int i=1;i<=n;i++) 92 for(int j=1;j<=m;j++) 93 scanf("%d",&mp[i][j]),mp[i][j]^=1; 94 prepare(); 95 for(int i=1;i<=q;i++) 96 work(); 97 return 0; 98 }
TOP1 NOIP 2016 天天爱跑步
主要考点:树,lca,差分.
先考虑链的情况.
若S<=T.则i点产生贡献的条件是满足存在i-S=w[i],移项
S=i-w[i].设i-w[i]=k[i],A[i]代表当前以i为起点还没有走到终点
的路径个数.那么每走到一个i,ans[i]=A[k[i]].若i这个点作为了
起点,则A[i]++,若作为终点,则A[这条路的起点]--.这个可以开
一个动态数组存下来.
S>T的情况类似.
树上的情况就差不多了,可以把每条路径看成S-LCA和T-LCA的
两条链,分别处理.deep[s]=deep[i]+w[i]才会产生贡献.那么,
对于每个点,要更新完了他的子树才能更新他.而且,所求的
A[k[i]]可能还包含其他子树的起点(因为下标是深度,有很多
深度一样的点).那么就可以在到这个点的时候记录一下A
数组,最后处理的时候减掉就可以了.
T-LCA要复杂一些,开两个动态数组分别记录要增加的位置和
要减少的位置.避免出现负数,把数组下标都加300000.
最后判断一下,若起点就是LCA,且w[s]==0,则要—(此时LCA是
有贡献的,但LCA出现了两次).
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<string> 6 #include<algorithm> 7 #include<map> 8 #include<complex> 9 #include<queue> 10 #include<stack> 11 #include<cmath> 12 #include<set> 13 #include<vector> 14 #define maxn 300010 15 using namespace std; 16 struct data{ 17 int nex,to; 18 }e[maxn*2]; 19 int head[maxn],edge=0,mov=300000; 20 int A[maxn*3],ans[maxn],deep[maxn],w[maxn],val[maxn],maxdep=0; 21 int size[maxn],top[maxn],dad[maxn],son[maxn]; 22 vector<int>vec1[maxn],vec2[maxn],vec3[maxn]; 23 inline void add(int from,int to){ 24 e[++edge].nex=head[from]; 25 e[edge].to=to; 26 head[from]=edge; 27 } 28 void DFS1(int x,int fa){ 29 maxdep=max(maxdep,deep[x]);size[x]=1; 30 for(int i=head[x];i;i=e[i].nex){ 31 int v=e[i].to;if(v==fa) continue; 32 dad[v]=x;deep[v]=deep[x]+1; 33 DFS1(v,x); 34 size[x]+=size[v]; 35 if(size[v]>size[son[x]]) son[x]=v; 36 } 37 } 38 void DFS2(int x,int fa){ 39 if(son[x]) top[son[x]]=top[x],DFS2(son[x],x); 40 for(int i=head[x];i;i=e[i].nex){ 41 int v=e[i].to;if(v==fa || v==son[x]) continue; 42 top[v]=v;DFS2(v,x); 43 } 44 } 45 inline int getlca(int x,int y){ 46 while(top[x]!=top[y]){ 47 if(deep[top[x]]>deep[top[y]]) x=dad[top[x]]; 48 else y=dad[top[y]]; 49 } 50 return deep[x]>deep[y]?y:x; 51 } 52 void dfs1(int x,int fa){ 53 int now=deep[x]+w[x],fb; 54 if(now<=maxdep) fb=A[now]; 55 for(int i=head[x];i;i=e[i].nex){ 56 int v=e[i].to; 57 if(v==fa) continue; 58 dfs1(v,x); 59 } 60 A[deep[x]]+=val[x]; 61 if(now<=maxdep) ans[x]+=A[now]-fb; 62 for(int i=0;i<vec1[x].size();i++) A[deep[vec1[x][i]]]--; 63 } 64 void dfs2(int x,int fa){ 65 int now=deep[x]-w[x],fb=A[now+mov]; 66 for(int i=head[x];i;i=e[i].nex){ 67 int v=e[i].to; 68 if(v==fa) continue; 69 dfs2(v,x); 70 } 71 for(int i=0;i<vec2[x].size();i++) A[vec2[x][i]+mov]++; 72 ans[x]+=A[now+mov]-fb; 73 for(int i=0;i<vec3[x].size();i++) A[vec3[x][i]+mov]--; 74 } 75 int main(){ 76 int n,m,x,y; 77 scanf("%d%d",&n,&m); 78 for(int i=1;i<n;i++) 79 scanf("%d%d",&x,&y),add(x,y),add(y,x); 80 dad[1]=top[1]=deep[1]=1; 81 DFS1(1,0),DFS2(1,0); 82 for(int i=1;i<=n;i++) 83 scanf("%d",&w[i]); 84 for(int i=1;i<=m;i++){ 85 scanf("%d%d",&x,&y);val[x]++; 86 int LCA=getlca(x,y),len=deep[x]+deep[y]-2*deep[LCA]; 87 if(deep[x]-deep[LCA]==w[LCA])ans[LCA]--; 88 vec1[LCA].push_back(x); 89 vec2[y].push_back(deep[y]-len); 90 vec3[LCA].push_back(deep[y]-len); 91 } 92 dfs1(1,0); 93 memset(A,0,sizeof(A)); 94 dfs2(1,0); 95 for(int i=1;i<=n;i++) 96 printf("%d ",ans[i]); 97 return 0; 98 }
NOIP2017 ++RP