第一题:贪吃蛇(snake)
【题目描述】
哲哲迷上了一个非常有意思的游戏,这个游戏的内容就是通过上下左右来操作一条蛇的前进,比方说,你现在正在向上走,而你按了一下右,那么这条蛇就会转向右走,很有趣吧!这个游戏的名字叫做贪吃蛇。但是,这个看起来简单的游戏也挺需要操作的,如果你不小心撞到了墙,或是撞到自己的身体的话,你就输了。
可是,哲哲由于手指灵活度不够,经常撞墙而死,所以,他自己设计了一个没有墙的贪吃蛇地图,地图可以表示成若干个点,而你可以操作蛇头从某一个一个点到它相邻的点上。这样,游戏的难度降低了许多,可是哲哲还是有可能挂掉的,因为他可能一不小心吃到自己。
现在,你得到了一些哲哲设计好的地图,哲哲希望你帮他看看他有没有可能一不小心碰到到自己的身体而导致游戏失败,为了简便,这里我们假设哲哲的蛇已经非常长了,长度可以视为无穷大。哲哲可以选任意点作为起点。
【输入数据】
第一行为一个数:t,表示哲哲设计好了t个地图
对于接下来的每一个地图,开头的为两个数:n,m,表示图中有n个点,编号为1~n,m条有向边
接下来m行,每行两个数x,y,表示哲哲可以将蛇从x点移动到y点
【输出数据】
对于每个地图,输出Yes或No,Yes表示哲哲不可能吃到自己,No表示哲哲有吃到自己危险
【输入样例】
2
3 2
1 2
2 3
3 3
1 2
2 3
3 1
【输出样例】
Yes
No
【数据约定】
对于30%的数据,n<=100
对于100%的数据,n<=20000,m<=100000,t<=20,没有自环,允许重边
正解:tarjan判环
解题报告:
第一眼看到这道题,果断上dfs,但是越想越不对,感觉会wa,而且复杂度还很gi。。。于是换tarjan判环,这样就不虚了。
考完试才知道dfs其实是可以的。。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cmath> 5 #include<cstring> 6 #include<algorithm> 7 #include<vector> 8 using namespace std; 9 const int MAXN = 20011; 10 const int MAXM = 200011; 11 int n,m; 12 int total; 13 int dfn[MAXN]; 14 int low[MAXN]; 15 int Stack[MAXN*5]; 16 int zhan; 17 bool pd[MAXN]; 18 int first[MAXN],to[MAXM],next[MAXM]; 19 int ecnt; 20 bool ok; 21 22 inline int getint() 23 { 24 int w=0,q=0; 25 char c=getchar(); 26 while((c<'0' || c>'9') && c!='-') c=getchar(); 27 if (c=='-') q=1, c=getchar(); 28 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 29 return q ? -w : w; 30 } 31 32 inline void tarjan(int x){ 33 dfn[x]=low[x]=++total; 34 Stack[++zhan]=x; 35 pd[x]=1; 36 for(int i=first[x];i;i=next[i]) { 37 int next=to[i]; 38 if(!dfn[next]) { 39 tarjan(next); 40 low[x]=min(low[x],low[next]); 41 } 42 else if(pd[next]) { 43 low[x]=min(low[x],dfn[next]); 44 } 45 } 46 47 if(dfn[x]==low[x]) { 48 if(Stack[zhan]!=x) { ok=false; return ; } 49 pd[Stack[zhan]]=0; 50 zhan--; 51 } 52 } 53 54 inline void init(){ 55 memset(first,0,sizeof(first)); ecnt=0; 56 ok=true; 57 int x,y; 58 for(int i=1;i<=m;i++) { 59 x=getint(); y=getint(); 60 next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; 61 } 62 memset(dfn,0,sizeof(dfn)); 63 memset(low,0,sizeof(low)); 64 memset(Stack,0,sizeof(Stack)); 65 total=0; zhan=0; 66 memset(pd,0,sizeof(pd)); 67 } 68 69 inline void solve(){ 70 int T=getint(); 71 while(T--) { 72 n=getint();m=getint(); 73 init(); 74 for(int i=1;i<=n;i++) if(!dfn[i]) { tarjan(i); if(!ok) break; } 75 if(ok) printf("Yes "); 76 else printf("No "); 77 } 78 } 79 80 int main() 81 { 82 solve(); 83 return 0; 84 }
第二题:营养计划(egg)
【题目描述】
哲哲最近因为醉心于学习中,饮食不太注意,导致身体消瘦,哲哲的妈妈为此深感担忧。为了给哲哲补充点营养,众所周知,鸡蛋这种东西营养丰富,而且更重要的是哲哲也很喜欢吃,鸡蛋不仅含有蛋白质、脂肪、卵黄素、卵磷脂、维生素和铁、钙、钾等人体所需要的矿物质,还含有自然界中最优良的蛋白质。哲哲的妈妈给哲哲制定了一个详细的营养计划,具体内容就是每天吃几个鸡蛋(当然有时候也可能一天不吃一个)。
为了更简洁地描述这个问题,这里我们先假设哲哲的胃口无限大,哲哲的妈妈一共准备了n个鸡蛋,这个营养计划持续m天,计划安排哲哲每天得吃ai个鸡蛋(0<=ai<=n),你需要求出所以方案总数。需要注意的是,方案中的鸡蛋数与顺序无关,比方说如果当n=8时,1,3,4和3,1,4,和4,1,3等方案会被视为同一个方案。需要注意的是,鸡蛋最终必须被吃完。
【输入数据】
第一行,包含两个数n,m
【输出数据】
仅一行,包含一个数ans,即方案总数,由于方案数可能非常大,而哲哲看到大数字就头晕,所以你只需要输出ans mod 19940714即可
【输入样例】
7 3
【输出样例】
8
【数据约定】
对于20%的数据,n,m<=10
对于60%的数据,n,m<=200
对于100%的数据,n,m<=2000
正解:递推(我是打表的)
解题报告:
看到这道题马上就激动了,北大夏令营第三试最后一题,样例都一模一样,然而这道题数据大多了。。。毕竟那道题是送肉题。
没办法,感觉想不出DP,不能保证方案不重复。打了个暴力了事。
等到其他题目切不动的时候又回来做这道题,打了个表,强上,看规律。
盯了半个小时之后终于看出了规律!!!
表:
1 1 1 1 1 1 1 1 1 1 1
1 2 2 3 3 4 4 5 5 6 6
1 2 3 4 5 7 8 10 12 14 16
1 2 3 5 6 9 11 15 18 23 27
1 2 3 5 7 10 13 18 23 30 37
1 2 3 5 7 11 14 20 26 35 44
1 2 3 5 7 11 15 21 28 38 49
1 2 3 5 7 11 15 22 29 40 52
1 2 3 5 7 11 15 22 30 41 54
1 2 3 5 7 11 15 22 30 42 55
1 2 3 5 7 11 15 22 30 42 56
自左往右是鸡蛋的个数,自上往下是天数。这规律看得我也是。。。
注意到第i行的第i个数开始出现与上一行不同的(加了一),而后面的数每一个都比上一行大一个特定的值。第j列等于上一行的第j列加上这一行的第(j-i)列的数。
打表找规律这种东西真的看人品。。。于是就这么AC了,显然是有科学解释的。。。
solution的解释:(感觉还是打表好理解)
本题可以使用递推方法解决,应用堆积木的思想。
记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]
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 2016; 21 const int MOD = 19940714; 22 int f[MAXN][MAXN]; 23 int n,m; 24 25 inline int getint() 26 { 27 int w=0,q=0; 28 char c=getchar(); 29 while((c<'0' || c>'9') && c!='-') c=getchar(); 30 if (c=='-') q=1, c=getchar(); 31 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 32 return q ? -w : w; 33 } 34 35 inline void solve(){ 36 m=getint(); n=getint(); 37 38 for(int i=1;i<=m;i++) f[1][i]=1; 39 40 for(int i=2;i<=n;i++) { 41 for(int j=1;j<=m;j++) { 42 f[i][j]=f[i-1][j]; 43 if(j==i) f[i][i]++; 44 else if(j>i) { 45 f[i][j]+=f[i][j-i]; 46 } 47 if(f[i][j]>=MOD) f[i][j]%=MOD; 48 } 49 } 50 51 printf("%d",f[n][m]%MOD); 52 } 53 54 int main() 55 { 56 solve(); 57 return 0; 58 }
第三题:购物狂人(shopping)
【题目描述】
哲哲喜欢购物,他尤其喜欢那种横扫一片商店的快感。最近,他打算对D地的商店实行他疯狂的购物计划。D地的商业区就是一条街,这条街上有n个商店,哲哲打算进攻m次,每次扫荡第Li~Ri个商店,哲哲会把他经过的每个商店扫荡一空(换句话说,就是一个商店不会被算两次),因为连续地扫一片商店是很爽的,所以哲哲把一次扫荡的Happy值定义为所有连续的一段这次扫空的商店数的平方的和,已被扫空的不再计算,
如图:
现在你不经意间得知了哲哲的购物计划,而你需要求出哲哲获得的Happy值之和
【输入数据】
第一行为n和m,意义如描述之所示
接下来m行,每行两个数
Li Ri 表示哲哲第i次行动要扫荡第Li到第Ri个商店
【输出数据】
一行,包含一个数即为哲哲所获得的Happy值之和
【输入样例】
14 2
4 9
2 12
【输出样例】
49
【数据约定】
对于40%的数据,n,m<=1000
对于70%的数据,n,m<=50000
对于100%的数据,n,m<=300000,Li<=Ri
正解:线段树 or 链表 or 并查集
解题报告:
考场上我想出来一种set的神奇做法,感觉很有道理,不敢打。结果居然有这么多种解法。
链表、并查集的话好说,每次处理一个[l,r]的区间,做的时候,就把这一段区间所有尚未被取的结点的father指向r,然后路径压缩,指向最右边的部分。
这样的话均摊一下,是O(1)的,总时间复杂度O(n)
线段树的话多一个log,不过好想到一些,毕竟是区间类的题目。%%%小胖犇考场上想出这道题。
线段树也有两种打法:
第一种的话,就是维护这个区间的目前被取之后的对答案的贡献,还有从区间左往右可以拓展多少格(延伸),从右往左也维护一下。然后每次做的时候一边查询一边修改就可以了。
第二种的话就比较神了:每次操作对于未被覆盖的区间打标记,这样的话就很方便了,维护的东西也很少,最后统计一下即可。
并查集的做法:
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 300011; 21 int n,m; 22 LL ans; 23 int father[MAXN]; 24 bool pd[MAXN]; 25 26 inline int getint() 27 { 28 int w=0,q=0; 29 char c=getchar(); 30 while((c<'0' || c>'9') && c!='-') c=getchar(); 31 if (c=='-') q=1, c=getchar(); 32 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 33 return q ? -w : w; 34 } 35 36 inline int find(int x){ 37 if(father[x]!=x) father[x]=find(father[x]); 38 return father[x]; 39 } 40 41 inline void solve(){ 42 n=getint(); m=getint(); 43 44 for(int i=1;i<=n+1;i++) father[i]=i; 45 46 int l,r; 47 for(int i=1;i<=m;i++){ 48 l=getint(); r=getint(); 49 int now=l; 50 LL k=0; 51 while(now<=r) { 52 if(!pd[now]) { 53 k++; pd[now]=1; father[now]=r; now++; 54 } 55 else { 56 ans+=k*k; father[now]=r; k=0; now++; 57 } 58 now=find(now); 59 } 60 ans+=k*k; 61 } 62 63 printf(OT,ans); 64 65 } 66 67 int main() 68 { 69 solve(); 70 return 0; 71 }
线段树做法:
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 300011; 21 int n,m; 22 int ql,qr; 23 LL ans; 24 int tr[MAXN*4]; 25 int total; 26 LL ljh[MAXN]; 27 28 inline int getint() 29 { 30 int w=0,q=0; 31 char c=getchar(); 32 while((c<'0' || c>'9') && c!='-') c=getchar(); 33 if (c=='-') q=1, c=getchar(); 34 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 35 return q ? -w : w; 36 } 37 38 inline void insert(int root,int l,int r){ 39 if(tr[root]) return ;//已经被覆盖,直接return 40 if(ql<=l && qr>=r) { tr[root]=total; return ; } 41 int mid=l+(r-l)/2; 42 int lc=root*2,rc=lc+1; 43 if(ql<=mid) insert(lc,l,mid); 44 if(qr>mid) insert(rc,mid+1,r); 45 } 46 47 inline void pushdown(int root,int l,int r){ 48 if(l==r) { ljh[l]=tr[root]; return ; } 49 int mid=l+(r-l)/2; 50 int lc=root*2,rc=lc+1; 51 if(!tr[lc]) tr[lc]=tr[root]; 52 if(!tr[rc]) tr[rc]=tr[root]; 53 pushdown(lc,l,mid); pushdown(rc,mid+1,r); 54 } 55 56 inline void solve(){ 57 n=getint(); m=getint(); 58 59 for(int i=1;i<=m;i++) { 60 ql=getint(); qr=getint(); 61 total++; 62 insert(1,1,n); 63 } 64 65 pushdown(1,1,n); 66 LL len=0; 67 for(int i=1;i<=n+1;i++) { 68 if(ljh[i]!=ljh[i-1]) { 69 if(ljh[i-1]) ans+=len*len; 70 len=1; 71 } 72 else{ 73 len++; 74 } 75 } 76 77 printf(OT,ans); 78 } 79 80 int main() 81 { 82 solve(); 83 return 0; 84 }
第四题:精彩比赛(match)
【题目描述】
D市最近要举办一场盛况空前的比赛,D市市民对此比赛都十分关心。这一系列比赛将所有选手分成了两组,每组有n人,比赛是在两个分属两组的选手之间进行的,主办方在这些人之中安排了m场比赛,由于每个人的实力不尽相同,市民们对每个人的关注度也是不尽相同的,自然的,市民对于在不同的人之间举行的比赛的关注度也不尽相同,而且可能有的比赛对观众完全没有吸引力。而身为主办方的哲哲自然希望在QZTV的黄金时段转播受关注度高的比赛,以获得较高的收视率。
但实际上转播比赛是要受日程的限制的,尽管哲哲很想将最精彩的比赛呈现给大家,但他的上司给他做出了如下规定:
1. 比赛是按第一组选手的编号顺序从1~n进行的
2. 主办方已经预先给两组选手按实力编了号,为了不使比赛双方的选手实力相差过大,被选出来转播的比赛只能是第一组的编号为L1~R1的和第二组的编号L2~R2的选手之间所进行的所有比赛(这里的L1,R1,L2,R2均可由哲哲自己来选定,当然,也可以一场都不播)
哲哲希望在满足上司规定的条件下安排若干场在黄金时段转播的比赛,使得这些比赛的关注度之和最大
【输入数据】
第一行两个数,为n,m,意义见描述
接下来m行,每行三个数
xi,yi,zi,表示在第一组的xi号和第二组的yi号选手之间安排了一场比赛,关注度为zi
需要注意的是,zi可能为负数,即这场比赛相当难看,播出了反而会降低收视率
【输出数据】
一行,仅一个数,即最大的选出的比赛关注度之和
【输入样例】
3 5
1 3 2
2 3 -3
2 2 3
3 2 -1
3 1 2
【输出样例】
4
【数据约定】
对于20%的数据,n<=10
对于50%的数据,n<=80
对于100%的数据,n<=300,m<=n^2,|zi|<=50000
正解:最大子矩阵和
解题报告:
考场上居然没抽象出最大子矩阵和的模型,真是。。。
就是一道模板题,每次枚举矩阵的上界和下界,然后记一下每竖行的前缀和,用最大子段和的方法求一下当前的最大子矩阵和。代码里面还是讲的很清楚了。。。
调了好久,一直都wa一个点,结果发现可能有重边,所以是会有问题的。。。加上去就好了。
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 311; 21 int n,m; 22 LL ans; 23 LL w[MAXN][MAXN]; 24 LL dp[MAXN];//dp[i]表示第i竖行的当前前缀和 25 26 inline int getint() 27 { 28 int w=0,q=0; 29 char c=getchar(); 30 while((c<'0' || c>'9') && c!='-') c=getchar(); 31 if (c=='-') q=1, c=getchar(); 32 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 33 return q ? -w : w; 34 } 35 36 inline LL getmax(){ 37 LL now=0; 38 for(int i=1;i<=n;i++) { 39 if(now>0) now+=dp[i]; 40 else now=dp[i]; 41 if(now>ans) ans=now; 42 } 43 return now; 44 } 45 46 inline void solve(){ 47 n=getint(); m=getint(); 48 int x,y,z; 49 for(int i=1;i<=m;i++) { 50 x=getint(); y=getint(); z=getint(); 51 w[x][y]+=z; 52 } 53 54 for(int i=1;i<=n;i++) { 55 memset(dp,0,sizeof(dp)); 56 for(int j=i;j<=n;j++) { 57 for(int k=1;k<=n;k++) { 58 dp[k]+=w[j][k]; 59 } 60 LL now=getmax(); 61 if(now>ans) ans=now; 62 } 63 } 64 65 printf(OT,ans); 66 67 } 68 69 int main() 70 { 71 solve(); 72 return 0; 73 }