Atcoder Beginner Contest 267

C. Index × A(Continuous ver.)

题目大意

给定一个序列A,找一长度为 m的连续子序列B,其 i=1mi×Bi值最大,求该最大值。

解题思路

枚举该子序列即可,注意到下一个子序列的值可以由上一个子序列的值O(1)转移过来,它们就相差了一个区间和sum[i1]sum[i1m]以及 m×A[i]

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<LL> a(n + 1, 0);
LL ans = -1e18;
LL sum = 0;
LL tmp = 0;
for(int i = 1; i <= n; ++ i){
cin >> a[i];
if (i <= m){
sum += a[i];
tmp += a[i] * i;
}else{
ans = max(ans, tmp);
tmp -= sum;
sum -= a[i - m];
sum += a[i];
tmp += a[i] * m;
}
}
ans = max(ans, tmp);
cout << ans << endl;
return 0;
}


D. Index × A(Not Continuous ver.)

题目大意

给定一个序列A,找一长度为 m的(不连续)子序列B,其 i=1mi×Bi值最大,求该最大值。

解题思路

dp[i][j]表示前 i个数选了 j个数的最大值,对于第i+1个数考虑选或不选转移即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<LL>> dp(n + 1, vector<LL>(m + 1, -1e18));
dp[0][0] = 0;
for(int i = 1; i <= n; ++ i){
LL a;
cin >> a;
dp[i][0] = 0;
for(int j = 1, up = min(i, m); j <= up; ++ j){
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + a * j);
}
}
cout << dp[n][m] << '\n';
return 0;
}


E. Erasing Vertices 2

题目大意

给定一张n个点 m条边的无向图,点有点权。需要进行n次操作,每次操作,选择一个点a,并移除该点以及与该点相连的所有边,其代价是与点a直接相连的所有点权和。问所有操作的代价的最大值的最小值是多少。

解题思路

我们每次移除当前代价最小的那个,那么最终一定是最大值最小的情况。于是我们用优先队列维护这个代价最小的那个点,移除后暴力修改周围点的代价,并把新的代价加入到优先队列里。

优先队列里出队时先看其代价是不是当前点的代价,就能判断这个是不是最新的代价了。

暴力修改的复杂度其实就是边的数量,因此总的时间复杂度是O((n+m)log(n+m)+m)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<LL> a(n);
for(auto &i : a)
cin >> i;
vector<vector<int>> edge(n, vector<int>());
vector<LL> sum(n, 0);
for(int i = 1; i <= m; ++ i){
int x, y;
cin >> x >> y;
-- x;
-- y;
edge[x].push_back(y);
edge[y].push_back(x);
sum[x] += a[y];
sum[y] += a[x];
}
priority_queue<pair<LL, int>> qwq;
for(int i = 0; i < n; ++ i){
qwq.push({- sum[i], i});
}
LL ans = 0;
int cnt = n;
set<int> ff;
while(cnt){
auto [val, u] = qwq.top();
val = -val;
qwq.pop();
if (val != sum[u])
continue;
ff.insert(u);
ans = max(ans, val);
cnt --;
for(auto &v : edge[u]){
if (ff.find(v) != ff.end())
continue;
sum[v] -= a[u];
qwq.push({-sum[v], v});
}
}
cout << ans << '\n';
return 0;
}


F. Exactly K Steps

题目大意

给定一棵树,有q个询问,第 i个询问求任意一个与点 ui 距离ki的点的下标。

解题思路

考虑到树上往父亲跑容易,但往儿子跑不太容易,因此我们都尽量往父亲跑。

注意到,任意一个点与树上的点距离最远的,一定是这棵树的直径上的点,这个从直径的证明里可以得出。

因此先跑两次dfs求得树的直径,然后再分别以直径两点为根,预处理往上跳的倍增数组。对于询问中的点ui,就在距离根节点最远的那棵树往上跳ki步就能得到答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
const int N = 2e5 + 8;
const int SP = 25;
vector<int> G[N];
int n, q;
struct tu{
int up[N][SP];
int dep[N];
void dfs(int u, int fa) {
up[u][0] = fa; dep[u] = dep[fa] + 1;
FOR (i, 1, SP) up[u][i] = up[up[u][i - 1]][i - 1];
for (int& v: G[u]) {
if (v == fa) continue;
dfs(v, u);
}
}
int lca(int u, int t) {
FOR (i, 0, SP) if (t & (1 << i)) u = up[u][i];
return u;
}
}l1, l2;
int dep[N];
int solve(int u, int fa) {
dep[u] = dep[fa] + 1;
int maxDeep = u;
for (int& v: G[u]) {
if (v == fa) continue;
int maxd = solve(v, u);
if (dep[maxDeep] < dep[maxd])
maxDeep = maxd;
}
return maxDeep;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i < n; ++ i){
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
int l = solve(1, 1);
dep[l] = 0;
int r = solve(l, l);
l1.dfs(l, l);
l2.dfs(r, r);
cin >> q;
tu* L;
for(int i = 1; i <= q; ++ i){
int u, k;
cin >> u >> k;
if (l1.dep[u] < l2.dep[u]){
L = &l2;
}else {
L = &l1;
}
if (L->dep[u] <= k)
cout << -1 << '\n';
else
cout << L->lca(u, k) << '\n';
}
return 0;
}


G. Increasing K Times

题目大意

给定一个序列A,可打乱其排列顺序,问有多少个排列情况满足,恰好有k个数对,前者小于后者。

解题思路

神奇的代码


Ex. Odd Sum

题目大意

给定一个序列A,其中 1Ai10,问有多少个长度为奇数的子序列其和为M

解题思路

朴素的DP很容易可以想到设 dp[i][0/1][j]表示前 i个数,选了偶数/奇数个,和为 j的方案数。虽然第一维可以再压缩一下,将 11010个数依次考虑,考虑选多少个数出来,最后再 C一下,但因为 j状态里面,O(NM)的状态始终降不下来。

这里考虑下转移策略,我们的转移方程始终是从i1转移过来的,即 (1,i1)的状态和 (i,i) 的状态结合,得到了(1,i)的状态。每次转移长度都增加 1

那么考虑倍增地方式结合。一开始我们有 n个状态,分别是 (1,1),(2,2),...,(n,n)。然后像线段树合并或者分治合并的方式,先让 (1,1),(2,2)合并成 (1,2)(3,3),(4,4)合并成 (3,4),然后 (1,2),(3,4)合并成 (1,4)这样,这样我们每次合并时的长度都会翻倍,因此我们只需要合并 logn次就可以得到(1,n)的状态,也即 dp[n]

而对于合并,观察dp转移方程dp[i][0][j]=dp[i1][0][k]+dp[i1][0][jk]...... ,第三维是个卷积形式,因此我们将dp[i][0]dp[i][1]分别看成一个多项式,其中 xj的系数为 dp[i][0][j]dp[i][1][j],转移的时候就是两个多项式相乘,用 NTT加速计算。

合并两个多项式也即:

dp[i][0]=dp[j][0]×dp[k][0]+dp[j][1]×dp[k][1]

dp[i][1]=dp[j][0]×dp[k][1]+dp[j][1]×dp[k][0]

理论时间复杂度是O(MlogMlogn),但由于中间实现会产生较多次的临时数组创建开销,不知该怎么优雅的实现榜一3分钟A的代码还确实用这种做法能过,但其贴了多项式全家桶长达800行不知怎么做到的

有个一个小小的优化,就是由于只有10个数,我们就预处理每一个数的选择方案,也即 dp[i][0/1]表示一个由选择了偶数个/奇数个i的多项式, 其中xj的系数表示和为 j=i×cnt的方案数,其实就是个组合数Cnumicnt,其中numi表示序列Ai个个数。然后我们只需要做 10NTT就可以了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
const int N = 1e5 + 8;
const LL MOD = 998244353;
const int G = 3;
int n, m;
int cnt[11];
vector<LL> dp[11][2];
vector<LL> f[2], g[2];
LL bin(LL x, LL n, LL MOD) {
LL ret = MOD != 1;
for (x %= MOD; n; n >>= 1, x = x * x % MOD)
if (n & 1) ret = ret * x % MOD;
return ret;
}
inline LL get_inv(LL x, LL p) { return bin(x, p - 2, p); }
LL wn[(N * 10) << 2], rev[(N * 10) << 2];
int NTT_init(int n_) {
int step = 0; int n = 1;
for ( ; n < n_; n <<= 1) ++step;
FOR (i, 1, n)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (step - 1));
int g = bin(G, (MOD - 1) / n, MOD);
wn[0] = 1;
for (int i = 1; i <= n; ++i)
wn[i] = wn[i - 1] * g % MOD;
return n;
}
void NTT(vector<LL>& a, int n, int f) {
FOR (i, 0, n) if (i < rev[i])
std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
for (int i = 0; i < n; i += (k << 1)) {
int t = n / (k << 1);
FOR (j, 0, k) {
LL w = f == 1 ? wn[t * j] : wn[n - t * j];
LL x = a[i + j];
LL y = a[i + j + k] * w % MOD;
a[i + j] = (x + y) % MOD;
a[i + j + k] = (x - y + MOD) % MOD;
}
}
}
if (f == -1) {
LL ninv = get_inv(n, MOD);
FOR (i, 0, n)
a[i] = a[i] * ninv % MOD;
}
}
vector<LL> operator+(vector<LL> a, const vector<LL>& b){
a.resize(max(a.size(), b.size()));
for(int i = 0; i < b.size(); ++ i)
a[i] = (a[i] + b[i]) % MOD;
return a;
}
vector<LL> conv(vector<LL> a, vector<LL> b) {
int len = a.size() + b.size() - 1;
int n = NTT_init(len);
a.resize(n);
b.resize(n);
NTT(a, n, 1);
NTT(b, n, 1);
FOR (i, 0, n)
a[i] = a[i] * b[i] % MOD;
NTT(a, n, -1);
a.resize(len);
return a;
}
LL invf[N], fac[N] = {1};
void fac_inv_init(LL n, LL p) {
FOR (i, 1, n)
fac[i] = i * fac[i - 1] % p;
invf[n - 1] = bin(fac[n - 1], p - 2, p);
FORD (i, n - 2, -1)
invf[i] = invf[i + 1] * (i + 1) % p;
}
LL C(int n, int m){
if (n < m)
return 0;
return fac[n] * invf[m] % MOD * invf[n - m] % MOD;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
fac_inv_init(n + 1, MOD);
for(int i = 1; i <= n; ++ i){
int a;
cin >> a;
cnt[a] ++;
}
for(int i = 1; i <= 10; ++ i){
dp[i][0].resize(cnt[i] * i + 1);
dp[i][1].resize(cnt[i] * i + 1);
for(int j = 0; j <= cnt[i]; ++ j){
dp[i][j & 1][i * j] = C(cnt[i], j);
}
}
f[0].resize(1);
f[0][0] = 1;
for(int i = 1; i <= 10; ++ i){
g[0] = conv(f[0], dp[i][0]) + conv(f[1], dp[i][1]);
g[1] = conv(f[1], dp[i][0]) + conv(f[0], dp[i][1]);
f[0].swap(g[0]);
f[1].swap(g[1]);
}
if (f[1].size() <= m)
cout << 0 << '\n';
else
cout << f[1][m] << '\n';
return 0;
}


posted @   ~Lanly~  阅读(138)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示