今天的题的确水。T3还是一道NOIP原题。
嘛,多刷点水题也不是什么坏事嘛。
说来也快,夏令营结束了整一星期了呢。大家也都回到了日常的暑假生活呢。
今天学业水平测试出成绩了。。。嗯结果还算满意呢,至少达到了预期目标。
NOIP这边,还要继续努力啊。
还好,这次我找到我的代码了。每道题我都会把我当时写的代码和GTY哥哥提供的std都贴出来,方便大家批判我巨丑无比的代码(删
好了来说一下Day3的题解。
T1:十字架
题目描述
小 D 是虔诚的嘟嘟教徒。现在小 G 送他了一幅著名画家芬达奇的作品。这是一幅 n n 的作品,由”.” 或者”#” 构成,其中”.” 相当于空白。但是现在小 D 怀疑小 G 送给他了一幅赝品。正版芬达奇的画作,是由若干个互不重叠的十字架拼起来的。每个十字架由五个”#” 组成,如下:
.#.
###
.#.
而赝品则不能将所有的”#” 分成若干个互不重叠的十字架,如:
.#..
####
.#..
特别地,如果一幅画里面全都是”.”,这仍然是一幅正品。
你的任务是帮助小 D 判断,小 G 送他的这幅画到底是不是正品。
输入格式
输入文件第一行一个数字 n,含义如题目所述。
第 2 行到第 n + 1 行,每行 n 个’.’ 或者’#’,描述整张画。
输出格式
输出文件一行,如果是正品,输出”YES”;如果是赝品,输出”NO”。
样例输入 1
5
.#...
####.
.####
...#.
.....
样例输出 1
YES
样例输入 2
4
####
####
####
####
样例输出 2
NO
数据范围
40% 数据,1 ≤n≤10
70% 数据,1 ≤n ≤50
100% 数据,1≤n ≤100
名副其实的签到题。n最大就到100.暴力BFS就可以过。但我在考场上想的并不是BFS,注意到n只有最大100,可以考虑n地遍历画面的每一个格子,每遍历到一个#字符就判断以此为中心的四连块是不是都是#并且没有打过标记,如果是这样就把这些#都打上标记 ,代表我们找到了一个十字架。不过我可能写丑了,我只拿了60。。。。。。。。。。。。。
我退群吧.jpg
(据说这题只输出yes或者no就能拿一半分。。
正解并没有像我写的那样去给每个找到的十字架打标记,而是直接把它变成点,这样遍历完之后再判断这个图里头有没有#,要是还有的话那就是NO了。
先把我的60分傻逼做法挂出来。。
1 #include<iostream> 2 #include<queue> 3 #include<cstdio> 4 #include<cstring> 5 #define maxn 233 6 #define debug cout << "ok" << endl; 7 using namespace std; 8 char a[maxn][maxn]; 9 bool used[maxn][maxn]; 10 const int dx[] = {1,0,0,-1}; 11 const int dy[] = {0,1,-1,0}; 12 int n; 13 bool all_dot = true; 14 bool exist_remain_sharp = false; 15 bool check_in(int x,int y){ 16 if (x >=1 && x<=n && y>=1 && y <=n) 17 return true; 18 else 19 return false; 20 21 } 22 void n2_search(void){ 23 for (int i=1;i<=n;i++) 24 for (int j=1;j<=n;j++){ 25 if (a[i][j] == ‘.‘ || (a[i][j]==‘#‘ && used[i][j])) 26 continue; 27 else{ 28 bool now_check_fail = false; 29 for (int k=0;k<4;k++){ 30 if (!check_in(i+dx[k],j+dy[k]) && a[i+dx[k]][j+dy[k]] == ‘.‘) 31 now_check_fail = true; 32 33 } 34 if (!now_check_fail){ 35 used[i][j] = true; 36 for (int k = 0;k<4;k++) 37 used[i+dx[k]][j+dy[k]] =true; 38 } 39 } 40 } 41 } 42 int main(){ 43 44 freopen("puzzle.in","r",stdin); 45 freopen("puzzle.out","w",stdout); 46 scanf("%d",&n); 47 for (int i=0;i<maxn;i++) 48 for (int j=0;j<maxn;j++) 49 a[i][j] = ‘.‘; 50 for (int i=1;i<=n;i++) 51 for (int j=1;j<=n;j++){ 52 cin >> a[i][j]; 53 if (a[i][j]==‘#‘) 54 all_dot =false; 55 } 56 if (all_dot){ 57 printf("YES\n"); 58 return 0; 59 } 60 memset(used,false,sizeof(used)); 61 n2_search(); 62 for (int i=1;i<=n;i++) 63 for (int j=1;j<=n;j++){ 64 if (a[i][j] == ‘#‘ && !used[i][j]) 65 exist_remain_sharp = true; 66 67 } 68 69 if (exist_remain_sharp) 70 printf("NO\n"); 71 else 72 printf("YES\n"); 73 fclose(stdin); 74 fclose(stdout); 75 return 0; 76 77 }
然后是std:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 6 const int max_n = 110; 7 int n; 8 bool a[max_n][max_n]; 9 bool is_legal(int x, int y) 10 { 11 return 1 <= x && x <= n && 1 <= y && y <= n && a[x][y]; 12 } 13 14 int main() 15 { 16 freopen("puzzle.in", "r", stdin); 17 freopen("puzzle.out", "w", stdout); 18 cin >> n; 19 for (int i = 1; i <= n; i++) 20 for (int j = 1; j <= n; j++) 21 { 22 char c; 23 scanf(" %c", &c); 24 if (c == ‘#‘) a[i][j] = true; 25 } 26 27 bool is_ok = true; 28 for (int i = 1; i <= n; i++) 29 { 30 for (int j = 1; j <= n; j++) 31 if (a[i][j]) 32 { 33 if (is_legal(i + 1, j) && is_legal(i + 1, j - 1) && is_legal(i + 1, j + 1) && is_legal(i + 2, j)) 34 { 35 a[i][j] = false; 36 a[i + 1][j] = false; 37 a[i + 1][j - 1] = false; 38 a[i + 1][j + 1] = false; 39 a[i + 2][j] = false; 40 } 41 else 42 { 43 is_ok = false; 44 break; 45 } 46 } 47 if (!is_ok) break; 48 } 49 50 if (is_ok) cout << "YES" << endl; 51 else cout << "NO" << endl; 52 }
T2:搭积木
题目描述
小 Z 喜欢搭积木。小 Z 一共有 n 块积木,并且积木只能竖着一块一块的摞,可以摞多列。小 Z 的积木都是智能积木,第 i 块积木有一个情绪值 Xi。当摞在该积木上面积木总数超过 Xi 时,i 号积木就会不高兴。小 Z 情商这么高,肯定不希望有积木不高兴。但是他又希望每块积木都被用上,并且摞的积木列的总数最少。你能帮帮萌萌的小 Z 吗?
输入格式
输入文件第一行一个数字 n,含义如题目所述。
第 2 行一共 n 个数,第 i 个数为 Xi,含义如题目所述。
输出格式
输出一个数字,表示最小的积木列数目。
样例输入 1
3
0 0 10
样例输出 1
2
样例输入 2
4
0 0 0 0
样例输出 2
4
数据范围
30% 数据,1 ≤n ≤10
60% 数据,1≤ n ≤100
80% 数据,1≤ n ≤1000
100% 数据,1 ≤n≤ 5000
对于所有数据点,都有 Xi ≤ n。
“不要让任何一个盒子感到不高兴”。盒子的情绪值指的是在这个盒子上面最多能放多少盒子。我们可以理解为“某一个盒子的载重量”。
搜索是显然的。但只能拿部分分。
这道题可以考虑一个贪心思想。先把所有盒子按照载重量从小到大排序,然后我们依次从小到大考虑每个盒子。考虑一个一般的状态,现在我们有若干列盒子,我们每次拿到一个新的盒子,根据之前排序的策略,这个新的盒子应该是载重量比较大的,那么我们就应该考虑把这个盒子放在某一列的最下面,但我们不能随便放,为了后续策略也最优,我们肯定要把这个盒子放在能放的个数最多的那一列放。我们可以用一个数组记录在第i列上已经有了多少个盒子,每次取一个盒子,找到这一列中盒子数最多的那一列(前提是可以放下),把这个盒子放进去。如果都放不进去了,那就只能开一个新列。
细心的同学会问了,为什么不能从大到小考虑每个盒子然后从底向上放呢?
可以想一下,如果是从大到小考虑每个盒子的贪心策略,肯定是我先拿一个最大的盒子放在底部,然后不断地向上摞小盒子,每次找到一个小盒子我们就放到还能放最多盒子的可行的那一列,若都不行就多开一列。实际上这是不行的。因为每次能不能在某一列摞盒子会受到底部的影响,而我们如果只看顶部还有多少可以放,这就有可能会引起底部的盒子不高兴。比如说盒子序列3,1,1,0,0,我们首先在第一列放了3,然后我们有1,因为3上边可以放三个,我们把1放在3上。然后我们又有1,因为1上边可以放1个,我们把1放在1上。然后有0,因为1上边可以放一个,我们就可以把0放在1上吗?这显然不行的。这样做3会不高兴。
我在考场上的做法和正解类似。首先还是按照盒子的载重量从小到大排序,设立一个vis数组记录第i个盒子是不是被用过了,然后我们从1到n依次考虑每一个没有被用过的盒子,找到一个后,就把它放在新的一列。然后我们从这个盒子的下一个盒子到第n个盒子进行考虑,如果之后检索的盒子没有被使用并且载重量比当前高度大,那就可以放在当前列。所以我们要在循环里记录一下当前列的高度。每次开启新列的时候这个高度就应该被初始化为1。这个算法是和正解等价的。具体请参见代码。
std:
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 using namespace std; 5 6 const int max_n = 5e3 + 10; 7 int n, a[max_n], p[max_n], tot = 0; 8 9 inline int get_num() 10 { 11 int ans = 0; 12 bool flag = false; 13 char c; 14 while ((c = getchar()) == ‘ ‘ || c == ‘\n‘ || c == ‘\r‘); 15 if (isdigit(c)) ans = c - ‘0‘; 16 else flag = true; 17 while (isdigit(c = getchar())) ans = ans * 10 + c - ‘0‘; 18 return (flag ? -1 : 1) * ans; 19 } 20 21 inline void solve() 22 { 23 for (int i = 1; i <= n; i++) 24 if (!tot) 25 p[++tot] = 1; 26 else 27 { 28 int wh = 0, maxx = 0; 29 for (int j = 1; j <= tot; j++) 30 if (a[i] >= p[j] && p[j] > maxx) maxx = p[j], wh = j; 31 if (!wh) 32 p[++tot] = 1; 33 else 34 p[wh]++; 35 } 36 cout << tot << endl; 37 } 38 39 int main() 40 { 41 freopen("box.in", "r", stdin); 42 freopen("box.out", "w", stdout); 43 n = get_num(); 44 for (int i = 1; i <= n; i++) a[i] = get_num(); 45 sort(a + 1, a + 1 + n); 46 47 solve(); 48 }
怎么没法对齐了。。
我的做法:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define maxn 5005 6 using namespace std; 7 int n; 8 int a[maxn]; 9 int vis[maxn]; 10 int ans = 0,m =0; 11 12 13 int main(){ 14 freopen("box.in","r",stdin); 15 freopen("box.out","w",stdout); 16 scanf("%d",&n); 17 for (int i=1;i<=n;i++) 18 scanf("%d",&a[i]); 19 sort(a+1,a+n+1); 20 21 memset(vis,0,sizeof(vis)); 22 for (int i=1;i<=n;i++) 23 if (vis[i]==0){ 24 ans++; 25 m = 1; 26 vis[i] = 1; 27 for (int j = i+1;j<=n;j++) 28 if ((vis[j]==0) && (a[j]>=m)){ 29 vis[j] = 1; 30 m++; 31 } 32 } 33 printf("%d",ans); 34 fclose(stdin); 35 fclose(stdout); 36 }
T3:分宿舍
题目描述
A 校有着神奇的住宿制度,不分男女宿舍,所有 n 个学生被统一分到两栋宿舍楼中。作为年轻人,学生之间心生爱慕之情是很正常。我们用爱慕值来表示两名学生之间的爱慕程度,如果两名爱慕值为 c 的学生被安排在同一宿舍楼,他们或她们便会在一起,并造成影响力为 c 的早恋事件。
每年年末,身为政教处主任的你会将所有早恋事件按照影响力从大到小排成一个列表,然后上报给校长。公务繁忙的校长只会去看列表中第一个事件的影响力,如果影响很大,他会考虑撤换政教处主任。
在详细考察了 n 个学生之间的爱慕关系后,你觉得压力很大。你要合理的将学生们分到两栋宿舍,以求产生的早恋事件影响力都比较小,以保住自己的官职。假设只要处于同一栋宿舍楼的两个人之间有爱慕关系,他们就一定会在这年的某个时候在一起。
那么,要怎么分配,才能让校长看到的那个早恋事件的影响力最小呢?这个最小值是多少?
输入格式
第一行两个整数 n 和 m,分别表示学生的数目和爱慕关系的对数。
接下来 m 行,每行为 3 个正整数 ai,bi,ci,表示学生 ai 和 bi 之间有爱慕关系,爱慕值为 ci。
数据保证 1 aibin,0 < ci ≤109,且每对爱慕关系只出现一次。
输出格式
输出一个数,为通过合理安排,校长看到的那个早恋事件的最小影响力。如果没有发生早恋事件,输出 0。
样例输入
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
样例输出
3512
数据范围
对于 30% 的数据,n≤15。
对于 70% 的数据,n≤2000,m≤50000。
对于 100% 的数据,n≤20000,m≤100000。
NOIP2010的关押罪犯。。。。
这题要求最大的早恋事件影响力最小,那么很显然我们要考虑尽量把搞事情影响力比较大的俩人拆到两个宿舍里。我们首先对这些搞事情的度排序,然后去模拟着把搞事情度最高的两个人分到不同的宿舍里,只要在拆开两个人的时候发现和已有的拆分相矛盾,那么这肯定就是最小的影响力最大事件了。
这要怎么实现呢?并查集!
这里并查集的应用比较巧妙。如果设有n个人,那么我们的并查集应该开到2n大小。就是网上题解所谓的“补集法”。i表示第i个学生,它的补集i+n表示不能和i在同一个宿舍的人。然后我们每次读入a和b的搞事情程度为c,就把a和b+n所在集合合并,a+n与b所在集合合并。
要注意,每次合并之前都要判断ab是不是在同一集合,如果是,那么这就是最小的最大冲突。
还有两种做法。一种是用「加权并查集」,每个节点额外存储一个type,值为0表示和父亲节点在一个宿舍楼,1则不是。有兴趣的读者可以尝试一下。
第二种则是用二分答案+DFS。
在考场上我这题写炸了。。。只拿了10分。。本来该A掉的QAQ
那就只贴std吧。。
1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #include <iostream> 5 using namespace std; 6 7 const int max_n = 20000 + 10; 8 const int max_m = 2e5 + 10; 9 const int inf = 1e9; 10 11 int prison[max_n]; 12 int point[max_n], nxt[max_m], v[max_m], c[max_m]; 13 int tot = 0; 14 int n, m; 15 int ANS; 16 17 inline void add_edge(int x, int y, int cc) 18 { 19 tot++; nxt[tot] = point[x]; point[x] = tot; v[tot] = y; c[tot] = cc; 20 tot++; nxt[tot] = point[y]; point[y] = tot; v[tot] = x; c[tot] = cc; 21 } 22 23 bool dfs(int now, int color) 24 { 25 prison[now] = color; 26 for (int i = point[now]; i; i = nxt[i]) 27 if (c[i] > ANS) 28 { 29 if (prison[v[i]] == -1) 30 { 31 if (!dfs(v[i], color ^ 1)) return false; 32 } 33 else 34 if (prison[v[i]] == color) return false; 35 } 36 return true; 37 } 38 39 inline bool check() 40 { 41 for (int i = 1; i <= n; i++) prison[i] = -1; 42 for (int i = 1; i <= n; i++) 43 if (prison[i] == -1) 44 if (!dfs(i, 0)) 45 return false; 46 return true; 47 } 48 49 inline int getnum() 50 { 51 int ans = 0; 52 bool flag = false; char c; 53 while ((c = getchar()) == ‘ ‘ || c == ‘\n‘ || c == ‘\r‘); 54 if (c == ‘-‘) flag = true; else ans = c - ‘0‘; 55 while (isdigit(c = getchar())) ans = ans * 10 + c - ‘0‘; 56 return (flag ? -1 : 1) * ans; 57 } 58 59 int main() 60 { 61 freopen("love.in", "r", stdin); 62 freopen("love.out", "w", stdout); 63 n = getnum(); m = getnum(); 64 for (int i = 1; i <= m; i++) 65 { 66 int x, y, cc; 67 x = getnum(); y = getnum(); cc = getnum(); 68 add_edge(x, y, cc); 69 } 70 int l = 0, r = inf; 71 while (l < r) 72 { 73 int mid = (l + r) >> 1; 74 ANS = mid; 75 if (check()) 76 r = mid; 77 else 78 l = mid + 1; 79 } 80 cout << l << endl; 81 }
题目描述
A 校有着神奇的住宿制度,不分男女宿舍,所有 n 个学生被统一分到两栋宿舍楼中。作为年轻人,学生之间心生爱慕之情是很正常。我们用爱慕值来表示两名学生之间的爱慕程度,如果两名爱慕值为 c 的学生被安排在同一宿舍楼,他们或她们便会在一起,并造成影响力为 c 的早恋事件。
每年年末,身为政教处主任的你会将所有早恋事件按照影响力从大到小排成一个列表,然后上报给校长。公务繁忙的校长只会去看列表中第一个事件的影响力,如果影响很大,他会考虑撤换政教处主任。
在详细考察了 n 个学生之间的爱慕关系后,你觉得压力很大。你要合理的将学生们分到两栋宿舍,以求产生的早恋事件影响力都比较小,以保住自己的官职。假设只要处于同一栋宿舍楼的两个人之间有爱慕关系,他们就一定会在这年的某个时候在一起。
那么,要怎么分配,才能让校长看到的那个早恋事件的影响力最小呢?这个最小值是多少?
输入格式
第一行两个整数 n 和 m,分别表示学生的数目和爱慕关系的对数。
接下来 m 行,每行为 3 个正整数 ai,bi,ci,表示学生 ai 和 bi 之间有爱慕关系,爱慕值为 ci。
数据保证 1aibin,0 < ci109,且每对爱慕关系只出现一次。
输出格式
输出一个数,为通过合理安排,校长看到的那个早恋事件的最小影响力。如果没有发生早恋事件,输出 0。