NOIp 2015真题模拟赛 By cellur925
果然我还是最菜的==不接受反驳==
Day1
T1:神奇的幻方
思路:直接模拟即可,由于当前放法只与上一放法有关系,用两个变量记录一下即可。10分钟内切掉==
预计得分:100分
实际得分:100分
1 #include<cstdio> 2 #include<algorithm> 3 4 using namespace std; 5 6 int n,lx,ly; 7 int vis[100][100],a[100][100]; 8 9 int main() 10 { 11 freopen("magic.in","r",stdin); 12 freopen("magic.out","w",stdout); 13 scanf("%d",&n); 14 a[1][(n+1)>>1]=1; 15 vis[1][(n+1)>>1]=1; 16 lx=1;ly=(n+1)>>1; 17 for(int i=2;i<=n*n;i++) 18 { 19 if(lx==1&&ly!=n) 20 { 21 a[n][ly+1]=i; 22 vis[n][ly+1]=1; 23 lx=n,ly++; 24 } 25 else if(lx!=1&&ly==n) 26 { 27 a[lx-1][1]=i; 28 vis[lx-1][1]=1; 29 lx--;ly=1; 30 } 31 else if(lx==1&&ly==n) 32 { 33 a[lx+1][ly]=i; 34 vis[lx+1][ly]=1; 35 lx++; 36 } 37 else if(lx!=1&&ly!=n) 38 { 39 if(!vis[lx-1][ly+1]) 40 { 41 a[lx-1][ly+1]=i; 42 vis[lx-1][ly+1]=1; 43 lx--;ly++; 44 } 45 else 46 { 47 a[lx+1][ly]=i; 48 vis[lx+1][ly]=i; 49 lx++; 50 } 51 } 52 } 53 for(int i=1;i<=n;i++) 54 { 55 for(int j=1;j<=n;j++) 56 printf("%d ",a[i][j]); 57 printf("\n"); 58 } 59 return 0; 60 }
T2:信息传递
思路:方法有多种。可以用tarjan求最小环,或者用拓扑排序,或者用并查集。以前做过,还写了题解 。感觉这是noip第二题少有的简单题了吧。考场上写的tarjan。
预计得分:100分
实际得分:80分
(是这样的qwq 因为实在win下评测的栈小,而且用的cena栈会更小,于是就出锅了qwq,拿到洛咕上是可以满分的,而且这个栈貌似还卡了递归版拓扑排序)
1 #include<cstdio> 2 #include<algorithm> 3 #include<stack> 4 #define maxn 200090 5 6 using namespace std; 7 8 int n,r,tot,dfs_clock,scc_cnt,ans=19260817; 9 int head[maxn],dfn[maxn],low[maxn],size[maxn],scc[maxn]; 10 struct node{ 11 int to,next; 12 }edge[maxn]; 13 stack<int>s; 14 15 void add(int x,int y) 16 { 17 edge[++tot].to=y; 18 edge[tot].next=head[x]; 19 head[x]=tot; 20 } 21 22 void tarjan(int x) 23 { 24 dfn[x]=low[x]=++dfs_clock; 25 s.push(x); 26 for(int i=head[x];i;i=edge[i].next) 27 { 28 int y=edge[i].to; 29 if(!dfn[y]) 30 { 31 tarjan(y); 32 dfn[x]=min(dfn[x],dfn[y]); 33 } 34 else if(!scc[y]) dfn[x]=min(dfn[x],low[y]); 35 } 36 if(low[x]==dfn[x]) 37 { 38 scc_cnt++; 39 while(1) 40 { 41 int u=s.top(); 42 s.pop(); 43 scc[u]=scc_cnt; 44 if(x==u) break; 45 } 46 } 47 } 48 49 int main() 50 { 51 freopen("message.in","r",stdin); 52 freopen("message.out","w",stdout); 53 scanf("%d",&n); 54 for(int i=1;i<=n;i++) 55 scanf("%d",&r),add(i,r); 56 for(int i=1;i<=n;i++) 57 if(!dfn[i]) tarjan(i); 58 for(int i=1;i<=n;i++) 59 size[scc[i]]++; 60 for(int i=1;i<=scc_cnt;i++) 61 if(size[i]!=1&&size[i]<ans) ans=size[i]; 62 printf("%d",ans); 63 return 0; 64 }
T3:斗地主
思路:我没玩过斗地主qwq,表示看到题理解游戏规则就用了好久。这貌似是个搜索,不过noip还会考裸的搜索嘛qwq(不过真的考了)。平时搜索能力就很弱,独立写出搜索的情况很少,不过最近有意在练习搜索了qwq,也已经有了自己的理解,对搜索应该还是缺乏训练。这篇写了题解(在这里)。考场上写了n=2/3/4的子任务,就是骗分了。
预计得分:30分
实际得分:20分
(是这样的qwq,n==4的情况我应该是写判断的时候出锅了,暴力都打错了。。。)
1 #include<algorithm> 2 #include<cstdio> 3 #include<cstring> 4 5 using namespace std; 6 7 int T,n,no,x,ans=19260817; 8 int tong[20]; 9 10 void dfs(int now) 11 { 12 int cnt=0; 13 for(int i=3;i<=14;i++) 14 {// shunzi single 15 if(tong[i]) cnt++; 16 else cnt=0; 17 if(cnt>=5) 18 { 19 tong[i]--,tong[i-1]--,tong[i-2]--,tong[i-3]--; 20 int k=i-cnt+1; 21 for(int j=i-4;j>=k;j--) 22 tong[j]--,dfs(now+1); 23 for(int j=k;j<=i;j++) 24 tong[j]++; 25 } 26 } 27 cnt=0; 28 for(int i=3;i<=14;i++) 29 {// shunzi double 30 if(tong[i]>=2) cnt++; 31 else cnt=0; 32 if(cnt>=3) 33 { 34 tong[i]-=2,tong[i-1]-=2; 35 int k=i-cnt+1; 36 for(int j=i-2;j>=k;j--) 37 tong[j]-=2,dfs(now+1); 38 for(int j=k;j<=i;j++) 39 tong[j]+=2; 40 } 41 } 42 cnt=0; 43 for(int i=3;i<=14;i++) 44 {// shunzi third 45 if(tong[i]>=3) cnt++; 46 else cnt=0; 47 if(cnt>=2) 48 { 49 tong[i]-=3; 50 int k=i-cnt+1; 51 for(int j=i-1;j>=k;j--) 52 tong[j]-=3,dfs(now+1); 53 for(int j=k;j<=i;j++) 54 tong[j]+=3; 55 } 56 } 57 for(int i=3;i<=15;i++) 58 {// four with 2 59 if(tong[i]>=4) 60 { 61 tong[i]-=4; 62 for(int j=3;j<=16;j++) 63 if(tong[j]) 64 {//2 single 65 tong[j]--; 66 for(int k=3;k<=16;k++) 67 if(tong[k]) 68 { 69 tong[k]--; 70 dfs(now+1); 71 tong[k]++; 72 } 73 tong[j]++; 74 } 75 for(int j=3;j<=15;j++) 76 if(tong[j]>=2) 77 {//2 double 78 tong[j]-=2; 79 for(int k=3;k<=15;k++) 80 if(tong[k]>=2) 81 { 82 tong[k]-=2; 83 dfs(now+1); 84 tong[k]+=2; 85 } 86 tong[j]+=2; 87 } 88 dfs(now+1); 89 //three card 90 tong[i]+=4; 91 } 92 } 93 for(int i=3;i<=15;i++) 94 { 95 if(tong[i]>=3) 96 { 97 tong[i]-=3; 98 for(int j=3;j<=16;j++) 99 if(tong[j]) 100 {//three with 1 101 tong[j]--; 102 dfs(now+1); 103 tong[j]++; 104 } 105 for(int j=3;j<=15;j++) 106 if(tong[j]>=2) 107 {//three with 2 108 tong[j]-=2; 109 dfs(now+1); 110 tong[j]+=2; 111 } 112 dfs(now+1);// 3 single 113 tong[i]+=3; 114 } 115 } 116 if(tong[16]==2) now++; 117 else if(tong[16]==1) now++; 118 for(int i=3;i<=15;i++) if(tong[i]) now+=tong[i]>>1; 119 for(int i=3;i<=15;i++) if(tong[i]) now+=tong[i]&1; 120 ans=min(ans,now); 121 } 122 123 int main() 124 { 125 scanf("%d%d",&T,&n); 126 while(T--) 127 { 128 for(int i=1;i<=n;i++) 129 { 130 scanf("%d%d",&x,&no); 131 if(x==1) x=14; 132 else if(x==2) x=15; 133 else if(x==0) x=16; 134 tong[x]++; 135 } 136 dfs(0); 137 printf("%d\n",ans); 138 ans=19260817; 139 memset(tong,0,sizeof(tong)); 140 } 141 return 0; 142 }
Day1 预计得分:230
实际得分:200
考场上感觉还是有点松懈,第三题没好好想,暴力还写错了,考场上一定要全身心投入啊qwq
Day2
T1:跳石头
思路:裸的二分答案。“最大距离最小”暗示着我们二分的性质,写check函数也很容易,标准的第一题难度。
预计得分:100分
实际得分:100分
(*Add:写的二分的边界细节好像还是不准,还需要多写一些二分题巩固qwq)
1 #include<cstdio> 2 #include<algorithm> 3 4 using namespace std; 5 6 int lin,n,m; 7 int seq[100000]; 8 9 bool check(int x) 10 { 11 int pre=0,cnt=0; 12 for(int i=1;i<=n;i++) 13 { 14 if(seq[i]-pre<x) cnt++; 15 else pre=seq[i]; 16 if(cnt>m) return 0; 17 } 18 return 1; 19 } 20 21 int main() 22 { 23 24 scanf("%d%d%d",&lin,&n,&m); 25 for(int i=1;i<=n;i++) 26 scanf("%d",&seq[i]); 27 seq[n+1]=lin; 28 int l=0,r=lin; 29 while(l<r) 30 { 31 int mid=(l+r+1)>>1; 32 if(check(mid)) l=mid; 33 else r=mid-1; 34 } 35 printf("%d\n",l); 36 return 0; 37 }
T2:子串
思路:本来dp就弱,字符串也没学多少,二者加起来就mengbier了...读题没有多久就弃疗去看第三题了,结果第三题一打就是几乎三个小时...考前10分钟把k=1的暴力打了,本想苟一苟把k=2的也写上,结果时间也不够了。
预计得分:10分
实际得分:10分
正解:方案数,dp。有经验的dalao可我不是可以轻松推出:设f[i][j][k]表示A串匹配到第i位,B串匹配到第j位,用了k个子串 的方案数。并显然有f[i][j][k]=f[i-1][j-1][k-1]+f[i-1][j-1][k],但是我们冷静分析会发现这个转移是不靠谱的。
首先是它的正确性:上述转移只有在A[i]==B[j]时才成立,于是我们就遗弃掉了没匹配上的情况。
我们可以再开一个辅助转移数组,s[i][j][k]表示一定用到了当前字符A[i]的方案数,f[i][j][k]表示用或不用当前字符的方案数。
分析s数组的转移:转移的前提是A[i]==B[j],既然A[i]一定用上了,那么有独自成一串,和与前面组合成一串的两种情况。
那么有s[i][j][k]=f[i-1][j-1][k-1]+s[i-1][j-1][k]。如果不能转移,则为0.(不能忽视的一步!!后面与滚动数组相关)
再分析f数组的转移:可由使用当前字符和不使用当前字符转移过来.
那么有f[i][j][k]=s[i][j][k]+f[i-1][j][k]。
至此我们成功解决了转移的正确性。
但是显然它是会超空间的,dp的优化我们考虑状态和转移,状态没有可优化的地方,那么我们考虑转移。观察到转移的第一维只与上一值有关,我们就可以用滚动数组。然后我是第一次写滚动数组==,其实就是把第一维改成两个量pre和now,转移后交换pre和now,达到i-1的效果。
以及不要忘记赋初值。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int M=1010,mod=1e9+7; 5 int n,m,k,dp[1002][102][102]; 6 char a[M],b[M]; 7 int main() 8 { 9 freopen("substring.in","r",stdin); 10 freopen("substring.out","w",stdout); 11 scanf("%d%d%d",&n,&m,&k); 12 scanf("%s",a+1); 13 scanf("%s",b+1); 14 if(k==1) 15 { 16 int ans=0; 17 for(int i=1;i<=n-m+1;i++) 18 { 19 bool flag=0; 20 for(int j=1;j<=m;j++) 21 if(a[i+j-1]!=b[j]){ 22 flag=1;break; 23 } 24 if(!flag)ans++; 25 } 26 ans%=mod; 27 printf("%d\n",ans); 28 return 0; 29 } 30 else if(k==2) 31 { 32 int ans=0; 33 for(int i=1;i<=n-m+1;i++)//枚举第一次选的开始位置 34 { 35 for(int len=1;len<=m-1;len++)//枚举第一次选了几个 36 { 37 int len1=m-len; 38 for(int j=i+len;j<=n-len1+1;j++)//枚举第二次从哪儿开始选 39 { 40 bool flag=0; 41 int p=0; 42 for(int t=i;t<=i+len-1;t++) 43 { 44 if(a[t]!=b[++p]){ 45 flag=1;break; 46 } 47 } 48 if(flag)continue; 49 for(int t=j;t<=j+len1-1;t++) 50 { 51 if(a[t]!=b[++p]){ 52 flag=1;break; 53 } 54 } 55 if(!flag){ 56 ans++; 57 //printf("%d %d %d %d\n",i,i+len-1,j,j+len1-1); 58 } 59 } 60 } 61 } 62 cout<<ans<<endl; 63 return 0; 64 } 65 else 66 { 67 for(int i=1;i<=k;i++) 68 dp[0][0][i]=1; 69 for(int i=1;i<=n;i++) 70 dp[i][0][0]=1; 71 for(int i=1;i<=m;i++) 72 dp[0][i][0]=1; 73 for(int i=1;i<=n;i++) 74 for(int j=1;j<=m;j++) 75 for(int t=1;t<=k;t++) 76 { 77 if(a[i]==b[j]) 78 dp[i][j][t]=((ll)dp[i][j][t]+dp[i-1][j-1][t]+dp[i-1][j][t-1])%mod; 79 else dp[i][j][t]=((ll)dp[i][j][t]+dp[i-1][j][t-1])%mod; 80 printf("%d %d %d %d\n",i,j,t,dp[i][j][t]); 81 } 82 cout<<dp[n][m][k]<<endl; 83 return 0; 84 } 85 86 }
1 #include<cstdio> 2 #include<algorithm> 3 4 using namespace std; 5 typedef long long ll; 6 7 int n,m,k; 8 ll moder=1e9+7,f[2][2000][2000],s[2][2000][2000]; 9 char A[2000],B[2000]; 10 11 int main() 12 { 13 scanf("%d%d%d",&n,&m,&k); 14 scanf("%s",A+1); 15 scanf("%s",B+1); 16 int now=1,pre=0;f[0][0][0]=1; 17 for(int i=1;i<=n;i++) 18 { 19 f[now][0][0]=1; 20 for(int j=1;j<=m;j++) 21 for(int q=1;q<=k;q++) 22 { 23 if(A[i]==B[j]) (s[now][j][q]=s[pre][j-1][q]+f[pre][j-1][q-1])%=moder; 24 else s[now][j][q]=0; 25 (f[now][j][q]=f[pre][j][q]+s[now][j][q])%=moder; 26 } 27 swap(now,pre); 28 } 29 printf("%lld",f[pre][m][k]%moder); 30 return 0; 31 }
T3:运输计划
考场思路:这题我跟它耗了将近三小时qwq.
心路历程如下:(考场上真实记录)
其实感觉m==1的情况还是比较悬的
要是最短路有多条 ,然后最后找到的那条上的最大值恰好比较小 那不就凉了么==
然后到3000的数据,开始想n^2算法应该能想出来,后来感觉好像需要n^2logn
dij的复杂度是多少来着???nlogn?????如果是nlogn海星 不是就凉了==
dij好像更稳一些,但是我一共也没打过几次dij==
不对,打完dij感觉好像根本不是那么回事 又给删了==
dij求的是单源最短路 复杂度岂不会变成n^3logn???
还不如用floyd呢(滑稽)那这样好像要用lca了==
LCA复杂度多少来着???
好像想出了正解(??)
但是感觉悬啊
60分差不多吧...
最后当然是非正解了==
预计得分 :0~100分
实际得分 :10分
1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 #include<queue> 5 #include<vector> 6 7 using namespace std; 8 typedef long long ll; 9 10 int n,m,tot,t,x,y,z,noww,chang; 11 ll odd,ans; 12 int head[300090],cnt[600090],d[300090],f[300090][20]; 13 struct node{ 14 int to,val,next; 15 }edge[600090]; 16 queue<int>q; 17 vector<int>son[300090]; 18 19 void add(int x,int y,int z) 20 { 21 edge[++tot].to=y; 22 edge[tot].next=head[x]; 23 head[x]=tot; 24 edge[tot].val=z; 25 } 26 27 void init() 28 { 29 q.push(1);d[1]=1; 30 while(!q.empty()) 31 { 32 int x=q.front(); 33 q.pop(); 34 for(int i=head[x];i;i=edge[i].next) 35 { 36 int y=edge[i].to; 37 if(d[y]) continue; 38 d[y]=d[x]+1; 39 f[y][0]=x; 40 for(int j=1;j<=t;j++) 41 f[y][j]=f[f[y][j-1]][j-1]; 42 q.push(y); 43 } 44 } 45 } 46 47 int lca(int x,int y) 48 { 49 if(d[x]>d[y]) swap(x,y); 50 for(int i=t;i>=0;i--) 51 if(d[f[y][i]]>=d[x]) y=f[y][i]; 52 if(x==y) return x; 53 for(int i=t;i>=0;i--) 54 if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; 55 return f[x][0]; 56 } 57 58 bool work(int fa,int x) 59 { 60 for(int i=head[fa];i;i=edge[i].next) 61 if(edge[i].to==x) 62 { 63 cnt[i]++; 64 son[noww].push_back(i); 65 return 1; 66 } 67 for(int i=head[fa];i;i=edge[i].next) 68 { 69 int y=edge[i].to; 70 if(y==x) 71 { 72 cnt[i]++; 73 son[noww].push_back(i); 74 break; 75 return 1; 76 } 77 if(work(y,x)) 78 { 79 cnt[i]++; 80 son[noww].push_back(i); 81 return 1; 82 } 83 } 84 return 0; 85 } 86 87 int main() 88 { 89 freopen("transport.in","r",stdin); 90 freopen("transport.out","w",stdout); 91 scanf("%d%d",&n,&m); 92 t=log2(n)+1; 93 for(int i=1;i<=n-1;i++) 94 { 95 scanf("%d%d%d",&x,&y,&z); 96 add(x,y,z),add(y,x,z); 97 } 98 init(); 99 for(int i=1;i<=m;i++) 100 { 101 scanf("%d%d",&x,&y); 102 noww=i; 103 int fa=lca(x,y); 104 if(fa!=x) work(fa,x); 105 if(fa!=y) work(fa,y); 106 } 107 for(int i=1;i<=tot;i+=2) 108 cnt[i]=cnt[i]+cnt[i+1]; 109 for(int i=1;i<=tot;i+=2) 110 { 111 ll tmp=cnt[i]; 112 tmp*=edge[i].val; 113 if(tmp>odd) chang=i,odd=tmp; 114 } 115 for(int i=1;i<=m;i++) 116 { 117 ll tmp=0; 118 for(int j=0;j<son[i].size();j++) 119 if(son[i][j]==chang||son[i][j]==chang+1) continue; 120 else tmp+=edge[son[i][j]].val; 121 ans=max(ans,tmp); 122 } 123 printf("%d",ans); 124 return 0; 125 }
但是后来努力学习了一下60分做法(开始是想搞到 60分的)
60分做法:50pts n² + 10pts m==1,这些方法就是暴力向上跳。
然后暴力的核心思想:因为这是在树上,所以每个点可以认为有唯一的入度,所以可以预处理出到每个点的边的权值,以及这个点的父亲。
瞎搞vis数组开的3000,数组还越界了...导致有两个骗分点一直输出0.
1 #include<cstdio> 2 #include<algorithm> 3 #define maxn 100090 4 5 using namespace std; 6 typedef long long ll; 7 8 int n,m,tot; 9 int head[maxn],d[maxn],fa[maxn],pre[maxn],vis[3009][3009],fin[maxn]; 10 struct node{ 11 int to,next,val; 12 }edge[2*maxn]; 13 struct cellur{ 14 int x,y; 15 }tas[3000]; 16 17 ll lmax(ll a,ll b) 18 { 19 if(a>b) return a; 20 else return b; 21 } 22 23 void add(int x,int y,int z) 24 { 25 edge[++tot].to=y; 26 edge[tot].next=head[x]; 27 head[x]=tot; 28 edge[tot].val=z; 29 } 30 31 void dfs(int x) 32 { 33 d[x]=d[fa[x]]+1; 34 for(int i=head[x];i;i=edge[i].next) 35 { 36 int y=edge[i].to; 37 if(y==fa[x]) continue; 38 pre[y]=edge[i].val; 39 fa[y]=x; 40 dfs(y); 41 } 42 } 43 44 int subw(int pos,int x,int y) 45 { 46 int ans=0; 47 if(d[x]<d[y]) swap(x,y); 48 while(d[x]>d[y]) 49 { 50 ans+=pre[x]; 51 vis[x][pos]=1; 52 x=fa[x]; 53 } 54 while(x!=y) 55 { 56 ans+=pre[x]+pre[y]; 57 vis[x][pos]=1,vis[y][pos]=1; 58 x=fa[x],y=fa[y]; 59 } 60 return ans; 61 } 62 63 void work1() 64 { 65 int odd=0,ans=0; 66 int x=tas[1].x,y=tas[1].y; 67 if(d[x]<d[y]) swap(x,y); 68 while(d[x]>d[y]) 69 { 70 ans+=pre[x]; 71 odd=lmax(odd,pre[x]); 72 x=fa[x]; 73 } 74 while(x!=y) 75 { 76 ans+=pre[x]+pre[y]; 77 odd=lmax(odd,pre[x]); 78 odd=lmax(odd,pre[y]); 79 x=fa[x],y=fa[y]; 80 } 81 printf("%lld",ans-odd); 82 } 83 84 void work2() 85 { 86 int odd=0; 87 int minn=-1; 88 for(int i=1;i<=m;i++) fin[i]=subw(i,tas[i].x,tas[i].y),minn=max(minn,fin[i]); 89 for(int i=1;i<=n;i++) 90 { 91 //所有路径只能是pre[i]上的 所以枚举这些边就行 92 int tmp=0; 93 for(int j=1;j<=m;j++) 94 tmp=max(tmp,fin[j]-pre[i]*vis[i][j]); 95 //vis数组就是这条边有没有在j这个计划中出现过 只能为0或1 96 if (minn==-1||minn>tmp)minn=tmp; 97 } 98 printf("%d",minn); 99 } 100 101 int main() 102 { 103 scanf("%d%d",&n,&m); 104 for(int i=1;i<=n-1;i++) 105 { 106 int x=0,y=0,z=0; 107 scanf("%d%d%d",&x,&y,&z); 108 add(x,y,z),add(y,x,z); 109 } 110 for(int i=1;i<=m;i++) 111 scanf("%d%d",&tas[i].x,&tas[i].y); 112 fa[1]=1; 113 dfs(1); 114 if(m==1) 115 work1(); 116 else work2(); 117 return 0; 118 }
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 using namespace std; 6 const int M=3e5+10,MAX=1e7; 7 typedef long long ll; 8 int read() 9 { 10 int ans=0; 11 char ch=getchar(),last=' '; 12 while(ch<'0'||ch>'9') 13 {last=ch;ch=getchar();} 14 while(ch>='0'&&ch<='9') 15 {ans=ans*10+ch-'0';ch=getchar();} 16 if(last=='-')ans=-ans; 17 return ans; 18 } 19 int n,m; 20 ll d[M],ans=1e12; 21 int num=0,head[M]; 22 struct node{ 23 int beg,end,next,w; 24 }e[M*2]; 25 struct Node{ 26 int str,fin,cost; 27 }P[M]; 28 struct PLAN{ 29 int from,to; 30 }p[M]; 31 void add(int x,int y,int z) 32 { 33 num++; 34 e[num].beg=x; 35 e[num].end=y; 36 e[num].w=z; 37 e[num].next=head[x]; 38 head[x]=num; 39 } 40 41 int size[M],fa[M],son[M],dep[M]; 42 void dfs1(int x) 43 { 44 dep[x]=dep[fa[x]]+1; 45 size[x]=1; 46 for(int i=head[x];i;i=e[i].next) 47 { 48 int y=e[i].end; 49 if(y==fa[x])continue; 50 fa[y]=x; 51 d[y]=(ll)d[x]+e[i].w; 52 dfs1(y); 53 size[x]+=size[y]; 54 if(!son[x]||size[son[x]]<size[y]) 55 son[x]=y; 56 } 57 } 58 59 int top[M]; 60 void dfs2(int x,int topfa) 61 { 62 top[x]=topfa; 63 if(!son[x])return; 64 dfs2(son[x],topfa); 65 for(int i=head[x];i;i=e[i].next) 66 { 67 int y=e[i].end; 68 if(y==son[x]||y==fa[x])continue; 69 dfs2(y,y); 70 } 71 } 72 73 int LCA(int x,int y) 74 { 75 while(top[x]!=top[y]) 76 { 77 if(dep[top[x]]<dep[top[y]])swap(x,y); 78 x=fa[top[x]]; 79 } 80 if(dep[x]>dep[y])swap(x,y); 81 return x; 82 } 83 bool cmp(Node X,Node Y) 84 { 85 return X.cost>Y.cost; 86 } 87 int main() 88 { 89 freopen("transport.in","r",stdin); 90 freopen("transport.out","w",stdout); 91 n=read();m=read(); 92 for(int i=1;i<=n-1;i++) 93 { 94 int x=read(),y=read(),z=read(); 95 add(x,y,z);add(y,x,z); 96 P[i].str=x;P[i].fin=y;P[i].cost=z; 97 } 98 for(int i=1;i<=m;i++) 99 p[i].from=read(),p[i].to=read(); 100 dfs1(1); 101 dfs2(1,1); 102 if((n<=3000&&m<=3000)||m==1){ 103 //O(nmlogn)枚举暴力,期望得分:55 104 for(int i=1;i<=num;i+=2)//枚举改造了哪个航道 105 { 106 int x=e[i].beg,y=e[i].end,A; 107 if(dep[x]<dep[y])A=x; 108 else A=y; 109 ll mx=0; 110 for(int j=1;j<=m;j++) 111 { 112 int B=p[j].from,E=p[j].to; 113 int lca=LCA(B,E); 114 ll tim=d[B]+d[E]-2*d[lca]; 115 if((LCA(A,B)==A||LCA(A,E)==E)&&dep[lca]<=dep[A]) 116 tim-=e[i].w; 117 mx=max(mx,tim); 118 } 119 ans=min(ans,mx); 120 } 121 cout<<ans<<endl; 122 } 123 else{ 124 sort(P+1,P+n,cmp); 125 for(int i=1;i<=min(n-1,(MAX/m));i++)//枚举改造了哪个航道 126 { 127 int x=P[i].str,y=P[i].fin,A; 128 if(dep[x]<dep[y])A=x; 129 else A=y; 130 ll mx=0; 131 for(int j=1;j<=m;j++) 132 { 133 int B=p[j].from,E=p[j].to; 134 int lca=LCA(B,E); 135 ll tim=d[B]+d[E]-2*d[lca]; 136 if((LCA(A,B)==A||LCA(A,E)==E)&&dep[lca]<=dep[A]) 137 tim-=P[i].cost; 138 mx=max(mx,tim); 139 } 140 ans=min(ans,mx); 141 } 142 cout<<ans<<endl; 143 } 144 fclose(stdin);fclose(stdout); 145 return 0; 146 }
正解:二分答案+lca+树上差分
一句话题意:给你许多树上的链,要求把树上其中一条边变为0后链权值的最大值最小。
这个题出的二分还是十分隐秘的qwq,(比如zhanggenchen篱落疏疏一径深那题)因为题目要我们求计划最大值最小,所以满足二分单调性。
我们就可以二分这个最终的答案。设这个答案为mid,则所有长度>mid的路径上都至少需要删除一条边,对这些路径求交,最优方案是删去路径中长度最大的边。如果删去这条最长边后最长路径还有大于mid的,那么这个答案不合法。
问题的关键转化为求树上路径交,我们可以使用树上差分(现学的)。
另外这里预处理各个计划的链长我用到的是树上倍增求LCA+dfs。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #include<cmath> 6 #define maxn 300090 7 // long long? 8 using namespace std; 9 10 int n,m,t,tot,maxlen,l,r,num,ret; 11 int pre[maxn],head[maxn],d[maxn],f[maxn][20],val[maxn],vis[maxn],dis[maxn]; 12 struct node{ 13 int to,next,val; 14 }edge[maxn*2]; 15 struct cellur{ 16 int x,y,len,lca; 17 }tas[maxn]; 18 19 void add(int x,int y,int z) 20 { 21 edge[++tot].to=y; 22 edge[tot].next=head[x]; 23 head[x]=tot; 24 edge[tot].val=z; 25 } 26 27 void LCA_prework() 28 { 29 queue<int>q; 30 q.push(1);d[1]=1; 31 while(!q.empty()) 32 { 33 int u=q.front();q.pop(); 34 for(int i=head[u];i;i=edge[i].next) 35 { 36 int v=edge[i].to; 37 if(d[v]) continue; 38 d[v]=d[u]+1; 39 f[v][0]=u; 40 pre[v]=edge[i].val; 41 for(int j=1;j<=t;j++) 42 f[v][j]=f[f[v][j-1]][j-1]; 43 q.push(v); 44 } 45 } 46 } 47 48 int LCA(int x,int y) 49 { 50 if(d[x]>d[y]) swap(x,y); 51 for(int i=t;i>=0;i--) 52 if(d[f[y][i]]>=d[x]) y=f[y][i]; 53 if(x==y) return x; 54 for(int i=t;i>=0;i--) 55 if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; 56 return f[x][0]; 57 } 58 59 void dfs(int x) 60 { 61 vis[x]=1; 62 for(int i=head[x];i;i=edge[i].next) 63 { 64 int y=edge[i].to; 65 if(vis[y]) continue; 66 dis[y]=dis[x]+edge[i].val; 67 dfs(y); 68 } 69 } 70 71 void review(int u,int fa) 72 { 73 for(int i=head[u];i;i=edge[i].next) 74 { 75 int v=edge[i].to; 76 if(v==fa) continue; 77 review(v,u); 78 val[u]+=val[v]; 79 } 80 if(val[u]==num&&pre[u]>ret) 81 ret=pre[u];//记录路径中最长的边 82 } 83 84 bool check(int x) 85 { 86 memset(val,0,sizeof(val)); 87 num=ret=0; 88 for(int i=1;i<=m;i++) 89 { 90 if(tas[i].len>x) 91 { 92 val[tas[i].x]++; 93 val[tas[i].y]++; 94 val[tas[i].lca]-=2; 95 num++; 96 } 97 } 98 review(1,0); 99 if(maxlen-ret>x) return 0; 100 return 1; 101 } 102 103 int main() 104 { 105 scanf("%d%d",&n,&m); 106 t=log2(n)+1; 107 for(int i=1;i<=n-1;i++) 108 { 109 int x=0,y=0,z=0; 110 scanf("%d%d%d",&x,&y,&z); 111 add(x,y,z);add(y,x,z); 112 } 113 for(int i=1;i<=m;i++) 114 scanf("%d%d",&tas[i].x,&tas[i].y); 115 LCA_prework(); 116 for(int i=1;i<=n;i++) 117 if(!vis[i]) dfs(i); 118 for(int i=1;i<=m;i++) 119 { 120 int fa=LCA(tas[i].x,tas[i].y); 121 tas[i].lca=fa; 122 tas[i].len=dis[tas[i].x]+dis[tas[i].y]-2*dis[fa]; 123 maxlen=max(maxlen,tas[i].len); 124 } 125 r=maxlen; 126 while(l<r) 127 { 128 int mid=(l+r)>>1; 129 if(check(mid)) r=mid; 130 else l=mid+1; 131 //printf("%d %d\n",ret,num); 132 } 133 printf("%d",l); 134 return 0; 135 }
(但是不开longlong好像也没关系的样子...)
Day2 预计得分:100+10+0~100=110~210
实际得分:100+10+10=120
感觉考场上不能像我今天一样再孤注一掷写T3正解了吧...,拿个稳妥的暴力分也是好的呀...。所以今天的时间分配出现了很大问题,第二题虽然我dp很不好应该至少还能加上20分k=2的情况吧,实在布星T3把所以m==1的情况都打出来,顺便再打个n==100的情况也比我现在的结果强啊qwq。考试策略和技巧还有待改善。
扯些别的:
现在感觉学习了那么多算法,目的当然是尽量在考场上打出正解。但是事实是,T2正解都很难想全,T3正解就更难了。OI赛制中暴力分,部分分还是王道。学习那么多算法,也是让我们在考场上掌握更多优雅的暴力方法,得到更多的分啊。比如ChemistDay2T3打了一个大概是树剖的东西吧,搞到了60pts(?,而我不会树剖,更不会从树的链角度出发分析==
然后感觉现在基础并不牢固==一些算法掌握的也不牢==比如滚动数组优化当初学长讲了但没写,搜索缺少方法,动规更是一塌糊涂==。对照学长给的noip知识表好像没有多少算法敢说自己掌握的特别牢==,比如树上倍增/差分。
还有一些实用技巧也并不会==,比如生成数据手打就有时候不会,(已加入todolist),有的算法复杂度不确定等等==