2023/7/20 模拟赛题解
2023/7/20 模拟赛题解
写在前面
这次比赛整体偏简单,而且部分分丰富,数据也不强,反正就是这次是运气好了。但是,还是要多加强思考的能力。
T1 古代龙人的谜题
题目描述
古代龙人手中共有n粒秘药,我们可以用1表示「古老的秘药」,其余的用0表示。他将它们排成一列。古代龙人认为平衡是美的,于是他问Mark能选出多少个「平衡的区间」。「平衡的区间」是指首先选出一个区间[L, R],在它内部选出一个中间点mid,满足L<mid<R,mid是「古老的秘药」,且区间[L, mid]和[mid, R]中「古老的秘药」个数相等。
思路
首先我们来考虑暴力:可以从左到右枚举中间点
但是这样显然还是无法线性做。我们来考虑如何继续优化。
我们重新考虑上述过程,即枚举中心位置向左右拓展,每次贡献答案为区间长度乘积。我们发现,对于一个区间,会一直与右侧的一些区间造成贡献;反过来,每个区间,都会与左侧的一些区间造成贡献。而如果一个个枚举某个区间左侧的
代码:
点击查看代码
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 1e6+100; inline int read(){ int x = 0, f = 1; char ch = getchar(); while(ch<'0' || ch>'9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar(); return x * f; } int T; int n; char s[N]; ll cnt[2], cnt0, lst; int now = 1; ll ans; int main(){ T = read(); n = read(); scanf("%s", s+1); int st = 1; while(s[st] == '0'){ ++st, ++lst; } for(int i = st+1; i<=n; ++i){ if(s[i] == '1'){ ans+=lst*cnt0; ans+=(cnt0+1)*cnt[now&1]; cnt[now&1]+=(lst+1); lst = cnt0;cnt0 = 0;++now; } else ++cnt0; } ans+=cnt0*lst; ans+=(cnt0+1)*cnt[now&1]; printf("%lld\n", ans); return 0; }
T2 指引
题意:
有
思路
场上想的做法是二分图(毕竟太明显了),但是最后一个点显然跑不动。有人说可以 KD-Tree 优化建图(反正我不会)。
正解应该是贪心(场上想了但是没细思考)。把人和门一起按横坐标降序、纵坐标降序排序,这样每次枚举到一个门,就加入数据结构,每次枚举到人,就把离他纵坐标最近的门给他。
考虑为什么这样做是对的。首先排序保证了后面枚举到的每个人的横坐标都不大于已有门的横坐标,而对于一个门,如果不给这个人而给其他人,显然其他人要么横坐标比他小,要么纵坐标比他小。考虑横坐标一样而纵坐标比他小的,这些人显然有可能有更多选择,所以门给这个人显然不劣;同样的,对于他左侧的人,如果这扇门能给他左侧的一个人,那另外那个人也可能会有更多选择;如果这扇门本来就不能给其他的人(纵坐标太高),那必须给这个人。总之,给这个人的答案不会劣于其他答案。这里可以用 multiset 维护。
代码:
点击查看代码
#include<bits/stdc++.h> using namespace std; const int N = 1e5+100; struct point{ int x, y, type; bool operator <(const point &b){ if(x == b.x){ return y>b.y; } return x>b.x; } }p[N<<1]; inline int read(){ int x = 0, f = 1; char ch = getchar(); while(ch<'0' || ch>'9'){if(ch == '-') f = -1; ch = getchar();} while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar(); return x * f; } int Num; int n; multiset<int> s; int ans; int main(){ Num = read(); n = read(); for(int i = 1; i<=n; ++i){ p[i] = (point){read(), read(), 0}; } for(int i = 1; i<=n; ++i){ p[i+n] = (point){read(), read(), 1}; } n<<=1; sort(p+1, p+n+1); multiset<int> :: iterator it; for(int i = 1; i<=n; ++i){ if(p[i].type){ s.insert(p[i].y); } else{ it = s.lower_bound(p[i].y); if(it != s.end()){ ++ans; s.erase(it); } } } printf("%d\n", ans); return 0; }
T3 碎片
题意
给定一个由大写字母组成的
思路
练习搜索的好题(
非常多的剪枝……反正这道题貌似就没有正解,都是瞎搞(正解貌似也是去搜索)。
这里首先要看到几个性质(非常有用):
- 矩阵合法的必要条件:对于所有行和所有列,除了中间位置(
或 为奇数)可以单独成一行/列外,其他的每一行/列对应的那一行/列中的每个字母的数量应一致(即集合相同)(我就是靠这个性质水掉 95 pts 的) - 对于一个已经中心对称的矩阵,将匹配的两行/列同时向里/外交换,矩阵仍合法
有了这两个行政我们就可以搜辣!
首先通过性质一可以直接排除很多不合法的方案,对于其他可能合法的方案,我们去搜索前一半要放哪些行,后一半放能和它匹配的,然后通过哈希来比较列于列是否可以匹配。复杂度玄学,但意外很快。
代码:
点击查看代码
#include<bits/stdc++.h> #define ull unsigned long long const int P = 233; using namespace std; const int N = 15; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch<'0' || ch>'9') { if(ch == '-') f = -1; ch = getchar(); } while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar(); return x * f; } ull Ps[N]; int Num, T; int n, m; char s[N][N]; int ax[N][27], ay[N][27];//记录每一行/列字母的数量 int typ[N], tot; int tx[N], ty[N]; bool check1(int x, int y) { for(int i = 0; i<26; ++i) { if(ax[x][i]!=ax[y][i]) return 0; } return 1; } bool check2(int x, int y) { for(int i = 0; i<26; ++i) { if(ay[x][i]!=ay[y][i]) return 0; } return 1; } int neH[N], neL[N];bool visH[N], visL[N]; bool ans; ull hasha[N], hashb[N]; bool check(){ for(int i = 1; i<=m; ++i){ hasha[i] = 0; hashb[i] = 0; // Ps[0] = 1; for(int j = 1; j<=n; ++j){ hasha[i] = hasha[i]*P+s[neH[j]][i]; } for(int j = n; j>=1; --j){ hashb[i] = hashb[i]*P+s[neH[j]][i]; } } memset(visL, 0, sizeof(visL)); int cnt = 0; for(int i = 1; i<=m; ++i){ if(visL[i]) continue; for(int j = i+1; j<=m; ++j){ if(visL[j]) continue; if(hasha[i] == hashb[j]){ cnt+=2; visL[i] = 1, visL[j] = 1; break; } } } if(m&1) return cnt>=m-1; else return cnt == m; } void dfs(int pos, int lim){ if(pos>lim){ if(check()){ ans = 1; } return; } if((n&1)&&(pos == lim)){ for(int i = 1; i<=n; ++i){ if(visH[i]) continue; neH[pos] = i; break; } dfs(pos+1, lim); } for(int i = 1; i<=n; ++i){ if(visH[i]) continue; visH[i] = 1; neH[pos] = i; for(int j = 1; j<=n; ++j){ if(visH[j]) continue; if(tx[i] == tx[j]){ visH[j] = 1; neH[n-pos+1] = j; dfs(pos+1, lim); if(ans) return; visH[j] = 0; } } visH[i] = 0; } } int main() { // freopen("data.txt", "r", stdin); // freopen("my.out", "w", stdout); Num = read(); T = read(); Ps[0] = 1; for(int i = 1; i<=12; ++i){ Ps[i] = Ps[i-1]*P; } while(T--) { n = read(), m = read(); memset(ax, 0, sizeof(ax)); memset(ay, 0, sizeof(ay)); memset(tx, 0, sizeof(tx)); memset(ty, 0, sizeof(ty)); for(int i = 1; i<=n; ++i) { scanf("%s", s[i]+1); } for(int i = 1; i<=n; ++i) { for(int j = 1; j<=m; ++j) { ++ax[i][s[i][j]-'A']; } } for(int j = 1; j<=m; ++j) { for(int i = 1; i<=n; ++i) { ++ay[j][s[i][j]-'A']; } } tot = 0; for(int i = 1; i<=n; ++i) { if(tx[i]) continue; if(!tx[i]) { tx[i] = ++tot; typ[tot] = 1; } for(int j = i+1; j<=n; ++j) { if(tx[j]) continue; if(check1(i, j)) { tx[j] = tx[i]; ++typ[tx[i]]; } } } bool flag = 1; bool is_failed = 0; if(n&1) flag = 0; for(int i = 1; i<=tot; ++i) { if(flag && (typ[i]&1)) { is_failed = 1; break; } else if(typ[i]&1) { flag = 1; } } if(is_failed) { puts("NO"); continue; } tot = 0; for(int i = 1; i<=m; ++i) { if(ty[i]) continue; if(!ty[i]) { ty[i] = ++tot; typ[tot] = 1; } for(int j = i+1; j<=m; ++j) { if(ty[j]) continue; if(check2(i, j)) { ty[j] = ty[i]; ++typ[ty[i]]; } } } flag = 1; if(m&1) flag = 0; for(int i = 1; i<=tot; ++i) { if(flag && (typ[i]&1)) { is_failed = 1; break; } else if(typ[i]&1) { flag = 1; } } if(is_failed) { puts("NO"); continue; } ans = 0; memset(visH, 0, sizeof(visH)); dfs(1, (n+1)/2); if(ans) puts("YES"); else puts("NO"); } return 0; }
T4 寻梦
updated 还是写了吧……
题面描述
初始时,旅者
每一天,位于
现在小X 想要知道,是否存在至少一组满足条件的
思路
首先很明显的一个套路就是把
然后这个问题就是一个分拆数问题,当然,你只需要找出一种方案即可。在
代码就不放了,分类讨论搞得还特别丑,没啥可读性。