今天时间本来是很充裕的,毕竟不考试留出了一天的时间进行整理,直到我发现我打完替罪羊树编译报错一大堆(往上滑都要十多秒的那种),还没有调试就已经10点多快十一点的那一刻......郭大佬还是你郭大佬,后来我问他他说这道题他就写了一个多不到两个小时就过了......而我直到现在都还没调完,上午写完后心态直接爆炸,就去做其他的题了,下午又调了将近一个小时,只得了三分之一的分数,持续烦躁,就和后排的三个大神一起去水洛谷了,以后每天抽大概一个小时调一调,最好再重新写一遍吧,毕竟郭大佬也是写了一遍,重构了一遍(话说今天洛谷说我不宜重构代码???),我先把我的一上午的辛苦成果放在这吧,有大佬帮我调一调,纠纠错当然更好qwq.
1 #include<bits/stdc++.h> 2 const int N=1e5+10; 3 struct Tree{ 4 int l,r,x,tot,siz,yxsiz,gssiz,fa; 5 //yx-有效 gs-个数 6 bool del; 7 }tree[N]; 8 double balance=0.75; 9 int len,shan[N],t,root; 10 int newp(){ 11 if(t>0) return shan[t--]; 12 return ++len; 13 } 14 void build(int x,int y,int fa){ 15 tree[y].x=x;tree[y].l=tree[y].r=0;tree[y].tot=1; 16 tree[y].del=false;tree[y].fa=fa; 17 tree[y].siz=tree[y].yxsiz=tree[y].gssiz=1; 18 } 19 int find(int x,int root){ 20 if(x<tree[root].x&&tree[root].l) return find(x,tree[root].l); 21 if(x>tree[root].x&&tree[root].r) return find(x,tree[root].r); 22 return root; 23 } 24 void update(int now,int x,int y,int z){ 25 if(!now) return; 26 tree[now].siz+=x; 27 tree[now].yxsiz+=y; 28 tree[now].gssiz+=z; 29 update(tree[now].fa,x,y,z); 30 } 31 struct Node{ 32 int x,tot; 33 }num[N]; 34 int num_tot=0; 35 void dfs_rebuild(int x) 36 { 37 if(x==0) return; 38 dfs_rebuild(tree[x].l); 39 if(!tree[x].del)num[++num_tot].x=tree[x].x,num[num_tot].tot=tree[x].tot; 40 shan[++t]=x; 41 dfs_rebuild(tree[x].r); 42 } 43 int readd(int l,int r,int fa){ 44 if(l>r) return 0; 45 int mid=(l+r)>>1;int id=newp(); 46 tree[id].fa=fa; 47 tree[id].tot=num[mid].tot; 48 tree[id].x=num[mid].x; 49 tree[id].l=readd(l,mid-1,id); 50 tree[id].r=readd(mid+1,r,id); 51 tree[id].gssiz=tree[tree[id].l].gssiz+tree[tree[id].r].gssiz+num[mid].tot; 52 tree[id].siz=tree[id].yxsiz=r-l+1; 53 tree[id].del=false; 54 return id; 55 } 56 void rebuild(int x){ 57 num_tot=0; 58 dfs_rebuild(x); 59 if(x==root) root=readd(1,num_tot,0); 60 else{ 61 update(tree[x].fa,-tree[x].siz+tree[x].yxsiz,0,0); 62 if(tree[tree[x].fa].l==x) tree[tree[x].fa].l=readd(1,num_tot,tree[x].fa); 63 else tree[tree[x].fa].r=readd(1,num_tot,tree[x].fa); 64 } 65 } 66 void find_rebuild(int now,int x){ 67 if((double)tree[tree[now].l].siz>(double)tree[now].siz*balance || 68 (double)tree[tree[now].r].siz>(double)tree[now].siz*balance || 69 (double)(tree[now].siz-(double)tree[now].yxsiz)>(double)tree[now].siz * 0.4){ 70 rebuild(now);return; 71 } 72 if(tree[now].x!=x) find_rebuild(x<tree[now].x ? tree[now].l : tree[now].r,x); 73 } 74 void Add(int x){ 75 if(root==0){ 76 build(x,root=newp(),0); 77 return; 78 } 79 int p=find(x,root); 80 if(tree[p].x==x){ 81 tree[p].tot++; 82 if(tree[p].del) tree[p].del=0,update(p,0,1,1); 83 else update(p,0,0,1); 84 } 85 else if(x<tree[p].x){ 86 build(x,tree[p].l=newp(),p);update(p,1,1,1); 87 } 88 else build(x,tree[p].r=newp(),p),update(p,1,1,1); 89 find_rebuild(root,x); 90 } 91 void del(int x){ 92 int p=find(x,root); 93 tree[p].tot--; 94 if(!tree[p].tot){ 95 tree[p].del=1; 96 update(p,0,-1,-1); 97 } 98 else update(p,0,0,-1); 99 find_rebuild(root,x); 100 } 101 void query_rank(int x){// 102 int now=root; 103 int ans=0; 104 while(tree[now].x!=x){ 105 if(x>=tree[now].x){ 106 ans+=tree[tree[now].l].gssiz+tree[now].tot; 107 now=tree[now].r; 108 } 109 else{ 110 now=tree[now].l; 111 } 112 } 113 ans+=tree[tree[now].l].yxsiz; 114 printf("%d ",ans+1); 115 } 116 void query_num(int rank){ 117 int now=root; 118 while(1){ 119 if(rank<=tree[tree[now].l].gssiz) now=tree[now].l; 120 else{ 121 rank-=tree[tree[now].l].gssiz; 122 if(rank<=tree[now].tot){ 123 printf("%d ",tree[now].x); 124 return; 125 } 126 rank-=tree[now].tot;now=tree[now].r; 127 } 128 } 129 } 130 bool ans=0; 131 void dfs_lson(int x){ 132 if(tree[x].r!=0) dfs_lson(tree[x].r); 133 if(ans) return; 134 if(!tree[x].del){ 135 ans=1; 136 printf("%d ",tree[x].x); 137 return; 138 } 139 if(tree[x].l!=0) dfs_lson(tree[x].l); 140 } 141 void dfs_rson(int x){ 142 if(tree[x].l!=0) dfs_rson(tree[x].l); 143 if(ans) return; 144 if(!tree[x].del){ 145 ans=1; 146 printf("%d ",tree[x].x); 147 return; 148 } 149 if(tree[x].r!=0) dfs_rson(tree[x].r); 150 } 151 void query_front(int now,int x,bool lson){ 152 if(!lson){ 153 query_front(tree[now].fa,x,tree[tree[now].fa].l==now); 154 return; 155 } 156 if(!tree[now].del&&tree[now].x<x){ 157 printf("%d ",tree[now].x); 158 return; 159 } 160 if(tree[now].l){ 161 ans=0; 162 dfs_lson(tree[now].l); 163 return; 164 } 165 query_front(tree[now].fa,x,tree[tree[now].fa].l==now); 166 } 167 void query_back(int now,int x,bool rson){ 168 if(!rson){ 169 query_back(tree[now].fa,x,tree[tree[now].fa].r==now); 170 return; 171 } 172 if(!tree[now].del&&tree[now].x>x){ 173 printf("%d ",tree[now].x); 174 return; 175 } 176 if(tree[now].r){ 177 ans=0; 178 dfs_rson(tree[now].r); 179 return; 180 } 181 query_back(tree[now].fa,x,tree[tree[now].fa].r==now); 182 } 183 int main(){ 184 //freopen("a.in","r",stdin); 185 int n; 186 scanf("%d",&n); 187 while(n--){ 188 int x,y; 189 scanf("%d%d",&x,&y); 190 if(x==1) Add(y); 191 if(x==2) del(y); 192 if(x==3) query_rank(y); 193 if(x==4) query_num(y); 194 if(x==5) query_front(find(y,root),y,true); 195 if(x==6) query_back(find(y,root),y,true); 196 } 197 return 0; 198 }
(这么彩的结果,你值得拥有)
先放一下这棵树来说一下今天的练习题吧.
看到这个题的第一感觉就是这道题建好图就已经成功了一大半了(某邹姓少年除外,他因为手懒用的STL的栈写的tarjan后来他自己都调不过,后来还是lc大佬调好的)最暴力的建图方式当然是枚举每一个兴奋值在兴趣值之中找到其倍数,之后连边,但是n最大到1e5,n^2的时间效率可能让我们的程序都活不到tarjan部分就挂了,我最先想到的思路是分别枚举兴趣值和兴奋值之中的约数和乘数,如果可以连在约数上的边一定可以连在乘数上,但是当我看到数据范围中的ei为质数后我就猜出题人应该也想到了并要卡这种做法,于是舍弃。
后来我们四个人没有人想到除n^2外的更好方法,就看了题解,其实这道题的正解的建图过程妙不可言。
我们曾经在做并查集时曾经用过一种开二倍空间建虚点的做法,而这道题也正是运用了这种思路。对于兴奋值到兴趣值的倍数关系我们可以在建好基础的边之后进行暴力枚举处理,我们考虑所开的第一倍的空间存储的是第几个玩具,然后第二倍的空间存储的是兴趣值和兴奋值,这样把输入的兴趣值建一条指向该玩具的编号,再把该玩具的编号建一条指向玩该玩具之后的兴奋值,这样就相当于经过一个玩具从一个兴趣值到达所指定的兴奋值,之后我们就处理出不同的兴趣值在第二维中可以到达哪些兴奋值就可以了,这样就完成了我们的建边大业,其实从最近的做题中我发现重点在建边的题目其实不在少数,比如之前做的那道所驼门王的宝藏,也是这样,不过那道题我们考虑的是把所有的横边和纵边用一种特殊的排序方式放在一起,再进行建边,只能说出题人的思路方向不同吧(所驼门王的宝藏链接:https://www.luogu.com.cn/problem/P2403)
1 #include<vector> 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 using namespace std; 7 const int N=2e6+10; 8 int Head[N],tot; 9 struct Node{ 10 int next,to; 11 }edge[N]; 12 void Add(int u,int v){ 13 edge[++tot].to=v; 14 edge[tot].next=Head[u]; 15 Head[u]=tot; 16 } 17 int n; 18 bool vis[N]; 19 int dfn[N],low[N],belong[N],st[N],top,scc_cnt,dfn_cnt; 20 void clear(){ 21 memset(Head,0,sizeof(Head)); 22 memset(vis,0,sizeof(vis)); 23 memset(edge,0,sizeof(edge)); 24 memset(dfn,0,sizeof(dfn)); 25 memset(low,0,sizeof(low)); 26 memset(belong,0,sizeof(belong)); 27 scc_cnt=0; dfn_cnt=0; tot=0; top=0; 28 } 29 void Tarjan(int u){ 30 st[++top]=u; 31 dfn[u]=low[u]=++dfn_cnt; 32 for(int i=Head[u];i;i=edge[i].next){ 33 int v=edge[i].to; 34 if(!dfn[v]){ 35 Tarjan(v); 36 low[u]=min(low[u],low[v]); 37 }else if(!belong[v]) 38 low[u]=min(low[u],dfn[v]); 39 } 40 if(dfn[u]==low[u]){ 41 belong[u]=++scc_cnt; 42 int siz=1; 43 while(st[top]!=u){ 44 belong[st[top]]=scc_cnt; 45 vis[st[top]]=1; 46 siz++; 47 top--; 48 } 49 if(siz>1)vis[u]=1; 50 --top; 51 } 52 } 53 int main(){ 54 int T; 55 scanf("%d",&T); 56 while(T--){ 57 scanf("%d",&n); 58 int Max=0; 59 for(int i=1,x;i<=n;i++){ 60 scanf("%d",&x); 61 Add(n+x,i),Max=max(Max,x); 62 } 63 for(int i=1,x;i<=n;i++){ 64 scanf("%d",&x); 65 Add(i,n+x); 66 } 67 for(int i=1;i<=Max;i++) 68 for(int j=2;j*i<=Max;j++) 69 Add(n+i,n+i*j); 70 for(int i=1;i<=n;i++) 71 if(!dfn[i]) Tarjan(i); 72 int ans=0; 73 for(int i=1;i<=n;i++) 74 if(vis[i]) ans++; 75 printf("%d ",ans); 76 clear(); 77 } 78 return 0; 79 }
今天还和大神们一起做了几道状压DP的水题,正好帮我联系一下我半生不熟的状压dp,这几道题都不算太难(至少和我们平时考的题目相比),这里就不再进行详细说明了,只给出题目编号的代码应该就足够了,他们的思路大致都是一样的,但其中有一维一定是表示当前的状态,另一位视情况而定,有一些是表示数列的前n位,还有的是表示当前的地点,或是对下一位将要进入的状态进行辅助,方便递推关系的进行.
1 #include<bits/stdc++.h> 2 using namespace std; 3 int dp[1<<21],a[100010],num[100100],sum[100010][25]; 4 int main(){ 5 int n,m; 6 scanf("%d%d",&n,&m); 7 for(int i=1;i<=n;++i){ 8 scanf("%d",&a[i]); 9 num[a[i]]++; 10 for(int j=1;j<=m;++j) 11 sum[i][j]=sum[i-1][j]; 12 sum[i][a[i]]++; 13 } 14 int ed=(1<<m)-1; 15 memset(dp,0x3f,sizeof(dp)); 16 dp[0]=0; 17 for(int i=1;i<=ed;++i){ 18 int len=0; 19 for(int j=1;j<=m;++j){ 20 if((i&(1<<(j-1)))) len+=num[j]; 21 } 22 for(int j=1;j<=m;++j){ 23 if(!(i&(1<<(j-1)))) continue; 24 dp[i]=min(dp[i],dp[i^(1<<(j-1))]+num[j]-(sum[len][j]-sum[len-num[j]][j])); 25 } 26 } 27 printf("%d ",dp[ed]); 28 return 0; 29 }
这道题的状态转移方程想出来就已经成功了一半了,即表示当前最后一位的偶像编号,第二位表示当前那些编号的偶像已经站好的状态
1 #include<bits/stdc++.h> 2 using namespace std; 3 double dis[20][20],dp[20][1<<16],x[20],y[20]; 4 double q_dis(int a,int b){ 5 return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])); 6 } 7 int main(){ 8 int n; 9 scanf("%d",&n); 10 for(int i=1;i<=n;++i){ 11 scanf("%lf%lf",&x[i],&y[i]); 12 } 13 n++; 14 x[n]=0;y[n]=0; 15 for(int i=1;i<=n;++i){ 16 for(int j=1;j<=n;++j){ 17 if(i==j) continue; 18 dis[i][j]=q_dis(i,j); 19 //printf("%.2lf ",dis[i][j]); 20 } 21 } 22 int ed=(1<<n)-1; 23 for(int i=1;i<=n;++i) 24 for(int j=0;j<=ed;++j) 25 dp[i][j]=(double)0x3f3f3f; 26 double ans=(double)0x3f3f3f; 27 dp[n][(1<<(n-1))]=0; 28 for(int j=0;j<=ed;++j) 29 for(int k=1;k<=n;++k) 30 for(int i=1;i<=n;++i){ 31 if(j&(1<<(i-1))) continue; 32 if(!(j&(1<<(k-1)))) continue; 33 if(k==i) continue; 34 dp[i][j|(1<<(i-1))]=min(dp[i][j|(1<<(i-1))],dp[k][j]+dis[k][i]); 35 //printf("%.2lf ",dp[k][j]+dis[k][i]); 36 if((j|(1<<(i-1)))==ed) ans=min(ans,dp[i][j|(1<<(i-1))]); 37 } 38 printf("%.2lf ",ans); 39 return 0; 40 }
做好预处理这道题就已经很简单啦.
最后还有一道昨天咕掉的题目,主要是昨天的代码是照题解敲的,但是今天上午我已经把那道题又盲打了一遍,现在就来说一下这道题吧.
这道题是紫题是真的理所应当,以前都没有见过这种转移方式,这次也是开拓了思路了.
我们首先想这道题一定存在重边,不然12个点如何连出1000条边,所以输入时记得去重取最小值,然后我们最后建成的图一定是一棵树,但不一定是最小生成树,毕竟开凿代价是次序乘上代价(黑心商人!!!!),从数据之中我们猜到不是状压就是暴搜,因为暴搜不好处理次序的问题,于是考虑状压,(除了昨天的考试题之外以暴搜为正解的毕竟是少数),于是我们考虑转移,由于路程代价时刻要变,所以我们在计算之前必须预先处理好一些东西,前面提到最后图是一棵树,而开凿的次序不就是树的深度么?于是我们考虑转移应当是从一层转移到另一层的过程,我们转移到另一层之后的状态一定是上一层的一个子集,这里要用到一个神奇的循环:for(int k=j;k;k=(k-1)&j)这样子我们可以求出j的所有子集,我们可以预处理出从一层向下一层扩展的状态,我们可以让k^j得出要新连出的边,之后枚举上一层已经存在的节点取最小距离即可.
完成了预处理,我们来考虑转移:dp[i][j]=min(dp[i][j],dp[i-1][k]+(i-1)*trans[k][j]),i表示层数,j,k分别表示状态,trans存的就是上面预处理出的状态转移的花费,这样我们就解决掉了这道紫题了.
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 int dis[15][15],trans[1<<13][1<<13]; 5 ll dp[15][1<<15]; 6 int main(){ 7 int n,m; 8 scanf("%d%d",&n,&m); 9 memset(dis,0x3f,sizeof(dis)); 10 for(int i=1;i<=m;++i){ 11 int x,y,z; 12 scanf("%d%d%d",&x,&y,&z); 13 if(dis[x][y]>z){ 14 dis[x][y]=dis[y][x]=z; 15 } 16 } 17 int ed=(1<<n)-1; 18 for(int i=0;i<=ed;++i) 19 for(int j=i;j;j=(j-1)&i){ 20 bool ok=0; 21 int s=i^j; 22 for(int k=n;k>=1;--k) 23 if(s>=(1<<(k-1))){ 24 int Min=0x3f3f3f3f; 25 for(int p=1;p<=n;++p) 26 if(j&(1<<(p-1))) 27 Min=min(Min,dis[p][k]); 28 if(Min==0x3f3f3f3f){ 29 ok=1;break;//建不成边无法转移 30 } 31 s-=(1<<(k-1)); 32 trans[j][i]+=Min; 33 } 34 if(ok) trans[j][i]=0x3f3f3f3f; 35 } 36 memset(dp,0x3f,sizeof(dp)); 37 for(int i=1;i<=n;++i) dp[1][1<<(i-1)]=0; 38 for(int i=2;i<=n;++i) 39 for(int j=0;j<=ed;++j) 40 for(int k=j;k;k=(k-1)&j){ 41 if(trans[k][j]==0x3f3f3f3f) continue; 42 dp[i][j]=min(dp[i][j],dp[i-1][k]+(i-1)*trans[k][j]); 43 } 44 ll ans=0x3f3f3f3f3f3f3f3fll; 45 for(int i=1;i<=n;++i) ans=min(ans,dp[i][ed]); 46 printf("%lld ",ans); 47 return 0; 48 }
今天还复习了一道数位dp(主要是考试时一道题别人都在写而我不会QAQ),由于太过于板子,直接放吧
1 //数位dp练习 2 #include<bits/stdc++.h> 3 using namespace std; 4 #define int long long 5 int dp[10][10],a[10]; 6 int DP(int pos,int pre,int pre_0,int limit){ 7 if(pos==0) return 1; 8 if(!limit && dp[pos][pre]!=-1) return dp[pos][pre]; 9 int ans=0; 10 int top= limit ? a[pos] : 9; 11 for(int i=0;i<=top;++i){ 12 if(abs(i-pre)<2) continue; 13 if(pre_0 && i==0) ans+=DP(pos-1,-2,1,limit&&i==top); 14 else ans+=DP(pos-1,i,0,limit&&i==top); 15 } 16 if(!limit && !pre_0) dp[pos][pre]=ans; 17 return ans; 18 } 19 int part(int x){ 20 int len=0; 21 int ans=0; 22 memset(dp,-1,sizeof(dp)); 23 while(x){ 24 a[++len]=x%10;x/=10; 25 } 26 ans=DP(len,-2,1,1); 27 return ans; 28 } 29 signed main(){ 30 int n,m; 31 scanf("%lld%lld",&n,&m); 32 printf("%lld ",part(m)-part(n-1)); 33 return 0; 34 }
还有一道和大神们竞速的树形DP水题(这个题本来是想练习树归的没想到这么水,就成了竞速啦qwq)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 const int N=5e5+10; 5 struct Node{ 6 int next,to,dis; 7 }edge[N]; 8 int Head[N],tot; 9 void Add(int x,int y,int z){ 10 edge[++tot].to=y; 11 edge[tot].next=Head[x]; 12 edge[tot].dis=z; 13 Head[x]=tot; 14 } 15 int dp[N],tim[N]; 16 void DP(int u,int fa){ 17 int Max=0; 18 for(int i=Head[u];i;i=edge[i].next){ 19 int v=edge[i].to; 20 if(v==fa) continue; 21 DP(v,u); 22 Max=max(Max,tim[v]+edge[i].dis); 23 dp[u]+=dp[v]; 24 } 25 tim[u]=Max; 26 for(int i=Head[u];i;i=edge[i].next){ 27 int v=edge[i].to; 28 if(v==fa) continue; 29 dp[u]+=Max-tim[v]-edge[i].dis; 30 } 31 } 32 signed main(){ 33 int n,root; 34 scanf("%lld%lld",&n,&root); 35 for(int i=1;i<n;++i){ 36 int x,y,z; 37 scanf("%lld%lld%lld",&x,&y,&z); 38 Add(x,y,z);Add(y,x,z); 39 } 40 DP(root,0); 41 printf("%lld ",dp[root]); 42 return 0; 43 }
总结: 今天上午的替罪羊树的练习让我认识到了自己码力还是不足,在打的时候即使是错的也不能很舒畅的把它完整的打下来,虽然让它弄得今天一天大脑都跟远走高飞了似的,但是总体来讲成果还是不错的,明天开始我们又要开始新一轮的NOI模拟练习了,文化课期末考试也越来越近了 ,还是希望明天可以对一些没有思路的题可以多加一些考虑,毕竟考试的时候写一道题想半个小时也能写出一道题,在平时也要尽力减少题解的使用,集训也在一天一天的进行着,希望明天会更好吧.