2019.8.22 TEST
Posted ljb666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2019.8.22 TEST相关的知识,希望对你有一定的参考价值。
回学校后的第一次测试,考的非常差,三道题都还是比较有一些思维难度的。算法都远远在noip的范围内。
T1 count
这道题最开始一看似乎是找规律的题,首先都至少有两种选法。这些被切掉的块都必须是n的因数,手推了几个例子似乎找到了一些规律,可实际上确是wa完了。
其实就是对子树的统计,如果我们要选一个点,必须要把这个点的子树全部选完,用反证法可以证明的,如果没有把子树选完,剩下的部分构成的块一定不符合要求。子树的割法是唯一的。
dfs就可以了,另外还要预处理出n的因数,2-sqrt(n)。然后dfs的时候判断是否有满足条件的子树,子树大小必须是n的约数,如果不够就将子树和向上累加,找到了满足条件的子树就把那一整块的size赋为0。最后看根节点的size是否为0,一旦为0的话,方案就是成立的。
代码如下:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; struct node int nxt,to; edge[maxn*2]; int head[maxn],cnt; inline int R() int a=0;char c=getchar(); while(c>‘9‘||c<‘0‘)c=getchar(); while(c>=‘0‘&&c<=‘9‘)a=a*10+c-‘0‘,c=getchar(); return a; void add(int x,int y) edge[++cnt].nxt=head[x]; edge[cnt].to=y; head[x]=cnt; int fa[maxn],size[maxn]; int n,x,y,tot,ans,ljb; int di[maxn]; bool flag; void dfs1(int x,int f) fa[x]=f; for(register int i=head[x];i;i=edge[i].nxt) int v=edge[i].to; if(v==fa[x]) continue; dfs1(v,x); void dfs2(int now,int num) size[now]=1; for(register int i=head[now];i;i=edge[i].nxt) int v=edge[i].to; if(v==fa[now]) continue; dfs2(v,num); size[now]+=size[v]; if(size[now]>num) return; if(size[now]==num) size[now]=0;//如果有一个num的子树 直接赋为0 vector<int> sb;//用vector储存要好一点。 void divide(int x) for(register int i=2;i<=sqrt(x);i++) if(x%i==0) if(i*i==x) sb.push_back(i); else sb.push_back(i),sb.push_back(x/i); int main() n=R(); for(register int i=1;i<n;i++) x=R();y=R(); add(x,y);add(y,x); dfs1(1,0); divide(n); for(register int i=0;i<sb.size();i++) dfs2(1,sb[i]); if(!size[1]) ans++;//1为根,最后所有子树必须被选完 printf("%d\\n",ans+2); return 0;
T2 dinner
最开始看这道题的时候以为是dp,不过发现状态很难转移,数据范围也不允许。
又觉得可以二分答案,将所有人划分为m段,以为是像简单的数列分段那样,和的最大值最小,但发现样例都过不了。
下来评讲的时候听到一堆二分套二分,倍增的神仙解法。
其实这道题就可以看作把一个圆分成m段,使每段的和的最大值最小。
关于圆的套路便是破环成链,将原来的数组复制一份即可。
而我们这样的话可以从任意一个人为起点开始,所以二分答案check的时候还要枚举以每个人为起点的情况。
check的时候有一个小剪枝,tot统计从1开始的数的和,超过了二分的mid就直接break,表示这是真的非常神奇。不然直接100->60。
虽然复杂度达到了n^2logn。不过比什么二分套二分之类的好理解多了。在学校的机子上也可以A。
//一句话题意,就是一个圆划分成m段每段和最大值最小 //以下是最好理解的一种做法 #include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; int n,m; int t[maxn]; int sum[maxn]; int maxx; int l,r; int ans; bool check(int x) int tot=0; for(int i=1;i<=n;i++) tot+=t[i]; if(tot>x) break;//当前的和已经超出了答案,相当于一个小小的剪枝,不加这两句就是60。 int tmp=0,cnt=1; for(int j=i;j<i+n;j++)//枚举区间还可以从哪个点开始 tmp+=t[j]; if(tmp>x) tmp=t[j]; cnt++; if(cnt<=m) return true; return false; int main() scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&t[i]);//破环为链 maxx=max(maxx,t[i]); t[i+n]=t[i]; r+=t[i]; l=maxx;//二分边界,至少都是数列中的最大值 while(l<=r) int mid=(l+r)>>1; if(check(mid)) ans=mid; r=mid-1; else l=mid+1; printf("%d\\n",ans); return 0;
T3 chess
一开始是真的脑残,以为直接就是两遍dfs的事情,但这样时间复杂度极高,但发现两个样例都是秒过,还是可以得部分分。结果交上去只有10分。而洛谷数据水还可以骗51分。
sb代码:
#include<bits/stdc++.h> using namespace std; const int N=550; int n,m; int ditu[N][N]; int stx,edx,sty,edy; long long minn; bool used[N][N]; int dx[]=0,1,1,-1,-1,2,2,-2,-2; int dy[]=0,2,-2,2,-2,1,-1,1,-1; bool flag; long long tot; void dfs1(int x,int y,long long bs) if(x==edx&&y==edy) flag=true; if(bs<minn) minn=bs; return; if(bs>minn) return; if(ditu[x][y]==2) return; for(int i=1;i<=8;i++) int xx=x+dx[i]; int yy=y+dy[i]; if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!used[xx][yy]) used[xx][yy]=true; if(ditu[xx][yy]==1||ditu[xx][yy]==4) dfs1(xx,yy,bs); if(ditu[xx][yy]==0) dfs1(xx,yy,bs+1); used[xx][yy]=false; void dfs2(int x,int y,long long bs) if(x==edx&&y==edy&&bs==minn) tot++; return; if(bs>minn) return; if(ditu[x][y]==2) return; for(int i=1;i<=8;i++) int xx=x+dx[i]; int yy=y+dy[i]; if(xx>=1&&xx<=n&&y>=1&&y<=m&&!used[xx][yy]) used[xx][yy]=true; if(ditu[xx][yy]==1||ditu[xx][yy]==4) dfs2(xx,yy,bs); if(ditu[xx][yy]==0) dfs2(xx,yy,bs+1); used[xx][yy]=false; int main() scanf("%d%d",&n,&m); minn=0x7fffffff; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&ditu[i][j]); if(ditu[i][j]==3) stx=i,sty=j; if(ditu[i][j]==4) edx=i,edy=j; dfs1(stx,sty,0); dfs2(stx,sty,0); if(!flag) printf("-1\\n"); return 0; printf("%lld\\n",minn); printf("%lld\\n",tot); return 0;
最后讲评的时候发现可以转化为最短路的模型,然后方案数就是最短路计数。对于所有的0号节点(包括对方的帅及初始起点)建单向边,因为你只能跳过去,不能再回来。建边的问题就一眼看出来了,将所有的空格点都连上一条边权为1的边,遇上友军就不连,相当于是连一条边权为0的边,那些敌军也不要管。最后直接跑dijkstra+最短路计数即可。
关于最短路计数,就是dp,此处不再展开,推荐两道题:
代码如下:
//这种图论建模的思想一定要学会。 #include<bits/stdc++.h> using namespace std; const int maxn=1e6+7; const int inf=0x7f7f7f; const int N=550; struct node int nxt,to,val; edge[maxn*3]; int head[maxn],cnt; int n,m; long long dp[maxn]; void add(int x,int y,int v) edge[++cnt].nxt=head[x]; edge[cnt].to=y; edge[cnt].val=v; head[x]=cnt; bool vis[maxn]; long long dis[maxn]; int hash[N][N]; int ditu[N][N]; int dx[]=0,1,1,-1,-1,2,2,-2,-2; int dy[]=0,2,-2,2,-2,1,-1,1,-1; int stx,sty,edx,edy; int st,ed; int used[N][N]; priority_queue< pair<long long ,int> >q; void dfs(int u,int x,int y) used[x][y]=true; for(int i=1;i<=8;i++) int xx=x+dx[i]; int yy=y+dy[i]; if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!used[xx][yy]) if(ditu[xx][yy]==1) dfs(u,xx,yy);//继续搜 else used[xx][yy]=true; add(u,hash[xx][yy],1);//所有空格连边 void dijkstra(int x) for(int i=1;i<=n*m;i++) dis[i]=inf; memset(vis,false,sizeof(vis)); q.push(make_pair(0,x)); dis[x]=0; dp[x]=1; //vis[x]=true;//dijkstra这里不用写!! while(q.size()) int u=q.top().second; q.pop(); if(vis[u]) continue; vis[u]=true; for(int i=head[u];i;i=edge[i].nxt) int v=edge[i].to; if(dis[v]>dis[u]+edge[i].val) dp[v]=dp[u]; dis[v]=dis[u]+edge[i].val; q.push(make_pair(-dis[v],v)); else if(dis[v]==dis[u]+edge[i].val) dp[v]+=dp[u]; int main() freopen("chess.in","r",stdin); freopen("chess.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) hash[i][j]=(i-1)*m+j;//每个点坐标hash成序号 for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&ditu[i][j]); if(ditu[i][j]==3) stx=i,sty=j,st=hash[i][j]; if(ditu[i][j]==4) edx=i,edy=j,ed=hash[i][j]; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(ditu[i][j]==0||ditu[i][j]==3)//空格以及起点与所有点连边,连有向边 memset(used,0,sizeof(used)); dfs(hash[i][j],i,j);//处理每一个点的连边 dijkstra(st); if(dis[ed]<inf) printf("%lld\\n",dis[ed]-1);//走到敌军阵营不消耗步数 printf("%lld\\n",dp[ed]); else printf("-1\\n"); return 0;
以上是关于2019.8.22 TEST的主要内容,如果未能解决你的问题,请参考以下文章