牛客多校第十场题解
Browser Games
https://ac.nowcoder.com/acm/contest/11261/A
A - Browser Games
题意:
按顺序往集合中插入字符串,要求输出最少的前缀串数量,使得这些前缀串能匹配出所有已经加入集合的字符串,并且不能匹配出未加入集合的字符串。题目保证任一字符串不是其它字符串的前缀。
思路:
如果不卡空间的话,光字典树就有许多不同的做法。一种是从上到下做,但是这种做法需要表示出每个节点的儿子有哪些,就比较浪费空间。对于一棵树来说,每个节点的父节点是唯一的,压缩字典树的时候应从这一点考虑。所以尝试先写写从下到上的做法。
建立字典树之后,记录每个字符串的尾节点的位置,然后从下往上走。如果遇到的节点只有当前字符串一个儿子,那么就可以继续往上走。当遇到有分叉的节点时,我们记录下已经有几个字符串经过这个节点了。如果经过这个点的字符串数量没到达分叉的数量,我们停止往上走。如果到了,说明后面的字符串都没有拥有当前的前缀,即当前的前缀串能匹配出来的字符串都已经加入集合了,所以我们只需要一个前缀串就能表示出分叉的所有字符串,更新下答案,并继续往上走。
接下来考虑压缩字典树。因为要让儿子指向父亲,就想到了递归处理,处理完了得到儿子节点,再让儿子节点指向当前节点。在字典树中,如果字符串在当前位的字符相同,就会用同一个节点。这一步可以在递归过程中使用桶排处理(好像刚开始在最外面直接sort也行),将要走同一个节点的字符串归类,然后进行递归处理。一长段相同的字符串也可以压成一个节点,写的时候可以用指针来写,不过没必要,因为编译器会把定义了但是没用到的内存忽略掉。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll INF = 0x3f3f3f3f3f3f3f3f; const int N = 1e5 + 7; int n, posNode[N], posStr[N], tot; char s[N][103]; vector<int> bkt[64]; int getId(char c) { if (islower(c)) return c - 'a'; if (isupper(c)) return c - 'A' + 26; if (isdigit(c)) return c - '0' + 52; if (c == '.') return 62; return 63; } struct Node { int son, del, fa; void init() { son = del = fa = 0; } }node[N * 100]; int newNode() { node[++tot].init(); return tot; } int bktSort(int dep, int l, int r) { if (l == r) { return posNode[ posStr[l] ] = newNode(); // 记录尾节点位置 } bool allSame = true; for (int i = l; i < r; ++i) { if (s[ posStr[i] ][dep] != s[ posStr[i + 1] ][dep]) { allSame = false; break; } } if (allSame) { // 如果存在相同的前缀,把这个前缀压成一个节点,节省内存 int u = bktSort(dep + 1, l, r); if (dep) return u; node[u].fa = 0; // 如果还没有根节点,先定义出来。这里默认0为根节点。 return 0; } for (int i = 0; i < 64; ++i) { bkt[i].clear(); } for (int i = l; i <= r; ++i) { // 按照当前位置的字符分到不同的桶里,因为保证了字符串不是其他字符串的前缀,直接分配没问题,最后一定会递归成l == r bkt[ getId(s[ posStr[i] ][dep]) ].push_back( posStr[i] ); } int cnt = l; vector<int> len; for (int i = 0; i < 64; ++i) { if (!bkt[i].size()) continue; for (auto j : bkt[i]) posStr[cnt++] = j; // 对排序后的字符串重新编号 len.push_back(bkt[i].size()); // 记录每个桶的大小,不可直接递归,否则后面会影响前面的桶 } int u = newNode(); for (auto w : len) { node[bktSort(dep + 1, l, l + w - 1)].fa = u; l += w; node[u].son++; } return u; } void solve() { scanf("%d", &n); for (int i = 0; i < n; ++i) { scanf("%s", s[i]); posStr[i] = i; // 因为需要桶排,每次移动整个字符串不太好,所以用一个数组代表当前下标代表的字符串 } int rt = bktSort(0, 0, n - 1); int ans = 0; for (int i = 0; i < n; ++i) { int now = posNode[i]; ans++; while ((now = node[now].fa) != rt) { node[now].del++; if (node[now].son == node[now].del) { ans -= node[now].son - 1; } else { break; } } printf("%d\n", ans); } } int main() { int t = 1; while (t--) solve(); return 0; }
F - Train Wreck
题意: 左括号表示一列火车进栈,右括号表示一列火车出栈。提供了一些颜色,要求对每列火车进行染色,使得栈中的所有的火车颜色序列不重复。
样例二解释:
4 ()(())() 1 2 4 4
栈中各时刻的火车序列为:
[1] [2] [2,3] [4]
样例的染色方案为,4 2 4 1,因此获得的颜色序列为
[4] [2] [2, 4] [1]
题目要求即为这些颜色序列,必须唯一
思路: 只有拥有共同前缀的火车,需要染成不同的颜色
样例解释:
假设当前有如下火车序列: [1] [1,2] [1,2,3] [1,2,4] [1,2,5] [1,2,6] [1,2,7]
对于火车3,4,5,6,7,他们拥有共同的前缀1-2,因此需要染成不同的颜色,才能保证颜色序列唯一。
根据入栈情况,对火车建边,如3,4,5,6,7都会跟在火车2的后面,由此建立2 → 3、2 → 4、2 → 5、2 → 6、2 → 7的有向边。
即2指向的所有火车,需要染成不同颜色。跑DFS,优先分配数量较多的颜色。
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define endl "\n" #define dbg(x...) \ do { \ cout << #x << " -> "; \ err(x); \ } while (0) void err() { cout << endl; } template <class T, class... Ts> void err(const T& arg, const Ts&... args) { cout << arg << ' '; err(args...); } const int maxn = 1e6 + 10; int cnt, head[maxn]; struct edge { int v, next; edge () {} edge (int v, int next) :v(v), next(next) {} } e[maxn << 1]; void add (int u, int v) { e[++cnt] = edge(v, head[u]), head[u] = cnt; } struct node { int col, cnt; node () {} node (int col, int cnt) : col(col), cnt(cnt) {} bool operator<(const node& A)const { return A.cnt > cnt; } }; int n, tot; char s[maxn << 1]; int col[maxn]; int ans[maxn]; priority_queue<node> Q; bool flag = true; void dfs (int u) { if (!flag) return; queue<node> tem; for (int i = head[u]; i; i = e[i].next) { int v = e[i].v; if (Q.size()) { node nod = Q.top(); Q.pop(); ans[v] = nod.col; nod.cnt--; if (nod.cnt) tem.emplace(nod); } else return (void)( flag = false ); } while (tem.size()) { Q.emplace(tem.front()); tem.pop(); } for (int i = head[u]; i; i = e[i].next) { int v = e[i].v; dfs(v); } } int pre[maxn]; void run() { scanf("%d", &n); scanf("%s", s + 1); int idx = 0; pre[ ++pre[0] ] = n + 1; for (int i = 1; i <= 2 * n; ++i) { if (s[i] == '(') { add(pre[ pre[0] ], ++idx); pre[ ++pre[0] ] = idx; } else pre[0]--; } for (int i = 1, c; i <= n; ++i) { scanf("%d", &c); col[c]++; } for (int i = 1; i <= n; ++i) { if (col[i]) Q.emplace(i, col[i]); } dfs(n + 1); if (flag) { puts("YES"); for (int i = 1; i <= n; ++i) { printf("%d%c", ans[i], " \n"[i == n]); } } else { puts("NO"); } } int main() { int t = 1; // scanf("%d", &t); while (t--) run(); return 0; } /* 7 ((()()()()())) 1 2 3 4 5 5 5 */
H - War of Inazuma (Easy Version)
题解: 签到题,按的奇偶染色即可
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define endl "\n" #define dbg(x...) \ do { \ cout << #x << " -> ";\ err(x); \ } while (0) void err() { cout << endl; } template <class T, class... Ts> void err(const T& arg, const Ts&... args) { cout << arg << ' '; err(args...); } int n; string s; void run() { cin >> n; n = pow(2, n); for(int i = 0; i < n; i++) { int num = 0; for(int j = 0; j <= 23; j++) { if((i >> j) & 1) num++; } if(num % 2) s += '1'; else s += '0'; } cout << s << endl; } int main() { int t = 1; // scanf("%d", &t); while (t--) run(); return 0; }
J - Illuminations
题意: 给出一个凸包以及凸包外若干个点,这些点射出光源,问最少要多少个点就能照亮整个凸包
题解:
- 先求出点到凸包的两个切点
- 得到了若干个区间,然后在凸包上求一个环覆盖
#include<bits/stdc++.h> using namespace std; #define dbg(x...) do { cout << #x << " -> "; err(x); } while (0) void err () { cout << endl;} template <class T, class... Ts> void err(const T& arg, const Ts&... args) { cout << arg << ' '; err(args...);} #define ll long long #define ull unsigned long long #define LL __int128 #define inf 0x3f3f3f3f #define INF 0x3f3f3f3f3f3f3f3f #define pii pair<int, int> #define PII pair<ll, ll> #define tint tuple<int, int, int> #define fi first #define se second #define pb push_back #define eb emplace_back #define em emplace #define mp(a,b) make_pair(a,b) #define all(x) (x).begin(), (x).end() #define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define PAUSE system("pause"); const double Pi = acos(-1.0); const double eps = 1e-8; const int maxn = 2e5 + 10; const int maxm = 1e5 + 10; const int mod = 998244353; inline ll rd() { ll f = 0; ll x = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) f |= (ch == '-'); for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0'; if (f) x = -x; return x; } void out(ll a) {if(a<0)putchar('-'),a=-a;if(a>=10)out(a/10);putchar(a%10+'0');} #define pt(x) out(x),puts("") inline void swap(ll &a, ll &b){ll t = a; a = b; b = t;} inline void swap(int &a, int &b){int t = a; a = b; b = t;} inline ll min(ll a, ll b){return a < b ? a : b;} inline ll max(ll a, ll b){return a > b ? a : b;} ll qpow(ll n,ll k,ll mod) {ll ans=1;while(k){if(k&1)ans=ans*n%mod;n=n*n%mod;k>>=1;}return ans%mod;} mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); int sgn(double x) { if (fabs(x) < eps) return 0; else return x < 0 ? -1 : 1; } struct Point { double x, y; Point() {} Point(double x, double y) :x(x), y(y) {} double operator ^ (const Point b)const { return x * b.y - y * b.x; } Point operator - (const Point b)const { return Point(x - b.x, y - b.y); } }p[maxn]; struct polygon { int n; Point p[maxn]; int onleft(Point p, Point a, Point b) { return sgn((a - p) ^ (b - p)); } pii tangentsConvex(Point u) { int l = 0, r = 0; for (int k = 20; k >= 0; k--) { int d = (1 << k) % n; if (onleft(u, p[l], p[(l + d) % n]) <= 0) l = (l + d) % n; if (onleft(u, p[l], p[(l + n - d) % n]) <= 0) l = (l + n - d) % n; if (onleft(u, p[r], p[(r + d) % n]) >= 0) r = (r + d) % n; if (onleft(u, p[r], p[(r + n - d) % n]) >= 0) r = (r + n - d) % n; } return mp(l, r); } }s; pii a[maxn]; int n, m; int id[maxn]; int nxt[maxn][22]; void solve() { scanf("%d%d", &n, &m); s.n = n; for (int i = 0; i < n; i++) scanf("%lf%lf", &s.p[i].x, &s.p[i].y); for (int i = 1; i <= m; i++) { Point t; scanf("%lf%lf", &t.x, &t.y); a[i] = s.tangentsConvex(t); } vector<tint> seg; for (int i = 1; i <= m; i++) { int r = a[i].fi, l = a[i].se; if (l <= r) { seg.pb({ l, r - 1, i }); seg.pb({ l + n, r - 1 + n, i }); } else { seg.pb({ l, r - 1 + n, i }); } } sort(all(seg)); int right = 0, rightId = 0; int p = 0; for (int i = 0; i < 2 * n; i++) { while (p < seg.size() && get<0>(seg[p]) <= i) { if (get<1>(seg[p]) + 1 > right) { right = get<1>(seg[p]) + 1; rightId = get<2>(seg[p]); } p++; } if (i < right) { nxt[i][0] = right; id[i] = rightId; } } int K = 22; for (int i = 2 * n - 1; i >= 0; i--) { for (int k = 1; k < K; k++) nxt[i][k] = nxt[nxt[i][k - 1]][k - 1]; } int ans = inf, ansSt = 0; for (int st = 0; st < n; st++) { int now = 0; int o = st; for (int k = K - 1; k >= 0; k--) if (nxt[o][k] != 0 && nxt[o][k] < st + n) { o = nxt[o][k]; now += 1 << k; } o = nxt[o][0]; now++; if (o != 0 && o >= st + n && now < ans) { ans = now; ansSt = st; } } if (ans == inf) { puts("-1"); } else { vector<int> vec; int o = ansSt; while (o < ansSt + n) { vec.eb(id[o]); o = nxt[o][0]; } printf("%d\n", vec.size()); for (auto g : vec) printf("%d ", g); puts(""); } } int main() { // freopen("in.txt","r",stdin); // freopen("out.txt", "w", stdout); int t = 1; // t = rd(); // scanf("%d", &t); // cin >> t; while(t--) solve(); PAUSE; return 0; }