AtCoder Beginner Contest 343 - AtCoder
思路全出了,手搓全唐了,好消息是前几题手很快,上分了
A - Wrong Answer
题意:
输出一个不等与A+B的范围在0到9内的整数
代码:
void solve()
{
int a, b;
scanf("%d%d", &a, &b);
if (a + b)printf("0\n");
else printf("1\n");
}
B - Adjacency Matrix
题意:
给出一张有N个点的简单无向图,邻接表中的 Ai,j = 1表示 i, j 之间有边相连,对于每个点输出所有直接与它相连的点的编号
代码:
void solve()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
vector<int>v;
for (int j = 1, x; j <= n; ++j)
{
scanf("%d", &x);
if (x)v.push_back(j);
}
for (auto j : v)printf("%d ", j);
printf("\n");
}
}
C - 343
题意:
输出小于N的最大的回文立方数
题解:
暴力枚举所有立方数并暴力判断回文数即可
bool check(LL x)//判断x是不是回文数
{
vector<int>v;
while (x)
v.push_back(x % 10), x /= 10;
int n = v.size() - 1;
for (int i = 0; i <= n; ++i)
if (v[i] != v[n - i])return 0;
return 1;
}
void solve()
{
LL n, mx = 1;
scanf("%lld", &n);
for (LL i = 1; i * i * i <= n; ++i)//枚举立方数
if (check(i * i * i))mx = i * i * i;
printf("%lld\n", mx);
}
D - Diversity of Scores
题意:
有N人参加比赛,编号分别为1到N,每人都有一个积分,这个积分初始为0,这些分数将进行共计T次变化,第 i 次变化Ai, Bi表示第Ai个人获得了Bi分,你需要在每次分数发现变化后,输出当前不同的分数的数量
题解:
数组A维护一下每个人的分数,map<x,y>表示获得x分的人数共有y人,若每次修改前mp[A[x]] = 1则本次修改会使分数为A[x]的人数变为0,此时删除A[x]即可,具体维护方式见代码,修改之后的答案即mp.size(),注意数据范围爆int
LL a[N];
map<LL, int>mp;
void solve()
{
int n, q;
scanf("%d%d", &n, &q);
mp[0] = n;
while (q--)
{
int x, y;
scanf("%d%d", &x, &y);
if (--mp[a[x]] == 0)
mp.erase(a[x]);
a[x] += y;
++mp[a[x]];
printf("%d\n", mp.size());
}
}
E - 7x7x7
题意:
在三维空间中有3个7x7x7的正方体,问是否存在一种放置方式使得同时被三个正方体包含的区域体积为V3,同时仅被两个正方体包含的区域体积为V2,仅被一个正方体包含的区域体积为V1,若存在输出三个正方体的位置
题解:
设第一个正方体的位置恒为(0, 0, 0),暴力枚举第二、三个正方体的位置,check每种放置方法是否合法即可,具体check方式见代码
对于枚举位置,显然让三个正方体离太远没有意义,我们只需要枚举所有能使得三个正方体相交/至少有一个面重合的最大范围即可,则我们可以枚举第二个正方体的三个维度的坐标值在(0, 7)范围,第三个正方体的坐标值在(-7, 14)范围即可(不知道还能不能更小)
int get_d(int x, int tx)//两个正方体的重合部分x/y/z方向上的边长
{
return max(0, min(x, tx) + 7 - max(x, tx));
}
int get_d(int sx, int x, int tx)//三个正方体的重合部分x/y/z方向上的边长
{
return max(0, min({ sx,x,tx }) + 7 - max({ sx,x,tx }));
}
int fun(int ax, int ay, int az, int bx, int by, int bz)//两个7x7x7的正方体的重合体积
{
int dx = get_d(ax, bx), dy = get_d(ay, by), dz = get_d(az, bz);
return dx * dy * dz;
}
void solve()
{
int v1, v2, v3;
scanf("%d%d%d", &v1, &v2, &v3);
for (int x = 0; x <= 7; ++x)
{
for (int y = 0; y <= 7; ++y)
{
for (int z = 0; z <= 7; ++z)
{
for (int tx = -7; tx <= 14; ++tx)
{
for (int ty = -7; ty <= 14; ++ty)
{
for (int tz = -7; tz <= 14; ++tz)
{
int dx = get_d(0, x, tx), dy = get_d(0, y, ty), dz = get_d(0, z, tz);
int tv3 = dx * dy * dz;
if (tv3 != v3)continue;
int tv2 = fun(0, 0, 0, x, y, z) + fun(0, 0, 0, tx, ty, tz) + fun(x, y, z, tx, ty, tz) - 3 * tv3;//容斥
if (tv2 != v2)continue;
int tv1 = 3 * 7 * 7 * 7 - 3 * tv3 - 2 * tv2;//容斥
if (tv1 != v1)continue;
printf("Yes\n");
printf("0 0 0 %d %d %d %d %d %d\n", x, y, z, tx, ty, tz);
return;
}
}
}
}
}
}
printf("No\n");
}
F - Second Largest Query
题意:
给出一个长度为N的数组A,对该数组进行T次操作:
1 p x:修改Ap的值为x
2 l r:查询区间l, r内的次大值的数量
题解:
线段树维护即可,具体见代码
#define ls(i) (i<<1)
#define rs(i) (i<<1|1)
#define mx(i,j) (tr[i].mx[j])
#define cnt(i,j) (tr[i].cnt[j])
struct node
{
int mx[2], cnt[2];//最大值,次大值;最大值的数量,次大值的数量
}tr[N << 2];
node push_up(node x, node y)
{
map<int, int>mp;
mp[x.mx[0]] += x.cnt[0];
mp[x.mx[1]] += x.cnt[1];
mp[y.mx[0]] += y.cnt[0];
mp[y.mx[1]] += y.cnt[1];
auto it = mp.rbegin();
node res = { {it->first,next(it)->first},{it->second,next(it)->second} };
return res;
}
void build(int l, int r, int i)
{
if (l == r)
{
scanf("%d", &mx(i, 0));
cnt(i, 0) = 1;
return;
}
int mid = l + r >> 1;
build(l, mid, ls(i));
build(mid + 1, r, rs(i));
tr[i] = push_up(tr[ls(i)], tr[rs(i)]);
}
void updata(int pos, int data, int l, int r, int i)
{
if (l == r)
{
mx(i, 0) = data;
return;
}
int mid = l + r >> 1;
if (pos <= mid)updata(pos, data, l, mid, ls(i));
else updata(pos, data, mid + 1, r, rs(i));
tr[i] = push_up(tr[ls(i)], tr[rs(i)]);
}
node query(int ql, int qr, int l, int r, int i)
{
if (ql <= l && r <= qr)
return tr[i];
int mid = l + r >> 1;
node res = { {0,0},{0,0} };
if (ql <= mid)res = push_up(res, query(ql, qr, l, mid, ls(i)));
if (qr > mid)res = push_up(res, query(ql, qr, mid + 1, r, rs(i)));
return res;
}
void solve()
{
int n, q;
scanf("%d%d", &n, &q);
build(1, n, 1);
while (q--)
{
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
if (op == 1)
updata(x, y, 1, n, 1);
else
printf("%d\n", query(x, y, 1, n, 1).cnt[1]);
}
}
G - Compress Strings
题意:
给出N个字符串S,求字符串T的最小长度使得所有的Si都是T的子串
题解:
相当于是像拼积木一样拼接所有字符串,当Si的后缀等于Sj的前缀时就可以拼接Si与Sj的同时删去Sj的那一段相同的前缀,同时当Sj是Si的子串时则可以直接把Sj塞进Si里,不产生更多的长度,求最小的拼接之后的总长度
对于求Si的后缀与Sj的前缀的最大匹配长度和Sj是否是Si的子串这类字符串匹配问题可以用Z函数、KMP或者哈希处理
对于求最小总长度,我们可以发现N很小,因此我们可以使用状压dp,dp[f][i]表示已经拼接的集合为f,并且当前的拼接字符串的后缀为Si的后缀时的最小长度,此时往拼接字符串末尾拼接字符Sj存在两种转移:
1、Sj是Si的子串,则往集合中加入j,拼接字符串后缀仍为Si,不产生多余花费;
2、Sj不是Si的子串,往集合中加入j,拼接字符串后后缀变更为Sj,花费为 Sj的长度 - Si后缀与Sj前缀的最大匹配长度。
从集合中元素数量从少到多枚举状态转移即可
思路很快,但搓代码搓的很慢,比赛结束几分钟才搓完然后爆wa,赛后20分钟才调完的bug,写的太唐了,用的字符串哈希判断的字符串匹配,赛后顺便整理了一下模板,感觉这个板子写的挺还帅的
const LL N = 2e1 + 10, INF = 0x3f3f3f3f;
//个人手搓的多模哈希板子,感觉挺好使的
const int LEN = 2e5 + 10, H = 3, L = 1e18, R = 2e9;//最长的字符串长度, 哈希数量, 随机数生成范围
mt19937_64 rnd(random_device{}());//生成随机数
uniform_int_distribution<int> dist(L, R);
typedef array<LL, H> Hash;
Hash P, mod, p[LEN];
Hash operator*(Hash x, Hash y)//重载运算符是好东西
{
Hash res;
for (int i = 0; i < H; ++i)
res[i] = x[i] * y[i] % mod[i];
return res;
}
Hash operator+(Hash x, Hash y)
{
Hash res;
for (int i = 0; i < H; ++i)
res[i] = (x[i] + y[i]) % mod[i];
return res;
}
Hash operator-(Hash x, Hash y)
{
Hash res;
for (int i = 0; i < H; ++i)
res[i] = (x[i] - y[i] + mod[i]) % mod[i];
return res;
}
void fill(Hash& hs, LL x)
{
for (auto& i : hs)i = x;
}
void init()
{
for (int i = 0; i < H; ++i)
P[i] = dist(rnd), mod[i] = dist(rnd);
fill(p[0], 1);
for (int i = 1; i < LEN; ++i)
p[i] = p[i - 1] * P;
}
vector<Hash> build_hash(string& str)
{
Hash t;
fill(t, 0);
vector<Hash>hs{ t };
for (auto& c : str)
{
fill(t, c);
hs.push_back(hs.back() * P + t);
}
return hs;
}
Hash get_hash(vector<Hash>& hs, int l, int r)
{
return hs[r] - hs[l - 1] * p[r - l + 1];
}
//哈希板子
string s[N];
vector<Hash>h[N];
int e[N][N], dp[1 << 20][N];
void solve()
{
init();
int n;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
cin >> s[i];
h[i] = build_hash(s[i]);
}
memset(e, 0x3f, sizeof e);
for (int i = 1; i <= n; ++i)//i包含j
{
for (int j = 1; j <= n; ++j)if (i != j && s[i].size() >= s[j].size())
{
Hash hj = get_hash(h[j], 1, s[j].size());
for (int l = 1; l + s[j].size() - 1 <= s[i].size(); ++l)
{
if (get_hash(h[i], l, l + s[j].size() - 1) == hj)
{
e[i][j] = 0;
break;
}
}
}
}
for (int i = 1; i <= n; ++i)//i拼接j的代价
{
for (int j = 1; j <= n; ++j)if (i != j)
{
int len = 0;//相同部分长度
for (int t = 1; t <= s[i].size() && t <= s[j].size(); ++t)
{
if (get_hash(h[i], s[i].size() - t + 1, s[i].size()) == get_hash(h[j], 1, t))
len = t;
}
e[i][j] = min(e[i][j], (int)s[j].size() - len);
}
}
vector<int>v[21];//预处理所有元素数量为i的集合vi
for (int f = 1; f < 1 << 20; ++f)
v[bitset<21>(f).count()].push_back(f);
memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; ++i)//初始化dp数组
dp[1 << i - 1][i] = s[i].size();
for (int s = 1; s < n; ++s)
{
for (auto f : v[s])
{
for (int u = 1; u <= n; ++u)if ((f >> u - 1) & 1)
{
for (int tv = 1; tv <= n; ++tv)if (!((f >> tv - 1) & 1))
{
if (e[u][tv] == 0)dp[f + (1 << tv - 1)][u] = min(dp[f + (1 << tv - 1)][u], dp[f][u]);
else dp[f + (1 << tv - 1)][tv] = min(dp[f + (1 << tv - 1)][tv], dp[f][u] + e[u][tv]);
}
}
}
}
int ans = INF;
for (int i = 1; i <= n; ++i)
ans = min(ans, dp[(1 << n) - 1][i]);
printf("%d\n", ans);
}