从这里开始

Problem A Dividing a String

  猜想每段长度不超过2。然后dp即可。

  考虑最后一个长度大于等于3的一段,如果划成$1 + 2$会和后面相同,那么划成$2 + 1$,如果前一段和前面相同,那么把前一段和前面合并。每次操作后段数都不会减少。所以存在一种最优方案使得每段长度不超过2。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 2e5 + 5; template <typename T>
void vmax(T& a, T b) {
(a < b) && (a = b, 0);
} int n;
char s[N];
int f[N][2]; int main() {
scanf("%s", s + 1);
f[0][0] = 0, f[0][1] = -1e9;
n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
f[i][0] = f[i][1] = -1e9;
vmax(f[i][0], f[i - 1][1] + 1);
if (s[i] != s[i - 1])
vmax(f[i][0], f[i - 1][0] + 1);
if (i > 1) {
vmax(f[i][1], f[i - 2][0] + 1);
if (i > 3 && (s[i] != s[i - 2] || s[i - 1] != s[i - 3])) {
vmax(f[i][1], f[i - 2][1] + 1);
}
}
}
printf("%d\n", max(f[n][0], f[n][1]));
return 0;
}

Problem B RGB Balls

  假设红绿蓝三种颜色的求按顺序排列后分别为 $r_1, r_2, \cdots, r_n, g_1, g_2, \cdots, g_n, b_1, b_2, \cdots, b_n$。

  设$m_i = \min\{r_i, b_i, g_i\}, M_i = \max\{r_i, b_i, g_i\}$。猜想答案是$\sum M_i - m_i$。

  假设每个人拿到的最小标号的球标号递增,证明考虑前$k$个人至多选择前$k$个红球,白球和蓝球,所以第$k + 1$个人拿到的最小标号的球的标号至少为$m_{k + 1}$,所以$m_i$是第$i$个人拿到的最小标号的球的上界。同理可以证明第$i$个人拿到的最大标号的球的下界是$M_i$。

  然后根据一种球的类型来贪心就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;
} else {
exgcd(b, a % b, y, x);
y -= (a / b) * x;
}
} int inv(int a, int n) {
int x, y;
exgcd(a, n, x, y);
return (x < 0) ? (x + n) : (x);
} const int Mod = 998244353; template <const int Mod = :: Mod>
class Z {
public:
int v; Z() : v(0) { }
Z(int x) : v(x){ }
Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) {
int x;
return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
}
friend Z operator - (const Z& a, const Z& b) {
int x;
return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
}
friend Z operator * (const Z& a, const Z& b) {
return Z(a.v * 1ll * b.v);
}
friend Z operator ~(const Z& a) {
return inv(a.v, Mod);
}
friend Z operator - (const Z& a) {
return Z(0) - a;
}
Z& operator += (Z b) {
return *this = *this + b;
}
Z& operator -= (Z b) {
return *this = *this - b;
}
Z& operator *= (Z b) {
return *this = *this * b;
}
friend boolean operator == (const Z& a, const Z& b) {
return a.v == b.v;
}
}; Z<> qpow(Z<> a, int p) {
Z<> rt = Z<>(1), pa = a;
for ( ; p; p >>= 1, pa = pa * pa) {
if (p & 1) {
rt = rt * pa;
}
}
return rt;
} typedef Z<> Zi; const int N = 1e5 + 5; int n;
char s[N * 3]; int main() {
scanf("%d", &n);
scanf("%s", s + 1);
int r, g, b, rg, rb, gb;
r = g = b = 0;
rg = rb = gb = 0;
Zi ans = 1;
for (int i = 1; i <= n; i++)
ans *= i;
for (int i = 1; i <= 3 * n; i++) {
if (s[i] == 'R') {
if (gb) {
ans *= gb--;
} else if (g) {
ans *= g--;
rg++;
} else if (b) {
ans *= b--;
rb++;
} else {
r++;
}
} else if (s[i] == 'G') {
if (rb) {
ans *= rb--;
} else if (r) {
ans *= r--;
rg++;
} else if (b) {
ans *= b--;
gb++;
} else {
g++;
}
} else if (s[i] == 'B') {
if (rg) {
ans *= rg--;
} else if (r) {
ans *= r--;
rb++;
} else if (g) {
ans *= g--;
gb++;
} else {
b++;
}
}
}
printf("%d\n", ans.v);
return 0;
}

Problem C Numbers on a Circle

  考虑倒着做,操作变成一个数减去两边的和,如果一个数可以操作,那么它两边的数都不能操作。所以要么它达到它目标的值,要么它比两边的数小。

  不难发现操作1次,要么折半,要么达到目标的值,要么判出无解,所以总时间复杂度$O(n\log V)$。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 2e5 + 5; int n;
int A[N];
int B[N];
boolean inq[N]; boolean check(int p) {
return B[p] >= B[(p + n - 1) % n] + B[(p + 1) % n];
} int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", A + i);
}
for (int i = 0; i < n; i++) {
scanf("%d", B + i);
}
queue<int> Q;
for (int i = 0; i < n; i++) {
if (check(i)) {
inq[i] = true;
Q.push(i);
}
}
long long ans = 0;
while (!Q.empty()) {
int p = Q.front();
Q.pop();
inq[p] = false;
int pre = (p + n - 1) % n, suf = (p + 1) % n;
int sum = B[pre] + B[suf];
int t = max(0, (B[p] - A[p]) / sum);
if (!t && B[p] != A[p]) {
puts("-1");
return 0;
}
ans += t;
B[p] -= sum * t;
if (!inq[pre] && check(pre)) {
inq[pre] = true;
Q.push(pre);
}
if (!inq[suf] && check(suf)) {
inq[suf] = true;
Q.push(suf);
}
}
for (int i = 0; i < n; i++) {
if (A[i] ^ B[i]) {
puts("-1");
return 0;
}
}
printf("%lld\n", ans);
return 0;
}

Problem D Sorting a Grid

  考虑第一次移动需要达到的条件:属于目标同一行的数不在同一列。

  问题相当于给这样一个图染色:有$nm$个点,如果$(i,j)$和$(x, y)$有边相邻当且仅当它们属于同一行或者属于目标同一行。

  它所在的颜色标号等于它被换到的列号。

  考虑每次标出一种颜色。这个可以转成匹配问题,把原图的每个点看成边,两端点分别是它所在的两个团。

  这是一个正则二分图,所以必定有解。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int inf = (signed) (~0u >> 1); typedef class Edge {
public:
int ed, nx, r; Edge() { }
Edge(int ed, int nx, int r) : ed(ed), nx(nx), r(r) { }
} Edge; typedef class MapManager {
public:
int *h;
vector<Edge> E; MapManager() { }
MapManager(int n) {
h = new int[(n + 1)];
memset(h, -1, sizeof(int) * (n + 1));
}
~MapManager() {
delete[] h;
E.clear();
} void add_edge(int u, int v, int r) {
E.emplace_back(v, h[u], r);
h[u] = (signed) E.size() - 1;
}
void add_arc(int u, int v, int r) {
add_edge(u, v, r);
add_edge(v, u, 0);
}
Edge& operator [] (int p) {
return E[p];
}
} MapManager; typedef class Network {
public:
int S, T;
MapManager g; int *d, *h; Network(int S, int T) : S(S), T(T), g(T) {
d = new int[(T + 1)];
h = new int[(T + 1)];
}
~Network() {
delete[] d;
delete[] h;
} boolean bfs() {
queue<int> Q;
memset(d, -1, sizeof(int) * (T + 1));
d[S] = 0;
Q.push(S);
while (!Q.empty()) {
int e = Q.front();
Q.pop();
for (int i = g.h[e], eu; ~i; i = g[i].nx) {
eu = g[i].ed;
if (!g[i].r || ~d[eu])
continue;
d[eu] = d[e] + 1;
Q.push(eu);
}
}
return d[T] != -1;
} int dfs(int p, int mf) {
if (p == T || !mf)
return mf;
int flow = 0, f;
for (int& i = h[p], j, e; ~i; (i != -1) && (i = g[i].nx)) {
e = g[i].ed, j = i;
if (g[i].r && d[e] == d[p] + 1 && (f = dfs(e, min(mf, g[i].r))) > 0) {
g[j].r -= f;
g[j ^ 1].r += f;
flow += f;
mf -= f;
if (!mf)
break;
}
}
return flow;
} int dinic() {
int rt = 0;
while (bfs()) {
for (int i = 0; i <= T; i++)
h[i] = g.h[i];
rt += dfs(S, inf);
}
return rt;
} void add_edge(int u, int v, int r) {
g.add_arc(u, v, r);
}
} Network; const int N = 105; int n, m;
int a[N][N];
int b[N][N];
int c[N][N];
int id[N][N];
int col[N][N]; int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", a[i] + j);
}
}
for (int c = 1, T; c <= m; c++) {
Network network (0, T = n + n + 1);
for (int i = 1; i <= n; i++)
network.add_edge(0, i, 1);
for (int i = 1; i <= n; i++)
network.add_edge(i + n, T, 1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!col[i][j]) {
network.add_edge(i, (a[i][j] + m - 1) / m + n, 1);
id[i][j] = (signed) network.g.E.size() - 1;
}
}
}
network.dinic();
MapManager& g = network.g;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!col[i][j] && g[id[i][j]].r) {
col[i][j] = c;
}
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
b[i][col[i][j]] = a[i][j];
}
}
for (int i = 1; i <= n; i++, putchar('\n')) {
for (int j = 1; j <= m; j++) {
printf("%d ", b[i][j]);
c[(b[i][j] + m - 1) / m][j] = b[i][j];
}
}
for (int i = 1; i <= n; i++, putchar('\n')) {
for (int j = 1; j <= m; j++) {
printf("%d ", c[i][j]);
}
}
return 0;
}

Problem E Reversing and Concatenating

  假设最小的字符为a,如果末尾有$a$,那么可以是$a$的数量变为之前的$2^{K}$倍,否则要先用一次操作使得它在末尾。

  你发现使得$a$的长度达到这个长,方案是唯一的。

  你枚举开始可能的串,这个至多有$O(n)$个。直接计算就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 5005; int n, K;
int mxR[N];
char s[N << 1], t[N], ans[N]; void check(char* s, int k, char min_c) {
int L = 0, p = n;
while (s[n - L] == min_c)
L++, p--;
L <<= k;
if (L >= n) {
for (int i = 1; i <= n; i++)
putchar(min_c);
putchar('\n');
exit(0);
}
for (int i = 1; i <= L; i++) {
t[i] = min_c;
}
for (int i = L + 1; i <= n; i++)
t[i] = s[p--];
for (int i = 1; i <= n; i++)
if (t[i] ^ ans[i]) {
if (t[i] > ans[i]) {
return;
}
break;
}
for (int i = 1; i <= n; i++)
ans[i] = t[i];
} int main() {
scanf("%d%d", &n, &K);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++)
s[2 * n - i + 1] = s[i];
char x = 'z', y = 'a';
for (int i = 1; i <= n; i++) {
x = min(x, s[i]);
y = max(y, s[i]);
}
if (K >= 20 || x == y) {
for (int i = 1; i <= n; i++)
putchar(x);
putchar('\n');
return 0;
}
ans[1] = 'z' + 1;
if (s[n] == x)
check(s, K, x);
int mxL = 0;
for (int i = 1, j = 1; i <= n; i = j) {
if (s[i] != x) {
j++;
continue;
}
while (s[j] == s[i])
j++;
mxR[i] = j - i;
mxL = max(mxL, mxR[i]);
}
for (int i = 1; i <= n; i++) {
if (mxR[i] == mxL) {
check(s + (n - i + 1), K - 1, x);
}
}
puts(ans + 1);
return 0;
}

Problem F Counting of Subarrays

  可以先把问题转化成,你可以选择至少$l$个数,将它们合成一个数,问有多少个区间能合成一个数。

  考虑如何判断一个区间是否可行:

  • 取最小的元素$x$的连续段,假设长度为$L$,那么至多可以合成$\lfloor L / l \rfloor$个$x + 1$。
  • 递归执行。

  考虑怎么计算数量,考虑一个区间在它被合成一个可能的最小的数的时候计算。

  假设当前序列中最小的数位$x$,每次计算能合成的最小数为$x + 1$的区间个数,然后把$x$的连续段缩成若干个$x + 1$。

  要计算合成的数位$x + 1$的区间个数,只用求选择的连续至少$l$或者$1$个$x$的方案数。

  缩数后为了保证不算重,相当于要求每次选择的区间不能被这个连续段包含。把被包含的方案数减去。然后计算这个连续段内产生$k$个$x + 1$的可能的左端点数和右端点数。

  时间复杂度$O(n\log n)$

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean; const int N = 2e5 + 5; #define pii pair<int, int>
#define ll long long typedef class Segment {
public:
int l, r, x, y; Segment() { }
Segment(int l, int r, int x, int y) : l(l), r(r), x(x), y(y) { } boolean operator < (Segment b) const {
return l < b.l;
}
} Segment; int n, L;
int a[N];
pii b[N]; ll calc(vector<pii>& a) {
ll ret = 0, sum = 0;
for (int l = 0, r = L - 1; r < (signed) a.size(); l++, r++) {
ret += (sum += a[l].first) * a[r].second;
}
return ret;
} ll ans = 0;
int main() {
scanf("%d%d", &n, &L);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
b[i] = pii(a[i], i);
}
ans = n;
sort(b + 1, b + n + 1);
int pos = 1, val;
vector<Segment> vcur, vnxt, vadd;
while (true) {
if (!vcur.size()) {
if (pos > n) {
break;
} else {
val = b[pos].first;
}
} else {
val++;
}
// cerr << pos << " " << val << '\n';
vadd.clear();
while (pos <= n && b[pos].first == val)
vadd.emplace_back(b[pos].second, b[pos].second, 1, 1), pos++;
vnxt.resize(vcur.size() + vadd.size());
merge(vcur.begin(), vcur.end(), vadd.begin(), vadd.end(), vnxt.begin());
swap(vcur, vnxt);
vnxt.clear();
int num = vcur.size();
for (int i = 0, j = 0; i < num; i = ++j) {
while (j < num - 1 && vcur[j].r + 1 == vcur[j + 1].l)
j++;
int len = j - i + 1, cnt = len / L;
if (cnt) {
vector<pii> tmp;
for (int k = i; k <= j; k++)
tmp.emplace_back(vcur[k].x, vcur[k].y);
ans += calc(tmp);
tmp.clear();
tmp.resize(cnt, pii(0, 0));
for (int k = L - 1; k < len; k++) {
tmp[cnt - (k - L + 1) / L - 1].first += vcur[j - k].x;
}
for (int k = L - 1; k < len; k++) {
tmp[(k - L + 1) / L].second += vcur[i + k].y;
}
ans -= calc(tmp);
for (int k = 0; k < cnt; k++)
vnxt.emplace_back(vcur[i].l + k, vcur[i].l + k, tmp[k].first, tmp[k].second);
vnxt.back().r = vcur[j].r;
}
}
swap(vcur, vnxt);
vnxt.clear();
}
printf("%lld\n", ans);
return 0;
}

AtCoder Grand Contest 037 简要题解的更多相关文章

  1. AtCoder Grand Contest 031 简要题解

    AtCoder Grand Contest 031 Atcoder A - Colorful Subsequence description 求\(s\)中本质不同子序列的个数模\(10^9+7\). ...

  2. AtCoder Grand Contest 039 简要题解

    从这里开始 比赛目录 Problem A Connection and Disconnection 简单讨论即可. Code #include <bits/stdc++.h> using ...

  3. AtCoder Grand Contest 040 简要题解

    从这里开始 比赛目录 A < B < E < D < C = F,心情简单.jpg. Problem A >< 把峰谷都设成 0. Code #include &l ...

  4. AtCoder Grand Contest 035 简要题解

    从这里开始 题目目录 Problem A XOR Circle 你发现,权值的循环节为 $a_0, a_1, a_0\oplus a_1$,然后暴力即可. Code #include <bits ...

  5. AtCoder Grand Contest 036 简要题解

    从这里开始 比赛目录 Problem A Triangle 考虑把三角形移到和坐标轴相交,即 然后能够用坐标比较简单地计算面积,简单构造一下就行了. Code #include <bits/st ...

  6. AtCoder Grand Contest 038 简要题解

    从这里开始 比赛目录 Problem A 01 Matrix Code #include <bits/stdc++.h> using namespace std; typedef bool ...

  7. AtCoder Grand Contest 037题解

    传送门 \(A\) 直接把每个字母作为一个字符串,如果某个串和它前面的相同,那么就把这个字母和它后面那个字母接起来.然而我并不会证明这个贪心的正确性 //quming #include<bits ...

  8. AtCoder Grand Contest 021完整题解

    提示:如果公式挂了请多刷新几次,MathJex的公式渲染速度并不是那么理想. 总的来说,还是自己太弱了啊.只做了T1,还WA了两发.今天还有一场CodeForces,晚上0点qwq... 题解还是要好 ...

  9. AtCoder Grand Contest 037

    Preface 这篇咕了可能快一个月了吧,正好今天晚上不想做题就来补博客 现在还不去复习初赛我感觉我还是挺刚的(微笑) A - Dividing a String 考虑最好情况把每个字符串当作一个来看 ...

随机推荐

  1. Linux iSCSI 磁盘共享管理

    Linux iSCSI 磁盘共享管理 iSCSI 服务是通过服务端(target)与客户端(initiator)的形式来提供服务.iSCSI 服务端用于存放存储源的服务器,将磁盘空间共享给客户使用,客 ...

  2. QT+OpenGL(02)-- zlib库的编译

    1.zlib库的下载 http://www.zlib.net/ zlib1211.zip 2.解压 3.进入  zlib1211\zlib-1.2.11\contrib\vstudio\vc14 目录 ...

  3. c# System.Net.Sockets =》TcpListener用法

     private TcpListener _listener;#region 初始化 listener public override void Init() { try { DevInfo.Read ...

  4. Python简单的get和post请求

    1.json 模块提供了一种很简单的方式来编码和解码JSON数据. 其中两个主要的函数是 json.dumps() 和 json.loads() , 要比其他序列化函数库如pickle的接口少得多. ...

  5. Web Api 模型绑定 二

    [https://docs.microsoft.com/zh-cn/aspnet/core/web-api/?view=aspnetcore-2.2] 1.ApiController属性使模型验证错误 ...

  6. maven 学习---使用Maven清理项目

    在基于Maven的项目中,很多缓存输出在“target”文件夹中.如果想建立项目部署,必须确保清理所有缓存的输出,从面能够随时获得最新的部署. 要清理项目缓存的输出,发出以下命令: mvn clean ...

  7. ANDROID培训准备资料之BroadcastReceiver

    BroacastReceiver的启动方式? (1)     创建需要启动的BroadcastReceiver的Intent. (2)     调用context的sendBroadcast()或者s ...

  8. 图解Java数据结构之双向链表

    上一篇文章说到了单链表,也通过案例具体实现了一下,但是单链表的缺点也显而易见. 单向链表查找的方向只能是一个方向 单向链表不能自我删除,需要靠辅助节点 而双向链表则能够很轻松地实现上面的功能. 何为双 ...

  9. 强大的 strace 工具

    什么是 strace strace是Linux环境下的一款程序调试工具,用来监察一个应用程序所使用的系统调用. Strace是一个简单的跟踪系统调用执行的工具.在其最简单的形式中,它可以从开始到结束跟 ...

  10. Android APP之WebView如何校验SSL证书

    Android系统的碎片化很严重,并且手机日期不正确.手机根证书异常.com.google.android.webview BUG等各种原因,都会导致WebViewClient无法访问HTTPS站点. ...