CSUST-17级集训队选拔赛解题报告
摸了一个寒假的鱼,赛前各种被奶,就感觉自己要凉,我的感觉向来是对的,结果真的凉透了QAQ。
开场就是裸题各种bug各种WA,心态爆炸,确实还是造成了不小的影响。后面真的怀疑人生到不想写,挂机(然后被骂了orz)。我每次都控制不好比赛的心态和节奏唉_(:з」∠)_
比较意外的是小伙伴们和大佬们也emmmm 凉得挺厉害……跟学长们同时期是完全不能比的吧……直接造成了我们有个一周的contest补题……
补完之后的感受是……真的基本都是裸题。然而赛场上发挥正常最多也只是再出1题2题,很多地方都差临门一脚的感觉,前段时间死磕DP感觉会一点了,比赛的时候还是一个也没做出来= =。暴力又不敢打。补题暴力A的时候真是心情复杂……真的是明白了什么叫菜是原罪
下周ccf,下下周多校,下下下周C4和蓝桥杯,有几位新大佬我都没他们一半努力,我可能要抢救一下。
A. 灾区重建
题意:N个城市由M条道路互相连通,每条道路最大承重量是w,要选出一条从一个城市到达另外N-1个城市的路径使一次能承受的重量最大,求这个最大重量
数据范围:T组样例(T <= 10), N <= 1e5, M <= 1e6, u,v <= N, w <= 1e9
思路:一条路径能承受的最大重量是这几条路w的最小值,连通N个节点只需要N-1条边,把所有边按从大到小排序,求出一棵最大生成树,最小的那条边就是答案
1 #include<cstdio> 2 #include<algorithm> 3 #define INF 0x3f3f3f3f 4 using namespace std; 5 6 const int mx = 1e6+10; 7 int pa[mx]; 8 9 struct edge{ 10 int u, v, w; 11 edge(int u = 0, int v = 0, int w = 0): u(u), v(v), w(w){} 12 bool operator < (const edge& a) const{ 13 return w > a.w; 14 } 15 }e[mx]; 16 17 int findset(int x){ 18 return pa[x] == x ? x : pa[x] = findset(pa[x]); 19 } 20 21 int main(){ 22 int t, kase = 0; 23 scanf("%d", &t); 24 while (t--){ 25 for (int i = 0; i < mx; i++) pa[i] = i; 26 int n, m, u, v, w; 27 scanf("%d%d", &n, &m); 28 for (int i = 0; i < m; i++){ 29 scanf("%d%d%d", &u, &v, &w); 30 e[i] = edge(u, v, w); 31 } 32 int ans = INF, sum = 0; 33 sort(e, e+m); 34 for (int i = 0; i < m; i++){ 35 int a = findset(e[i].u), b = findset(e[i].v); 36 if (a != b){ 37 pa[a] = b; 38 sum++; 39 ans = min(ans, e[i].w); 40 } 41 if (sum == n-1) break; 42 } 43 printf("Case #%d: %d\n", ++kase, ans); 44 } 45 return 0; 46 }
B. 洗衣
题意:durong有N件衣服要洗,洗衣机一次洗一件衣服,每件衣服只能在固定的一个区间内洗,问需要多少洗衣机
数据范围:1 <= N <= 1e5, 1 <= st < en <= 1e9
思路1:一道裸贪心,至少大佬们都觉得它裸,原题是POJ3190。挑战题单上是有这题的,但我拉了没刷= =。我是真的不会。诶,基础不牢。
首先为了方便遍历按开始时间从小到大排序。为了找出正确的贪心策略,先模仿人的思路:对于新拿到的一件衣服,尝试能不能放在空闲的洗衣机后面,能就更新洗衣机的结束时间,不能就新加洗衣机。
至于实现的方法,用一个优先队列维护每件衣服的结束时间即可。因为队列中的元素都是在洗衣机里的,重载小于号让结束时间早的优先,如果最早洗衣机的结束时间还晚于当前的开始时间,那就必须新开洗衣机了。
1 #include<cstdio> 2 #include<queue> 3 #include<algorithm> 4 using namespace std; 5 6 const int mx = 1e5+10; 7 8 struct Node{ 9 int s, t; 10 bool operator < (const Node& a) const{ 11 return t > a.t || (t == a.t && s > a.s); 12 } 13 }p[mx]; 14 15 bool cmp(Node a, Node b){ 16 return a.s < b.s || (a.s == b.s && a.t < b.t); 17 } 18 19 int main(){ 20 int n; 21 while (scanf("%d", &n) == 1){ 22 int ans = 1; 23 for (int i = 0; i < n; i++) 24 scanf("%d%d", &p[i].s, &p[i].t); 25 sort(p, p+n, cmp); 26 priority_queue<Node> q; 27 q.push(p[0]); 28 for (int i = 1; i < n; i++){ 29 Node u = q.top(); 30 if (p[i].s >= u.t) q.pop(); 31 else ans++; 32 q.push(p[i]); 33 } 34 printf("%d\n", ans); 35 } 36 return 0; 37 }
另外不得不说,杜荣菊苣真有钱,洗衣机也多,衣服还有1e5件Σ(゚д゚lll)
思路2:赛场上不会上面的贪心做法,倒是想到了等价于选择一个点在尽量多的区间内,需要用洗衣机的数量等于最多的重合区间数量,想到了线段树的染色问题。那么就是区间更新+查询最大值了。不过1e9的数据范围需要离散化,我还写得不熟练,比赛时我没带线段树板子,甚至连I题裸的区间更新都忘了,手推线段树,WA到我不敢写,这个思路就没在赛场上实现。补完这种做法以后再附代码,就当练下离散化了。
C. 先有durong后有天
题意:N个点M条无向边的连通图,在满足s1到e1的距离不大于t1,s2到e2的距离不大于t2的情况下,拆掉尽可能多的边,求拆掉的边数
数据范围:N <= 3000, N-1 <= M <= N*(N-1)/2
思路:原题codeforces 543B,我感觉我是不是打过这场……
不知道从哪下手,首先可以知道的是,如果不拆的距离都大于了题目要求,那就是-1,否则肯定有拆法(或不拆)。那么就考虑最短路,边权为1用bfs就可以搞了。
这里zz了一下,以前做bfs都是迷宫题,给的都是n*n的矩阵,习惯性觉得跑一遍bfs复杂度是O(n^2),但其实这里是O(n),N才只有3000个点……所以贼暴力的跑出每两个点的最短路就行了。
至于为什么要求出所有最短路,因为这两条路线分为有重合部分和没有重合部分的两种情况,想了好久也不知道怎么优雅的判断,最后发现暴力的判断重合部分两个端点最优雅了,n^2验证emm。
要注意的一点是只写成s1->i->j->e1,s2->i->j->e2是不行的,这样写有方向性,是默认s1->t1, s2->t2是一个方向,所以还要检查s2->j->i->e2是不是更小。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #include<vector> 6 #define INF 0x3f3f3f3f 7 using namespace std; 8 9 const int mx = 3010; 10 vector<int> G[mx]; 11 int d[mx][mx]; 12 13 void bfs(int x){ 14 queue<int> q; 15 q.push(x); 16 d[x][x] = 0; 17 while (!q.empty()){ 18 int u = q.front(); q.pop(); 19 int len = G[u].size(); 20 for (int i = 0; i < len; i++){ 21 int v = G[u][i]; 22 if (d[x][v] > d[x][u]+1){ 23 d[x][v] = d[x][u]+1; 24 q.push(v); 25 } 26 } 27 } 28 } 29 30 int main(){ 31 int n, m, u, v; 32 while (scanf("%d%d", &n, &m) == 2){ 33 for (int i = 0; i < mx; i++){ 34 G[i].clear(); 35 for (int j = 0; j < mx; j++) 36 d[i][j] = INF; 37 } 38 for (int i = 1; i <= m; i++){ 39 scanf("%d%d", &u, &v); 40 G[u].push_back(v); 41 G[v].push_back(u); 42 } 43 int s1, e1, t1, s2, e2, t2; 44 scanf("%d%d%d", &s1, &e1, &t1); 45 scanf("%d%d%d", &s2, &e2, &t2); 46 for (int i = 1; i <= n; i++) bfs(i); 47 if (d[s1][e1] > t1 || d[s2][e2] > t2){ 48 printf("-1\n"); 49 continue; 50 } 51 int ans = d[s1][e1] + d[s2][e2]; 52 for (int i = 1; i <= n; i++){ 53 for (int j = 1; j <= n; j++){ 54 int v0 = d[s1][i]+d[i][j]+d[j][e1]; 55 int v1 = d[s2][i]+d[i][j]+d[j][e2], v2 = d[e2][i]+d[i][j]+d[j][s2]; 56 if (v0 <= t1 && v1 <= t2) ans = min(ans, v0+v1-d[i][j]); 57 if (v0 <= t1 && v2 <= t2) ans = min(ans, v0+v2-d[i][j]); 58 } 59 } 60 printf("%d\n", m-ans); 61 } 62 return 0; 63 }
后来的新大佬和学弟学妹们(如果有),彪神出的先有durong后有天系列(现在有5题了)要慎做,比赛看到就别开了orz
D. 一棵树
题意:给出一棵N个点的树,求任意两点之间的距离之和
数据范围:T <= 10组数据,N <= 1e5
思路:类似题有HDU2376。简单粗暴的描述加上对萌新不算水的算法orz。树形dp,选取第一个点为根,dfs遍历这棵树同时计算,每次记录一条边的两端有多少个点记为sum,这条边被经过的次数就是sum*(n-sum),加到边权和中。
树形dp还不是很熟,但这题感觉不用知道是树形dp就能搞,就是个找规律递推(所以说dp一般看规律?)然后要记得开long long。因为vector忘记clear RE一发,后来发现大家都在这题忘了……真是神奇。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define LL long long 5 using namespace std; 6 7 const int mx = 1e5+10; 8 LL sum[mx], dp[mx]; 9 int n; 10 11 struct edge{ 12 int v; 13 LL w; 14 edge(int v = 0, LL w = 0): v(v), w(w){} 15 }; 16 vector<edge> G[mx]; 17 18 void dfs(int u, int fa){ 19 sum[u] = 1; 20 int len = G[u].size(); 21 for (int i = 0; i < len; i++){ 22 int v = G[u][i].v; 23 LL w = G[u][i].w; 24 if (v != fa){ 25 dfs(v, u); 26 sum[u] += sum[v]; 27 dp[u] += dp[v] + sum[v]*(n-sum[v])*w; 28 } 29 } 30 } 31 32 int main(){ 33 int t, u, v; 34 LL w; 35 scanf("%d", &t); 36 while (t--){ 37 for (int i = 0; i < mx; i++){ 38 sum[i] = dp[i] = 0; 39 G[i].clear(); 40 } 41 scanf("%d", &n); 42 for (int i = 1; i < n; i++){ 43 scanf("%d%d%lld", &u, &v, &w); 44 G[u].push_back(edge(v, w)); 45 G[v].push_back(edge(u, w)); 46 } 47 dfs(1, -1); 48 printf("%lld\n", dp[1]); 49 } 50 return 0; 51 }
E. 杜荣NB
题意:给出一个数字b和T个查询x,询问x各位数字之和是否为b,如果是,输出是从小到大第几个这样的数
数据范围:b < 40, T < 1e4, x < 1e7(或1e8)
思路:1e7的数据范围就是个打表题……最暴力的一个个验证然后二分查询,是个大水题结果没敢暴力。1e8的有点dp思路想不出来,数位dp好像可以搞,等以后会了补上dp做法。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 const int mx = 1e7; 7 int s[mx]; 8 9 int check(int n){ 10 int sum = 0; 11 while (n){ 12 sum += n%10; 13 n /= 10; 14 } 15 return sum; 16 } 17 18 int solve(int ans){ 19 int cnt = 0; 20 for (int i = 1; i < mx; i++) 21 if (check(i) == ans) s[cnt++] = i; 22 return cnt; 23 } 24 25 int main(){ 26 int b, t, x; 27 while (scanf("%d", &b) == 1){ 28 int cnt = solve(b); 29 scanf("%d", &t); 30 while (t--){ 31 scanf("%d", &x); 32 if (check(x) == b) printf("%d\n", lower_bound(s, s+cnt, x)-s+1); 33 else printf("durongNB\n"); 34 } 35 } 36 return 0; 37 }
暴力都还要dls说,我菜真的是原罪
F. 挑战迷宫
题意:N个结点的树,M次查询两个点的距离
数据范围:N,M <= 1e5,w <= 1e4
思路:LCA模板题,树上两点间的距离是他们到根结点的距离之和减去他们的重复路径长度,即LCA到根结点的距离。
顺便附一下自己用的最顺手的倍增模板。挑战上的代码太鬼畜了。
倍增学习自刘神在群里发的寒训资料里面的链接:http://blog.csdn.net/pi9nc/article/details/8142384
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 using namespace std; 6 7 const int mx = 1e5+10; 8 int p[mx][30], rk[mx], d[mx]; 9 int n; 10 11 struct edge{ 12 int v, w; 13 edge(int v = 0, int w = 0): v(v), w(w){} 14 }; 15 vector<edge> G[mx]; 16 17 void dfs(int u, int fa, int cnt){ 18 rk[u] = cnt; 19 p[u][0] = fa; 20 int len = G[u].size(); 21 for (int i = 0; i < len; i++){ 22 int v = G[u][i].v; 23 if (v != fa){ 24 d[v] = d[u]+G[u][i].w; 25 dfs(v, u, cnt+1); 26 } 27 } 28 } 29 30 void lca(){ 31 for (int i = 1; i <= n; i++) 32 for (int j = 1; (1<<j) <= n; j++) p[i][j] = -1; 33 for (int j = 1; (1<<j) <= n; j++) 34 for (int i = 1; i <= n; i++) 35 if (p[i][j-1] != -1) p[i][j] = p[p[i][j-1]][j-1]; 36 } 37 38 int query(int x, int y){ 39 if (rk[x] < rk[y]) swap(x, y); 40 int k; 41 for (k = 0; (1<<(k+1)) <= rk[x]; k++); 42 for (int i = k; i >= 0; i--) 43 if (rk[x] - (1<<i) >= rk[y]) x = p[x][i]; 44 if (x == y) return x; 45 for (int i = k; i >= 0; i--){ 46 if (p[x][i] != -1 && p[x][i] != p[y][i]){ 47 x = p[x][i]; 48 y = p[y][i]; 49 } 50 } 51 return p[x][0]; 52 } 53 54 int main(){ 55 int q, u, v, w; 56 scanf("%d", &n); 57 for (int i = 1; i < n; i++){ 58 scanf("%d%d%d", &u, &v, &w); 59 G[u].push_back(edge(v, w)); 60 G[v].push_back(edge(u, w)); 61 } 62 dfs(1, -1, 0); 63 lca(); 64 scanf("%d", &q); 65 while (q--){ 66 scanf("%d%d", &u, &v); 67 printf("%d\n", d[u]+d[v]-2*d[query(u, v)]); 68 } 69 return 0; 70 }
比赛的时候dfs以后就忘记在main里面调用预处理的lca()直接查询了。。。偏偏,样例是一条链,本来就不用LCA。我要被大佬们笑死了
G. 括号匹配
题意:给出由‘(’和‘)’和‘?’组成的字符串s,‘?’可以转变为左右两种括号,问有多少种匹配的方案%1e9+7
数据范围:s的长度 <= 3000,数据小于20组
思路:类似题有codeforces 918C。括号匹配的思想都很接近啊。左括号多一层,右括号少一层,问号可左可右。
感觉是这次比赛最难的题,其实后来看也只是个裸dp,我dp不行是真的很难受。我首先往区间dp上考虑,发现不好做,看数据范围考虑n^2的线性dp。
总结一下人生经验状态一般是这样设的:dp(i,j)表示对于前i个字符,第j层括号时匹配的方案数。可能这个“第j层括号”不是太好理解,就比如s[j]为‘(’时,dp(i,j)从dp(i-1,j-1)转移过来,反之则从dp(i-1,j+1)转移过来。
这里之前Dillonh老师问我为什么左括号多了一层不是j+1,我还呆住了1s,才反应过来这是转移啊,按你的想法是反的。然后如果s[j]是‘?’,可左可右,那么就是方案数之和了。要注意的地方是如果0层有右括号,大于len/2层有左括号,那方案就是0了,在相加这里要取模。
这里第i阶段的值只和第i-1阶段有关,所以可以滚动数组优化一下,不过在这题不是必须的,唯一会的滚动是01异或_(:з)∠)_还手动枚举正确写法。
1 #include<cstdio> 2 #include<cstring> 3 4 const int mx = 3010; 5 const int dalao = 1e9+7; 6 char s[mx]; 7 int dp[2][mx]; 8 9 int main(){ 10 int t; 11 scanf("%d", &t); 12 while (t--){ 13 memset(dp, 0, sizeof(dp)); 14 scanf("%s", s); 15 int n = strlen(s); 16 if (n&1) { 17 printf("0\n"); 18 continue; 19 } 20 dp[1][0] = 1; 21 int k = 0; 22 for (int i = 0; i < n; i++){ 23 for (int j = 0; j < n/2+1; j++){ 24 if (s[i] == '(') { 25 if (j > 0) dp[k][j] = dp[k^1][j-1]; 26 else dp[k][j] = 0; 27 } 28 else if (s[i] == ')'){ 29 if (j < n/2) dp[k][j] = dp[k^1][j+1]; 30 else dp[k][j] = 0; 31 } 32 else { 33 dp[k][j] = 0; 34 dp[k][j] = j>0 ? (dp[k][j]+dp[k^1][j-1])%dalao : dp[k][j]; 35 dp[k][j] = j<n/2 ? (dp[k][j]+dp[k^1][j+1])%dalao : dp[k][j]; 36 } 37 } 38 k ^= 1; 39 } 40 printf("%d\n", dp[k^1][0]%dalao); 41 } 42 return 0; 43 }
你们dp从哪学啊?dalaoA:紫书。dalaoB:挑战。dalaoC:OJ。鶸QAQorz:男票 qwq。
感谢男票,男票太强了我真是太菜了qwq
就是秀你们打死我啊!
H. 逃出监狱
题意:一个n*m的矩阵,‘#’代表障碍物,‘A’‘B’‘E’分别代表小偷、警察和出口,小偷和警察每秒可以往上下左右走一格。问小偷能不能比警察先到出口
数据范围:T <= 50组数据,n,m <= 500
思路:很裸的bfs最短路。比赛时放弃I题来写的,结果还是全场1A,开始已经半小时了。从终点开始bfs求到AB的最短路而不用跑两次bfs,这个通过新生赛菊花题早就很熟悉了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #define INF 0x3f3f3f3f 6 using namespace std; 7 8 const int mx = 505; 9 char s[mx][mx]; 10 int d[mx][mx]; 11 int n, m, disA, disB; 12 int dr[] = {-1, 0, 1, 0}; 13 int dc[] = {0, -1, 0, 1}; 14 15 struct Node{ 16 int r, c; 17 Node(int r = 0, int c = 0): r(r), c(c){} 18 }; 19 20 bool inside(const Node& v){ 21 int r = v.r, c = v.c; 22 return r >= 0 && r < n && c >= 0 && c < m; 23 } 24 25 Node walk(const Node& u, int i){ 26 return Node(u.r+dr[i], u.c+dc[i]); 27 } 28 29 void bfs(int r0, int c0){ 30 queue<Node> q; 31 Node u(r0, c0); 32 d[r0][c0] = 0; 33 q.push(u); 34 while (!q.empty()){ 35 Node u = q.front(); q.pop(); 36 if (s[u.r][u.c] == 'A') disA = d[u.r][u.c]; 37 if (s[u.r][u.c] == 'B') disB = d[u.r][u.c]; 38 for (int i = 0; i < 4; i++){ 39 Node v = walk(u, i); 40 if (inside(v) && d[v.r][v.c] < 0 && s[v.r][v.c] != '#'){ 41 d[v.r][v.c] = d[u.r][u.c]+1; 42 q.push(v); 43 } 44 } 45 } 46 } 47 48 int main(){ 49 int t, sx, sy; 50 scanf("%d", &t); 51 while (t--){ 52 memset(d, -1, sizeof(d)); 53 disA = disB = INF; 54 scanf("%d%d", &n, &m); 55 for (int i = 0; i < n; i++){ 56 scanf("%s", s[i]); 57 for (int j = 0; j < m; j++){ 58 if (s[i][j] == 'E'){ 59 sx = i; 60 sy = j; 61 } 62 } 63 } 64 bfs(sx, sy); 65 if (disA < disB) printf("Yes\n"); 66 else printf("No\n"); 67 } 68 return 0; 69 }
I. 简单题
题意:N个整数的数列,Q次两种操作:区间加上一个值x,查询区间[l,r]的平均值。
数据范围:N,Q <= 1e5, x <= 100
思路:线段树区间更新模板题。而我现场没带模板的同时,居然忘了区间更新而手推lazy WA成狗了orz。结果WA的主要原因还是没开longlong。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define LL long long 5 #define lid id << 1 6 #define rid id << 1 | 1 7 using namespace std; 8 9 const int mx = 1e5+10; 10 LL a[mx]; 11 12 struct tree{ 13 int l, r; 14 LL sum, lazy; 15 }tree[4*mx+10]; 16 17 void pushdown(int id){ 18 if (tree[id].lazy != 0 && tree[id].l != tree[id].r){ 19 LL x = tree[id].lazy; 20 tree[lid].lazy += x; 21 tree[rid].lazy += x; 22 tree[lid].sum += x*(tree[lid].r-tree[lid].l+1); 23 tree[rid].sum += x*(tree[rid].r-tree[rid].l+1); 24 tree[id].lazy = 0; 25 } 26 } 27 28 void build(int l, int r, int id){ 29 tree[id].l = l; 30 tree[id].r = r; 31 tree[id].lazy = 0; 32 if (l == r){ 33 tree[id].sum = a[l]; 34 return; 35 } 36 int mid = (l+r) >> 1; 37 build(l, mid, lid); 38 build(mid+1, r, rid); 39 tree[id].sum = tree[lid].sum + tree[rid].sum; 40 } 41 42 void upd(int l, int r, int id, LL x){ 43 pushdown(id); 44 if (tree[id].l == l && tree[id].r == r){ 45 tree[id].lazy += x; 46 tree[id].sum += x*(r-l+1); 47 return; 48 } 49 int mid = (tree[id].l + tree[id].r) >> 1; 50 if (r <= mid) upd(l, r, lid, x); 51 else if (mid < l) upd(l, r, rid, x); 52 else { 53 upd(l, mid, lid, x); 54 upd(mid+1, r, rid, x); 55 } 56 tree[id].sum = tree[lid].sum + tree[rid].sum; 57 } 58 59 LL query(int l, int r, int id){ 60 pushdown(id); 61 if (tree[id].l == l && tree[id].r == r) return tree[id].sum; 62 int mid = (tree[id].l + tree[id].r) >> 1; 63 if (r <= mid) return query(l, r, lid); 64 else if (mid < l) return query(l, r, rid); 65 else return query(l, mid, lid) + query(mid+1, r, rid); 66 } 67 68 int main(){ 69 int t, n, q; 70 scanf("%d", &t); 71 while (t--){ 72 scanf("%d", &n); 73 for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); 74 build(1, n, 1); 75 scanf("%d", &q); 76 while (q--){ 77 int ok, x, y, z; 78 scanf("%d%d%d", &ok, &x, &y); 79 if (ok) printf("%.2f\n", 1.0*query(x, y, 1)/(y-x+1)); 80 else { 81 scanf("%d", &z); 82 upd(x, y, 1, z); 83 } 84 } 85 } 86 return 0; 87 }
补题1A了,很心痛
终于更完了,马上滚去准备一下最短路之类图论内容冲击ccf的第四题。恍然间都大一下了还考不到ccsp的分,太菜了QAQ。