Noip模拟 Day6.12
第一题:贪吃蛇(snake)
本题其实就是判断一个有向图中有没有环,做一次拓扑排序就可以了,如果所有点都入队了,就表示没有环,否则就有环。或者就是dfs一次,每个点只需要被访问一次,这样也是O(n)的。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; #define N 1000010 struct edge { int next,to; }e[N<<1]; int head[N<<1]; int cnt; int n,m,T; int u,v; int flag; int vis[N<<1],f[N<<1]; void link(int x,int y) { e[++cnt]=(edge){head[x],y}; head[x]=cnt; } void dfs(int x,int i) { if (f[i]) { flag=1; return ; } if (vis[i]) return ; f[i]=1; vis[i]=1; int t=head[i]; while (t) { dfs(x,e[t].to); if (flag) break; t=e[t].next; } f[i]=0; } int main() { freopen("snake.in","r",stdin);freopen("snake.out","w",stdout); scanf("%d",&T); while (T--) { memset(f,0,sizeof(f)); memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); flag=0; scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) scanf("%d%d",&u,&v),link(u,v); for (int i=1;i<=n;i++) { if (!vis[i]) dfs(i,i); if (flag) break; } if (flag) printf("No\n"); else printf("Yes\n"); } return 0; }
第二题:营养计划(egg)
本题可以使用递推方法解决,应用堆积木的思想。
记f[i,j]为将i个鸡蛋分j天的方案总数
由于方案与顺序无关,那么每次考虑怎么处理给最少鸡蛋的那天,
给最少的那天再加上一个,每一天就都要加上一个;或者最少的那天不再加了,就继续将i个鸡蛋分j-1天即可。
即:f[i,j]=f[i-j,j]+f[i,j-1]
边界:f[i,0]=1
那么ans=f[n,m]
本题需要取模,因为加法是满足a mod mo+b mod mo=(a+b) mod mo的,所以直接加一下模一下就可以了。
#include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define MOD 19940714 #define N 2010 int n,m; int ans; int f[N<<1][N<<1]; int main() { freopen("egg.in","r",stdin);freopen("egg.out","w",stdout); scanf("%d%d",&n,&m); f[0][0]=1; for (int i=1;i<=m;i++) for (int j=1;j<=n;j++) f[i][j]=(f[i-1][j-1]+f[i][j-i])%MOD; for (int i=1;i<=m;i++) ans=(ans+f[i][n])%MOD; printf("%d\n",ans); return 0; }
第三题:购物狂人(shopping)
本题是简单数据结构的应用。
由于每个商店只会被算一次,而且本题中的操作是区间型的,那么可以考虑使用链表或类似并查集的方法来做到对所有操作均摊O(1)的时间复杂度。
类似并查集的方法,记f[i]为i这个商店所在的连续的这一段已被覆盖过的商店的最右边的那个,对于每次操作,将L~R这一段都并入R所在的集合即可。可是答案怎么统计呢?其实可以就用一个布尔数组存每个商店有没被覆盖过,在将指针扫过L~R时,顺便统计一下连续的若干段的平方和加进答案即可。
其实,本题也是可以用线段树做的,方法略微复杂一点
#include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; typedef long long LL; #define N 300010 LL n,m; LL l,r; LL ans; LL f[N],b[N]; int find(LL x) { return x==f[x] ? f[x] : f[x]=find(f[x]); } int main() { freopen("shopping.in","r",stdin);freopen("shopping.out","w",stdout); scanf("%lld%lld",&n,&m); for (int i=1;i<=n+1;i++) f[i]=i; for (int i=1;i<=m;i++) { scanf("%lld%lld",&l,&r); LL j=find(l),k=0; while (j<=r) { if (!b[j]) k++,b[j]=1,f[j]=r,j++; else ans+=(k*k),k=0,f[j]=r,j++; j=find(j); } ans+=(k*k); } printf("%lld",ans); return 0; }
第四题:精彩比赛(match)
本题的题目描述相当繁琐,但关键信息在规定2,要求取第一组的连续的几个人和第二组的连续几个人之间的所有比赛,如果记g[i,j]表示第一组的i号选手和第二组的j号选手之间的比赛关注度(没比赛就是0),容易想到本题其实就是求一个矩阵g的最大子矩阵。
至于最大子矩阵的求法,这里介绍一种O(n^3)的算法
记f[i,j,k]为前i行第j~k列的最大子矩阵(第i行必须选),s[i,j]=sigma(g[i,1]~g[i,j])
有f[i,j,k]=max{f[i-1,j,k],0}+s[i,k]-s[i,j-1]
那么ans=max{f[i,j,k]}
至于空间的话,可以使用滚动数组把空间复杂度优化到O(n^2)
#include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; typedef long long LL; #define N 310 int n,m; int x,y,w; LL ans; LL sum[N]; int g[N][N]; int main() { freopen("match.in","r",stdin);freopen("match.out","w",stdout); scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) scanf("%d%d%d",&x,&y,&w),g[x][y]+=w; for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) sum[j]=g[i][j]; for (int j=i+1;j<=n;j++) { for (int k=1;k<=n;k++) sum[k]+=g[j][k]; LL res=0; for (int k=1;k<=n;k++) { res+=sum[k]; if (res<0) res=0; else ans=max(ans,res); } } } printf("%lld\n",ans); }