- A. Casimir's String Solitaire
- B. Shifting Sort
- C. Ticks
- D. Productive Meeting
- E1. Permutation Minimization by Deque
- E2. Array Optimization by Deque
- F. Array Stabilization (AND version)
- G. Minimal Coverage
-
题意
给你一个字符串,你可以删除AB或者BC,问你是否可以将字符串删除变为空。 -
解题思路
签到,判断 c n t a + c n t c = c n t b cnt_a+cnt_c=cnt_b cnta+cntc=cntb。 -
AC代码
#includeB. Shifting Sort#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 1e5 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t; string s; void solve(){ } int main(){ cin >> t; while(t -- ){ cin >> s; int cnt[3]; cnt[0] = cnt[1] = cnt[2] = 0; for(int i = 0; i < s.size(); ++ i){ cnt[s[i] - 'A'] ++; } if(cnt[1] == cnt[0] + cnt[2])puts("YES"); else puts("NO"); } solve(); return 0; }
-
题意
给定一个数组,你每次可以选择一个区间 [ l , r ] [l,r] [l,r]然后进行循环左移 d d d。需要你将数组变成递增的操作输出,操作总数不能超过 n n n。 -
解题思路
不难发现,我们每次操作可以将一个最大值放在最右边,即我们先找到我们当前要确定的第 i i i的数,那么它肯定是在前 i i i个数中的,我们只需要找到这个数的位置 l l l,然后以它应该出现的位置 r r r,进行操作 [ l , r ] [l,r] [l,r]循环左移 1 1 1位即可。然后依次进行模拟。 -
AC代码
#includeC. Ticks#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 50 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t, n, a[N], b[N]; struct node{ int l, r, d; }; void solve(){ //每一次可以确定最大值。将最大值移动到相应位置即可。 vector res; int k = n; sort(b + 1, b + 1 + n); while(k){ int idx = 0; for(int i = 1; i <= k; ++ i){ if(a[i] == b[k]){ idx = i; break; } } //i应该要到k。 if(idx == k){ -- k; continue; } else res.push_back({idx, k, 1}); //修改ai。 for(int i = idx; i < k; ++ i)a[i] = a[i + 1]; a[k] = b[k]; -- k; } cout << res.size() << endl; for(int i = 0; i < res.size(); ++ i){ cout << res[i].l << " " << res[i].r << " " << res[i].d << endl; } } int main(){ cin >> t; while(t -- ){ cin >> n; for(int i = 1; i <= n; ++ i){ cin >> a[i]; b[i] = a[i]; } solve(); } return 0; }
-
题意
给你一个地图,问你是否可以进行绘制使得形成这个地图。绘制操作为选定一个点 ( i , j ) (i,j) (i,j),将其绘制成黑色,同时将左对角线和右对角线的 x x x个格子绘制成黑色,需要满足 x ≥ d xgeq d x≥d。 -
解题思路
由题可得,我们可以进行模拟,即从下往上选定一个出发点 ( i , j ) (i,j) (i,j),然后进行合理的 d f s dfs dfs判断是否可行,如果可行,将其绘制,并用vis数组标记,最后再检测黑色的格子是否都已经被标记了即可。 -
AC代码
#includeD. Productive Meeting#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 20 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t, n, m, k; char s[N][N]; bool vis[N][N];//标记是否可以被染色。 int dfs1(int x, int y, bool flag, int res){ int ans = 1; if(flag && res == 0)return ans; if(x - 1 >= 1 && x - 1 <= n && y - 1 >= 1 && y - 1 <= m && s[x - 1][y - 1] == '*'){ ans += dfs1(x - 1, y - 1, flag, res - 1); } if(flag)vis[x][y] = true; return ans; } int dfs2(int x, int y, bool flag, int res){ int ans = 1; if(flag && res == 0)return ans; if(x - 1 >= 1 && x - 1 <= n && y + 1 >= 1 && y + 1 <= m && s[x - 1][y + 1] == '*'){ ans += dfs2(x - 1, y + 1, flag, res - 1); } if(flag)vis[x][y] = true; return ans; } void solve(){ bool flag = false; for(int i = n; i >= 1; -- i){ for(int j = m; j >= 1; -- j){ if(s[i][j] == '*'){ int x = dfs1(i, j, false, 0) - 1, y = dfs2(i, j, false, 0) - 1; //cout << i << " " << j << " " << x << " " << y << endl; if(min(x, y) < k && !vis[i][j]){ flag = true; break; } else if(min(x, y) >= k){ dfs1(i, j, true, min(x, y) + 1), dfs2(i, j, true, min(x, y) + 1); } } } if(flag)break; } for(int i = 1; i <= n; ++ i){ for(int j = 1; j <= m; ++ j){ if(s[i][j] == '*' && !vis[i][j])flag = true; } } if(flag)puts("NO"); else puts("YES"); } int main(){ cin >> t; while(t -- ){ cin >> n >> m >> k; memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; ++ i)cin >> s[i] + 1; solve(); } return 0; }
-
题意
给定一个数组 a a a,你每次需要选定两个数组元素都大于 0 0 0的 a [ i ] a[i] a[i]和 a [ j ] a[j] a[j],然后将它们都减 1 1 1,问操作次数最大为多少。 -
解题思路
利用优先队列或者multiset均可,我这里使用muliset每次取出实时最大值最小值进行操作,操作完之后更新即可。
ps:好像有个结论,每次取出实时最大值和次大值进行操作也是最优的,这里就可以用到优先队列,请读者自行实现。 -
AC代码
#includeE1. Permutation Minimization by Deque#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 2e5 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t, n, a[N]; struct node{ int i; bool operator < (const node &A) const{ return a[i] < a[A.i]; } }; void solve(){ multiset s; vector res; for(int i = 1; i <= n; ++ i)s.insert({i}); while(s.size() > 1){ auto iter1 = s.begin(), iter2 = s.end(); -- iter2; int x = (*iter1).i, y = (*iter2).i; if(a[x] == 0){ s.erase(iter1); continue; } if(a[y] == 0){ s.erase(iter2); continue; } -- a[x], -- a[y]; s.erase(iter1), s.erase(iter2); s.insert({x}), s.insert({y}); res.push_back({x, y}); } cout << res.size() << endl; for(auto iter : res){ cout << iter.first << " " << iter.second << endl; } } int main(){ scanf("%d", &t); while(t -- ){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ scanf("%d", &a[i]); } solve(); } return 0; }
-
题意
你需要顺序将数组 p p p放入双端队列中,你需要使得放入之后的双端队列字典序最小。 -
解题思路
不难发现,双端队列的队头才是真正决定字典序大小的存在,所以我们需要使得队头最小即可。至此模拟操作。 -
AC代码
#includeE2. Array Optimization by Deque#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 2e5 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t, n, p[N]; void solve(){ deque q; q.push_back(p[1]); for(int i = 2; i <= n; ++ i){ int x = q.front(); if(p[i] < x)q.push_front(p[i]); else q.push_back(p[i]); } while(!q.empty()){ cout << q.front() << " "; q.pop_front(); } cout << endl; } int main(){ scanf("%d", &t); while(t -- ){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ scanf("%d", &p[i]); } solve(); } return 0; }
-
题意
你需要顺序将数组 p p p放入双端队列中,你需要使得放入之后的逆序对数量最少。 -
解题思路
由于放前面和放后面都是和队列中已有的数作比较,此次计算不会考虑队列中的顺序,所以我们放前面和放后面对之后要放的数是没有影响的,即只需要考虑放前面的代价和放后面的代价哪个小即可。实际上就是放前面的话,那么所有小于它的都要统计,放后面的话所有大于它的都要统计,这个利用树状数组实现。注意离散化。 -
AC代码
#includeF. Array Stabilization (AND version)#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 2e5 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t, n, a[N]; int c[N]; vector temp; int lowbit(int x){ return x & (- x); } void add(int x, int v){ while(x <= n){ c[x] += v; x += lowbit(x); } } ll get(int x){ ll ans = 0; while(x){ ans += c[x]; x -= lowbit(x); } return ans; } void solve(){ //离散化。可以记住这个离散化操作。 sort(temp.begin(), temp.end()); temp.erase(unique(temp.begin(), temp.end()), temp.end()); for(int i = 1; i <= n; ++ i){ a[i] = lower_bound(temp.begin(), temp.end(), a[i]) - temp.begin() + 1; } ll res = 0; for(int i = 1; i <= n; ++ i){ res += min(get(a[i] - 1), get(n) - get(a[i])); add(a[i], 1); } printf("%lldn", res); } int main(){ scanf("%d", &t); while(t -- ){ scanf("%d", &n); temp.resize(n); for(int i = 1; i <= n; ++ i){ scanf("%d", &a[i]); temp[i - 1] = a[i]; c[i] = 0; } solve(); } return 0; }
-
题意
给定一个数组n,下标从0开始,只包含0和1.给定d。现在需要你确定经过多少次操作后数组元素全为0。
操作为将数组循环移位 d d d,再和原数组相与得到新数组。 -
解题思路
由于与操作的特征,为 0 0 0的位置只会为 0 0 0,且其可以去将其它的位置变为 1 1 1,这种即可理解成移动,显然可以通过 b f s bfs bfs来解决,不过为了避免复杂度过大,所以我们需要减少重复的步骤(即已经放入过优先队列中的点)。至此模拟操作即可。 -
AC代码
#includeG. Minimal Coverage#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 1e6 + 10; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; // int t, n, d, a[N]; pii q[N]; int st, ed; void solve(){ st = 1, ed = 0; int res = 0; for(int i = 0; i < n; ++ i){ if(!a[i])q[++ ed] = {0, i}; } while(st <= ed){ auto x = q[st ++]; int u = (x.second + d) % n; if(a[u]){ a[u] = 0; q[++ ed] = {x.first + 1, u}; res = max(res, x.first + 1); } } for(int i = 0; i < n; ++ i){ if(a[i]){ res = -1; break; } } printf("%dn", res); } int main(){ scanf("%d", &t); while(t -- ){ scanf("%d%d", &n, &d); for(int i = 0; i < n; ++ i){ scanf("%d", &a[i]); } solve(); } return 0; }
-
题意
按顺序给定n条线段,从0开始,每一个线段可以正放也可以反放(+x,-x),但起始端必须放在上一个线段的结束位置。问n条放完之后最小覆盖区间长度。 -
解题思路
此题我们第一眼看到会发现要保存的信息太多,非常复杂,但实际上我们是可以进行一些约束的,即我们可以将左端点固定在 0 0 0处,即左端点不在 0 0 0处的我们可以进行偏移实现,这并不影响答案。
此题不然发现局部解可以构成最优解,因为我们每次都是要利用上一个结果来进行的。
所以我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]表示的是前 i i i个线段,且结束位置为 j j j的线段长度。
初始状态为dp[1][a[1]], 终止状态为dp[n][j]。
注意,我们需要一直保证左端点为0,这样便于我们处理,如果不为0,我们进行偏移即可,所以数组长度需要开到2000.
状态转移
如果正放,此时不用考虑越界(即不会出现数组下标为负数),那么我们只需要枚举 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j],
则dp[i][j + a[i]] = min(dp[i][j + a[i]], max(dp[i - 1][j], j + a[i]));注意更新线段长度,
如果反放,此时需要考虑越界,即j和a[i]的大小关系。
如果 j ≤ a [ i ] j leq a[i] j≤a[i],那么反放会超出0,此时我们可以将其进行偏移,即区间覆盖长度相对增加了a[i] - j。
则dp[i][0] = min(dp[i][0], dp[i - 1][j] + a[i] - j)。
否则,不会超出左边界,dp[i][j - a[i]] = min(dp[i][j - a[i]], dp[i - 1][j]);
至此题解。 -
AC代码
#include#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl using namespace std; typedef pair pii; typedef long long ll; const int N = 1e4 + 10, M = 2010; const int P = 1e9 + 7; const int INF = 0x3f3f3f3f; int t, n, a[N]; int dp[N][M];//dp[i][j]表示的是前i个线段,且结束位置为j的线段长度。 void solve(){ dp[1][a[1]] = a[1]; for(int i = 2; i <= n; ++ i){ for(int j = 0; j < M; ++ j){ if(j <= a[i]){ dp[i][0] = min(dp[i][0], dp[i - 1][j] + a[i] - j); } else{ dp[i][j - a[i]] = min(dp[i][j - a[i]], dp[i - 1][j]); } dp[i][j + a[i]] = min(dp[i][j + a[i]], max(dp[i - 1][j], j + a[i])); } } int res = INF; for(int j = 0; j < M; ++ j){ res = min(res, dp[n][j]); } printf("%dn", res); } int main(){ scanf("%d", &t); while(t -- ){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ scanf("%d", &a[i]); for(int j = 0; j < M; ++ j){ dp[i][j] = INF; } } solve(); } return 0; }



