0118ACM自闭赛
Posted zqytcl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0118ACM自闭赛相关的知识,希望对你有一定的参考价值。
<前言>
ACM赛:vjudge 20200118acm赛
好好记录一下自己干了啥。
就是在颓废。
<正文>
<考试经历>
今天acm赛队友随便找了个,结果就是个懒货,题都不写几道,哎。
他写了一道最水的E,然后两道是我写的,我挺想骂人。
这次的题简直毒瘤,无法描述。
密码:nicaibudao
上场我先说队友(zjh)分配一下题。他就直接捡了个E题,几分钟爆切,全场一血,我当时吓死了,然而考试后得知E题最水,代码就几行。
我拿到了D题,稍微看了下,做过原题或是一样的题,几分钟也写完了,却不是一血。区间最小值和不就是维护一个单调栈然后瞎搞就行了。来回各一趟维护出数组,再直接计算。
接着全场陷入瓶颈期,很长时间没人切出除了DE外的题。
然后先不管fjz的吊打全场,我开始磕F题,队友写完E之后就开始无(da)所(shui)事(te)事(shui),他们都去吃饭了,我在机房磕。
一直在6点WA,不知为何。万念俱灰间加了个long long 他还就真过了。。(掀桌)
然后时间就是各题看看,也磕不出来惹。。。自闭
最后只有rank5,没泡面,但是我两顿没吃了啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
晚上,也就是现在,开始四处觅食,感觉都没吃的,就自闭写blog去了。
游戏体验极差,全程被吊打。也就一开始的时候成为了好久的rank1,因为大家都做不出来。
贴一张骄傲的图(PS:号是队友的,取名雨我无瓜):
部分题解
A - Negative Cycle AtCoder - 5147
A题不会,抄了一下,至少自己写不出来,看着还好理解的样子。
(mathrm{Code:})
#include <bits/stdc++.h>
#define N 510
#define int long long
using namespace std;
int a[N][N] = {};
int read()
{
int s = 0, w = 1;
char c = getchar();
while ((c < '0' || c > '9') && c != '-')
c = getchar();
if (c == '-')
c = getchar(), w = -1;
while (c <= '9' && c >= '0')
s = (s << 3) + (s << 1) + c - '0', c = getchar();
return s * w;
}
int n;
int b[N][N] = {}, c[N][N] = {};
int f[N][N] = {};
signed main()
{
n = read();
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (i != j)
a[i][j] = read();
for (int i = 1; i <= n; ++i)
for (int j = n; j >= i + 1; --j)
{
b[i][j] = b[i][j + 1];
for (int k = 1; k <= i; ++k)
b[i][j] += a[j][k];
}
for (int i = 1; i <= n; ++i)
for (int j = i + 1; j <= n + 1; ++j)
{
c[i][j] = c[i][j - 1];
for (int k = i; k <= j - 1; ++k)
c[i][j] += a[k][j];
}
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for (int i = 0; i <= n; ++i)
for (int j = i; j <= n; ++j)
if (f[i][j] < 1e18)
for (int k = j + 1; k <= n + 1; ++k)
f[j][k] = min(f[j][k], f[i][j] + c[j + 1][k] + b[j][k + 1] - b[i][k + 1]);
int minn = 1e18;
for (int i = 0; i <= n; ++i)
minn = min(minn, f[i][n + 1]);
printf("%lld
", minn);
return 0;
}
B - Robots and Exits - AtCoder - 4353
B题干脆不写了,刷题记录表里随便写点就行了。
C - 和風いろはちゃん / Iroha and Haiku - AtCoder - 1975
C题同。
D - Minimum Sum - AtCoder - 2060
稍微讲一下D,这题就是求每段子区间的最小值和。
每段考虑并不好做,考虑计算每个值的贡献。
一个位于(i)处的值(x)能影响到的区间一定是以它为最小值的区间,我们只需找到左右第一个比它小的值位置,这两个位置之间的区间都是以(x)为最小值得区间,计算个数以及贡献即可。
[ egin{aligned} & sum_{1leq l leq r leq n} min(a_l,a_{l+1}……a_{r-1},a_r) &= sum_{min(a_l,a_{l+1}……a_{r-1},a_r)=x} (r-i+1) imes (i-l+1) imes x end{aligned} ]
两个单调栈一开,维护左/右比x小的第一个数位置,就分别是l、r了。
(mathrm{Code:})
#include <bits/stdc++.h>
#define N 200010
#define int long long
using namespace std;
int n;
int a[N] = {};
int read()
{
int s = 0, w = 1;
char c = getchar();
while ((c < '0' || c > '9') && c != '-')
c = getchar();
if (c == '-')
w = -1, c = getchar();
while (c <= '9' && c >= '0')
s = (s << 3) + (s << 1) + c - '0', c = getchar();
return s * w;
}
int pre[N] = {}, suf[N] = {};
int q[N << 1] = {}, h = 1, t = 0;
void push1(int x)
{
while (h <= t && a[q[t]] >= a[x])
suf[q[t]] = x - 1, --t;
q[++t] = x;
}
void push2(int x)
{
while (h <= t && a[q[t]] >= a[x])
pre[q[t]] = x + 1, --t;
q[++t] = x;
}
signed main()
{
n = read();
for (int i = 1; i <= n; ++i)
a[i] = read(), pre[i] = 1, suf[i] = n;
for (int i = 1; i <= n; ++i)
push1(i);
h = 1; t = 0;
for (int i = n; i >= 1; --i)
push2(i);
int sum = 0;
for (int i = 1; i <= n; ++i)
{
sum += (i - pre[i] + 1) * (suf[i] - i + 1) * a[i];
}
printf("%lld
", sum);
return 0;
}
挺水一题,不知为何被拖了很久,没拿到一血。
E - STring - AtCoder - 2059
题目给一个巨tm大的操作次数((10^{10000}))就是告诉你随便删就好了,管够。
所以E作为本场最水被过了。
zjh的(mathrm{Code:})
#include <bits/stdc++.h>
using namespace std ;
inline int read ()
{
char c = getchar () ; int x = 0 , f = 1 ;
while (c < '0' || c > '9') { if (c == '-') f = -1 ; c = getchar () ; }
while (c >= '0' && c <= '9'){ x = x * 10 + c - '0' ; c = getchar () ; }
return x * f ;
}
string s ;
int st[200005] , ans , top ;
int main ()
{
cin >> s ;
ans = s.size () ;
for (int i = 0 ; i < s.size () ; i ++)
{
if (s[i] == 'S') st[++ top] = 'S' ;
else if (top) top -- , ans -= 2 ;
}
cout << ans ;
}
F - Pizza Delivery - Gym - 101986F
这题也是我写的。
一直被long long 卡,要是开了long long 我午饭时间后的第二发就可以A,能吃上一顿饭呢。(但是晚上吃到泡面还是挺血赚的)
之前我的分类讨论中,一开始我就分了最短路边和非最短路边,两者又进行了细分,后来发现不必要,对于非最短路边,直接判断就好。
最短路边略麻烦。
你要确定删去之后会不会对最短路径有影响,就要判断该边是否是属于最短路的边(注意最短路可能有多条)组成的图的割边了。
若不是桥,说明会有其他路径代替本路径,取反这条边没啥卵用。
若是桥,则它取反之后无法找到更短或相等的路径代替,是可以证明的,但是我不会。
那么局势变得明朗起来:
- 找最短路,标记所有最短路上的边。
- 在最短路径组成的图中找割边,可用两种方式实现。
对于最短路边,若是割边则翻转会产生影响。不是割边则不会。
对于非最短路边,强行把它放在最短路上看看和原最短路比起来怎么样。
割边的两种查找方式:重新建图tarjan,最短路路径计数。
贴出计数法判割边的代码:
(mathrm{Code:})
#include <bits/stdc++.h>
#define N 200010
#define int long long
#define inf 0x3f3f3f3f
#define mp(x, y) make_pair(x, y)
using namespace std;
int n, m;
struct gragh
{
int to[N] = {}, net[N] = {}, w[N] = {};
int from[N] = {};
int fl[N] = {}, len = 0;
inline void inc(int x, int y, int z)
{
to[++len] = y;
from[len] = x;
w[len] = z;
net[len] = fl[x];
fl[x] = len;
}
} G[2];
struct tree
{
int to[N << 1] = {}, net[N] = {}, w[N] = {};
int fl[N] = {}, len = 0;
inline void inc(int x, int y, int z)
{
to[++len] = y;
w[len] = z;
net[len] = fl[x];
fl[x] = len;
}
} T;
int read()
{
int s = 0, w = 1;
char c = getchar();
while ((c < '0' || c > '9') && c != '-')
c = getchar();
if (c == '-')
c = getchar(), w = -1;
while (c <= '9' && c >= '0')
s = (s << 3) + (s << 1) + c - '0', c = getchar();
return s * w;
}
int vis[N] = {}, dis[2][N] = {};
int is[2][N] = {}, si[2][N] = {};
void DJ(int st, int p)
{
memset(dis[p], 0x3f, sizeof(dis[p]));
memset(vis, 0, sizeof(vis));
memset(is[p], 0, sizeof(is[p]));
dis[p][st] = 0;
si[p][st] = 1;
priority_queue<pair<int, int> >q;
q.push(mp(0, st));
while (!q.empty())
{
int t = q.top().second;
q.pop();
if (vis[t])
continue;
vis[t] = 1;
for (int i = G[p].fl[t]; i; i = G[p].net[i])
{
int v = G[p].to[i];
if (dis[p][v] > dis[p][t] + G[p].w[i])
{
dis[p][v] = dis[p][t] + G[p].w[i];
si[p][v] = si[p][t];
q.push(mp(-dis[p][v], v));
}
else if (dis[p][v] == dis[p][t] + G[p].w[i])
si[p][v] += si[p][t], si[p][v] %= inf;
}
}
}
signed main()
{
n = read();
m = read();
for (int i = 1; i <= m; ++i)
{
int x = read(), y = read(), z = read();
G[0].inc(x, y, z);
G[1].inc(y, x, z);
}
DJ(1, 0);
DJ(2, 1);
int vis[N] = {};
for (int i = 1; i <= G[0].len; ++i)
if (dis[0][G[0].from[i]] + G[0].w[i] + dis[1][G[0].to[i]] == dis[0][2])
is[0][i] = 1,
vis[G[0].from[i]] = vis[G[0].to[i]] = 1;
for (int i = 1; i <= G[0].len; ++i)
{
if (is[0][i])
{
int u = G[0].from[i], v = G[0].to[i];
if (si[0][u] * si[1][v] % inf == si[0][2])
{
puts("SAD");
}
else
{
puts("SOSO");
}
}
else
{
int u = G[0].from[i], v = G[0].to[i];
int minn = dis[0][v] + G[0].w[i] + dis[1][u];
if (minn < dis[0][2])
puts("HAPPY");
else
puts("SOSO");
}
}
return 0;
}
G - New Year and Old Subsequence - CodeForces - 750E
毒瘤题。线段树优化广义矩乘,其实就是加速dp的递推。
我们将几种状态进行编号:(emptyset,2,20,201,2017)分别编号01234.
这样对于每一个递推,就有一种编号间的递推转移了。
重定义乘法为dp的递推,在本题中为加法变取min,乘法变加法。
考虑构造转移矩阵。
对于当前字符为‘2‘时:若要保持空集,则需删去,代价为1,若不变成空集,则可以不花费代价。其他每个状态保持都无需花费,故转移为:
[
mathrm{
egin{bmatrix}
0&1&inf&inf&infinf&0&inf&inf&infinf&inf&0&inf&infinf&inf&inf&0&infinf&inf&inf&inf&0
end{bmatrix}
}
]
其他字符类似。
对于字符‘6‘,因为无论如何不能要它,所以从(201、2017)的转移保持代价为1,因为题目为子序列,所以2017这种状态也不能加6
[ egin{bmatrix} 0&inf&inf&inf&infinf&0&inf&inf&infinf&inf&0&inf&infinf&inf&inf&1&infinf&inf&inf&inf&1 end{bmatrix} ]
用线段树维护运算,注意左右子矩阵顺序,即使这题中没问题,但是其他的广义运算是要注意的。
代码:
(mathrm{Code:})
#include <bits/stdc++.h>
#define int long long
#define N 200010
using namespace std;
int n, m;
struct matrix
{
int a[5][5];
int n, m;
matrix()
{
n = m = 0;
memset(a, 0x3f, sizeof(a));
}
inline int *operator [](int x)
{
return a[x];
}
matrix operator *(matrix b)
{
matrix c;
c.n = n; c.m = b.m;
for (int i = 0; i < c.n; ++i)
for (int j = 0; j < c.m; ++j)
for (int k = 0; k < m; ++k)
c.a[i][j] = min(c.a[i][j], a[i][k] + b.a[k][j]);
return c;
}
};
struct segment_tree
{
int l, r;
matrix k;
} tr[N << 2];
int read()
{
int s = 0, w = 1;
char c = getchar();
while ((c < '0' || c > '9') && c != '-')
c = getchar();
if (c == '-')
w = -1, c = getchar();
while (c <= '9' && c >= '0')
s = (s << 3) + (s << 1) + c - '0', c = getchar();
return s * w;
}
char s[N] = {};
inline void push(int p)
{
tr[p].k = tr[p << 1].k * tr[p << 1 | 1].k;
}
void build(int p, int l, int r)
{
tr[p].l = l; tr[p].r = r;
if (l == r)
{
tr[p].k.n = tr[p].k.m = 5;
for (int i = 0; i < 5; ++i)
tr[p].k[i][i] = 0;
if (s[l] == '2')
tr[p].k[0][0] = 1, tr[p].k[0][1] = 0;
if (s[l] == '0')
tr[p].k[1][1] = 1, tr[p].k[1][2] = 0;
if (s[l] == '1')
tr[p].k[2][2] = 1, tr[p].k[2][3] = 0;
if (s[l] == '7')
tr[p].k[3][3] = 1, tr[p].k[3][4] = 0;
if (s[l] == '6')
tr[p].k[3][3] = 1, tr[p].k[4][4] = 1;
return;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
push(p);
}
matrix ask(int p, int l, int r)
{
if (l <= tr[p].l && tr[p].r <= r)
return tr[p].k;
int mid = (tr[p].l + tr[p].r) >> 1;
if (r <= mid)
return ask(p << 1, l, r);
if (l > mid)
return ask(p << 1 | 1, l, r);
return ask(p << 1, l, r) * ask(p << 1 | 1, l, r);
}
signed main()
{
n = read();
m = read();
cin >> (s + 1);
build(1, 1, n);
for (int i = 1; i <= m; ++i)
{
int x = read(), y = read();
matrix ans = ask(1, x, y);
printf("%lld
", ans[0][4] > n ? -1 : ans[0][4]);
}
return 0;
}
H - Shorten Diameter - AtCoder - 1981
没搞,听说第二水,也不想写了。
<后记>
然后就是这样了,一场ACM打完,神清气爽,回头刷题。
以上是关于0118ACM自闭赛的主要内容,如果未能解决你的问题,请参考以下文章