2019.8.13 NOIP模拟测试19 反思总结
最早写博客的一次∑
听说等会儿还要考试【真就两天三考啊】,教练催我们写博客…
大约是出题最友好的一次【虽然我还是炸了】,并且数据也非常水…忽视第三题的锅的话的确可以这么说。但是T3数据出锅就是你的错了虽然就算那样我考场上也写不对
我这次考得挺神奇的,T1加个快读由80pts变成AC【再不学快读我就从这里跳下去】,T2写了个玄学nlog二分答案正确性爆炸但是给了我60分,T3暴力dfs给我20分
侥幸程度太大太大了,硬要说的话,我现在本来应该蹲在角落里爆锤自己一通,虽然其实现在也应该这么做。
看看那些差点AK的大佬就会觉得,自己还是没用到爆炸呀。
T1count:
把一棵树分成几个大小一样的连通块,发现只有√n种可能的块的大小,不能整除的大小一定分不出来。然后就有了n√n的做法,枚举每一个因数,然后跑一遍这棵树从底下开始往里塞点,能完整把整棵树塞成大小都为那个因子的几块的话就说明可行,ans++。
实际上这样写常数要大…瞅瞅题解,发现这样一句话:我们可以发现一个可行的方案的最浅的那个点为根的子树的 SIZE 一定可以整除 D。
这话稍微有点绕…我的理解是,每一个正好能分成几块大小为d的连通块的子树或者整棵树,它的size一定可以被d整除。
这不是废话吗?
再往下读:Check 就不用 N 去 check 了,如果有 N/D 个节点的 SIZE 可以整除 D,那么就可行。
嗯?
n/d是把整棵树分成d大小的连通块的块数,那么一个连通块要对应一个size被d整除的子树?形象化地理解一下,就好像一个一个连通块堆叠,每叠一次就对应一个新的最高点,正好是n/d块。堆叠也保证了整块连通。
那么只要跑一遍整棵树记下size,对于n的每个因子都扫一遍这个size数组,只要有n/d个size能被d整除就ans++。虽然也是n√n,但是常数变小了。
不过因为我懒得所以没有改原来的写法,底下的代码是常数大的那种:D
#include<iostream> #include<cstdio> using namespace std; int n,ans,k,flag; int siz[2000010],vis[2000010],prime[2000010]; int ver[4000010],Next[4000010],head[2000010],tot,tag[2000010],cnt; int read(){ char ch=getchar(); int val=0; for(;(ch<'0')||(ch>'9');ch=getchar()); for(;(ch>='0')&&(ch<='9');val=val*10+ch-'0',ch=getchar()); return val; } void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } int dfs(int x,int fa,int limit){ int num=0; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(y==fa)continue; num+=dfs(y,x,limit); if(flag)return 0; } if((num+1)==limit)return 0; else if(num<limit)return num+1; else{ flag=1; return 0; } } int main() { n=read(); for(int i=1,x,y;i<n;i++){ x=read(),y=read(); add(x,y),add(y,x); } if(n==1){ printf("1"); return 0; } for(int i=1;i*i<=n;i++){ if(n%i==0){ tag[++cnt]=i; if(i!=n/i)tag[++cnt]=n/i; } } if(cnt==2){ printf("2"); return 0; } else{ ans=2; for(int i=3;i<=cnt;i++){ flag=0; dfs(1,0,tag[i]); if(!flag)ans++; } } printf("%d",ans); return 0; }
后面围观的tkj神仙表示如果你把所有size扔进桶里,对于每个d找d,d*2,d*3的size是否存在,最后复杂度会变成nlogn
T2dinner:
我的玄学二分答案:主函数二分答案,check函数直接扫一遍断环成链的n*2的链,能和前面的t一起往一个菜单里塞就继续塞,不能就新开一个菜单。然后走到n以后的每一个位置都和n以前的位置作一下已用菜单数量的差,处理一下+1的细节,最后这个差小于等于m就合法。
正确性显然有问题!没有考虑整个菜单以外的零头情况。
然后这题有一大堆复杂度不对的AC做法,原因是数据过水…比如二分套二分,外面二分时间,里面枚举左端点以后二分地找同一张菜单能覆盖的最远范围,走到下一个位置,然后继续这样找到下一张菜单覆盖的范围…但是这样还是会T,要加一个剪枝,枚举左端点的时候记下已经扫过的左端点的t的和,如果这个和已经大于一张菜单能解决的时间【就是二分答案出来的x】,那么直接return 0回到主函数的二分。原因是,如果左端点扫过的这一段已经大于一张菜单了,那么这个开头对应当前区间的结尾一定强制使用一张菜单,就相当于从1开始做一遍左端点为1右端点为n的check…既然你头一次做这个check的时候就不满足答案,那么再来一次也显然…而如果左端点之前扫过的部分不满足一张菜单,我们仍然可以把当前左端点到右端点的部分看作可以把原来开头扔到后面去考虑能不能凑一张菜单的一个过程【你在说什么】。于是这样剪枝就能让代码跑得飞快地AC了XD
正解是倍增+二分答案。外面的二分不变,里面要先处理一个倍增数组,记录在当前答案下每一个点跳2的次方步最远能跳到哪里。然后枚举左端点,如果跳m步能跳过自己,那么就满足答案。
代码是前面的二分套二分
#include<iostream> #include<cstdio> using namespace std; int n,m; int t[200010],maxx,sum,ans,cnt[200010],b[200010],flag; int check(int x){ int num1=0; for(int i=1;i<=n;i++){ num1+=t[i]; if(num1>x)return 0; int j=i; int cnt1=0; flag=0; while(j<=i+n-1){ cnt1++; if(cnt1>m){ flag=1; break; } int l=j,r=i+n-1; int ans1=0; while(l<=r){ int mid=(l+r)/2; if(b[mid]-b[j-1]<=x){ ans1=mid; l=mid+1; } else r=mid-1; } j=ans1+1; } if(!flag&&cnt1<=m)return 1; } return 0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&t[i]); t[i+n]=t[i]; maxx=max(t[i],maxx); sum+=t[i]; } for(int i=1;i<=n*2;i++){ b[i]=b[i-1]+t[i]; } int l=maxx,r=sum; while(l<=r){ int mid=(l+r)/2; if(check(mid)){ ans=mid; r=mid-1; } else l=mid+1; } printf("%d",ans); return 0; }
T3chess:
这题乍一看是个简单bfs,但是仔细读题发现有大坑:只有走过的空格不同才算是不同路径。
于是果断推翻了自己没写多少的代码,折腾不出来正解所以打了个暴力dfs…居然还有20ptsXD
正解是缩边,把不需要的0边全都搞掉。出环一定不优,所以不用考虑边权为1的环。而对于边权为0的环或者链,直接把这个连通块揪出来,把能走到的空地都扔进vector里,然后把这些空地两两连边为1。因为走有敌军的地方不需要计算步数,所以通过敌军连通块能互相达到的点一定距离为1。
然后再对于每个点像它的八个方向正常连距离为1的边。最后最短路计数,跑bfs就可以。因为边权为1,加入队列的顺序就是距离顺序。
比较有意思的是这题坑了好多人一个下午,因为数据有锅。最后衡中那边的巨神们造了一些hack数据,把AC的人的代码都hack掉了。然后大约是std也被hack了吧,总之最后才换上正确数据。
#include<iostream> #include<cstdio> #include<vector> #include<cstring> #include<queue> using namespace std; int m,n,sx,sy,ex,ey,flag; int a[55][55],cnt,c[55][55],vis[3010],dis[3010]; long long f[3010]; int p[3010][3010]; int h[10]={0,-1,-2,-2,-1,1,2,2,1}; int l[10]={0,-2,-1,1,2,2,1,-1,-2}; vector<int>v; queue<int>q; void dfs(int x,int y){ vis[c[x][y]]=1; for(int i=1;i<=8;i++){ int x2=x+h[i],y2=y+l[i]; if(x2>0&&x2<=m&&y2>0&&y2<=n&&!vis[c[x2][y2]]){ vis[c[x2][y2]]=1; if(a[x2][y2]!=1&&a[x2][y2]!=2&&a[x2][y2]!=4){ v.push_back(c[x2][y2]); } else if(a[x2][y2]==1||a[x2][y2]==4){ if(a[x2][y2]==4)flag=1; dfs(x2,y2); } } } } int main() { scanf("%d%d",&m,&n); for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ scanf("%d",&a[i][j]); c[i][j]=++cnt; if(a[i][j]==3)sx=i,sy=j; if(a[i][j]==4)ex=i,ey=j; } } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(a[i][j]==1&&!vis[c[i][j]]){ flag=0; v.clear(); vis[c[i][j]]=1; dfs(i,j); for(int i=0;i<v.size();i++){ for(int j=0;j<v.size();j++){ p[v[i]][v[j]]=p[v[j]][v[i]]=1; } } if(flag){ for(int i=0;i<v.size();i++){ p[v[i]][c[ex][ey]]=p[c[ex][ey]][v[i]]=1; } } for(int i=0;i<v.size();i++){ vis[v[i]]=0; } } } } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(a[i][j]==1||a[i][j]==2)continue; for(int k=1;k<=8;k++){ int x2=i+h[k],y2=j+l[k]; if(x2>0&&x2<=m&&y2>0&&y2<=n){ if(a[x2][y2]!=1&&a[x2][y2]!=2){ p[c[i][j]][c[x2][y2]]=p[c[x2][y2]][c[i][j]]=1; } } } } } memset(vis,0,sizeof(vis)); memset(dis,0x3f3f3f3f,sizeof(dis)); dis[c[sx][sy]]=0; vis[c[sx][sy]]=1; f[c[sx][sy]]=1; q.push(c[sx][sy]); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(p[u][c[i][j]]&&u!=c[i][j]){ if(dis[c[i][j]]>dis[u]+1){ dis[c[i][j]]=dis[u]+1; f[c[i][j]]=f[u]; } else if(dis[c[i][j]]==dis[u]+1){ f[c[i][j]]+=f[u]; } if(!vis[c[i][j]]){ vis[c[i][j]]=1; q.push(c[i][j]); } } } } } if(dis[c[ex][ey]]<0x3f3f3f3f){ printf("%d\n%lld\n",dis[c[ex][ey]]-1,f[c[ex][ey]]); } else printf("-1\n"); return 0; }
马上又有新的考试了,大约再好好调整一下状态。其实对于这次的考试…幸运成分太大了,没有什么好说的,只会让我显得非常垃圾。
总的来说这套题还是挺考验思维的,感觉很有意思…自己其实能想到不少东西,大约还是有点进步的吧。