2022“杭电杯”中国大学生算法设计超级联赛(3) 题解
B. Boss Rush
二分答案,问题转化为求 \(T\) 时间内可造成的最大伤害。
对技能进行状压,枚举到每一个状态时选取一个已经加入状态的技能作为最后一个被释放的技能。
此时施放此技能的时刻可被确定,直接进行转移即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 100000 + 5;
struct Node {
int t, len, sum[100005];
}a[20];
int n, H, f[(1 << 18) + 5];
bool Check(int T) {
for(int s = 0; s < (1 << n); ++s) f[s] = 0;
for(int s = 1; s < (1 << n); ++s) {
int t = 0;
for(int i = 0; i < n; ++i) if((s & (1 << i))) t += a[i].t;
for(int i = 0; i < n; ++i) {
if(!(s & (1 << i)) || t - a[i].t > T) continue;
int cur = a[i].sum[ (int)min(T - (t - a[i].t), a[i].len) ];
f[s] = max(f[s], f[s ^ (1 << i)] + cur);
}
if(f[s] >= H) return 1;
}
return 0;
}
void Solve() {
scanf("%lld%lld", &n, &H);
int T = 0;
for(int i = 0; i < n; ++i) {
scanf("%lld%lld", &a[i].t, &a[i].len); T += a[i].t;
for(int j = 1; j <= a[i].len; ++j) scanf("%lld", &a[i].sum[j]);
for(int j = 1; j <= a[i].len; ++j) a[i].sum[j] += a[i].sum[j - 1];
}
int l = 1, r = 1.8e6;
while(l < r) {
int mid = l + r - 1 >> 1;
if(Check(mid)) r = mid;
else l = mid + 1;
}
if(l >= 1.8e6) l = 0;
printf("%lld\n", l - 1);
}
signed main()
{
int T; scanf("%lld", &T);
while(T--) Solve();
return 0;
}
C. Cyber Language
签到题,根据题意模拟即可。
#include<bits/stdc++.h>
using namespace std;
signed main()
{
int T; cin >> T; getchar();
while(T--) {
string S;
getline(cin, S);
bool flag = 1;
for(auto i : S) {
if(flag) cout << (char)(i - 'a' + 'A'), flag = 0;
else if(i == ' ') flag = 1;
}
cout << "\n";
}
}
J. Range Reachability Query
设 \(f[i][j]\) 表示点 \(i\) 是否能在询问 \(j\) 的条件下到达 \(v_i\) ,其第二维可以用bitset优化。空间复杂度为 \(O(\frac{nq}{\omega})\)
考虑一条编号为 \(k\) 的边 \(u \rightarrow v, u < v\),设 \(S = \{i \ | \ l_i \leq k \leq r_i\}\),有 \(f[u] |= (f[v] \ \& \ S)\)
考虑将询问离线并按顺序维护 \(S\),每个询问对于 \(S\) 有两种操作:① 在 \(l\) 处加入 \(S\);② 在 \(r+1\) 处退出 \(S\)。
发现我们需要存 \(m\) 个大小为 \(\frac{q}{\omega}\) 的bitset,空间复杂度可能会过高,我们考虑牺牲一部分时间复杂度换取空间。
具体做法是利用分块思想,每次对于 \(S\) 的操作记录一个 \(cnt\),当 \(cnt > BLOCK\_SIZE\) 时才记录此时的值。每次查询时先取出对应其 \(k\) 值的 \(S\) 的副本,在此基础上继续暴力操作。当 \(BLOCK\_SIZE\) 为 \(\sqrt{2 \cdot q}\) 时均摊复杂度最优。
时间复杂度为 \(O(\frac{mq}{\omega}+m\sqrt{q})\)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;
const int BLOCK_SIZE = 350;
int n, m, q, pos[100005], End[355], U[50005], nxt[100005];
bitset<50005> f[50005], S[355], S0;
vector<pii> G[50005], vec;
vector<int> Q[100005];
void GetS(int x) {
S0 = S[ pos[x] ];
for(int i = nxt[ End[ pos[x] ] ]; i <= x; i = nxt[i]) for(auto j : Q[i]) S0.flip(j);
}
void Solve() {
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i++) f[i].reset(), G[i].clear();
for(int i = 1; i <= m; i++) Q[i].clear();
for(int i = 1; i <= m; i++) {
int u, v; scanf("%d%d", &u, &v);
G[u].push_back({v, i});
}
for(int i = 1; i <= q; i++) {
int u, v, l, r; scanf("%d%d%d%d", &u, &v, &l, &r);
U[i] = u, f[v].set(i);
Q[l].push_back(i), Q[r + 1].push_back(i);
}
S0.reset(); int p = 0, cnt = 0;
for(int i = 1; i <= m; i++) {
cnt += Q[i].size();
for(auto j : Q[i]) S0.flip(j);
if(cnt > BLOCK_SIZE) S[++p] = S0, End[p] = i, cnt = 0;
pos[i] = p;
}
p = m + 1;
for(int i = m; i >= 0; i--) {
nxt[i] = p;
if( Q[i].size() ) p = i;
}
for(int i = n; i >= 1; i--) for(auto j : G[i]) GetS(j.se), f[i] |= (f[j.fi] & S0);
for(int i = 1; i <= q; i++) {
if(f[ U[i] ][i]) printf("YES\n");
else printf("NO\n");
}
}
signed main()
{
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; scanf("%d", &T);
while(T--) Solve();
return 0;
}
K. Taxi
将 \(|x-x_k|+|y-y_k|\) 中的绝对值拆开并重新排列,有四种情况:
发现我们可以通过维护\(-x_k-y_k, \ x_k+y_k, \ x_k-y_k, \ -x_k+y_k\)的最大值,从而比较这四个情况的最大值,设其为 \(Max\)。
将所有点按 \(w\) 值升序排序,对于每个询问 \((x, y)\) ,我们从后往前加点维护 \(Max\),发现 \(Max\) 是单调不上升的。
于是将 \(w\) 和 \(Max\) 看成单调函数,二分找交点,此交点两侧的下标中必定存在答案。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;
namespace fastIO {
#define BUF_SIZE 100000
//fread -> read
bool IOerror = 0;
inline char nc() {
static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
if (p1 == pend) {
p1 = buf;
pend = buf + fread(buf, 1, BUF_SIZE, stdin);
if (pend == p1) {
IOerror = 1;
return -1;
}
}
return *p1++;
}
inline bool blank(char ch) {
return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
}
inline void read(int &x) {
char ch;
while (blank(ch = nc()));
if (IOerror) return;
for (x = ch - '0'; (ch = nc()) >= '0' && ch <= '9'; x = x * 10 + ch - '0');
}
#undef BUF_SIZE
};
using namespace fastIO;
const int MAXN = 1e5 + 5;
int INF = 0;
struct Node {
int x, y, w;
}a[MAXN];
struct Tree {
int maxadd, minadd, maxsub, minsub;
}tree[MAXN][25];
int n, q;
inline void write(int x) {
static int sta[65];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while(x);
while(top) putchar(sta[--top] + 48);
}
Tree PushUp(Tree ls, Tree rs) {
Tree res;
res.maxadd = max(ls.maxadd, rs.maxadd);
res.maxsub = max(ls.maxsub, rs.maxsub);
res.minadd = min(ls.minadd, rs.minadd);
res.minsub = min(ls.minsub, rs.minsub);
return res;
}
void Build() {
for(int p = 1; p <= n; p++) {
tree[p][0].maxadd = tree[p][0].minadd = a[p].x + a[p].y;
tree[p][0].maxsub = tree[p][0].minsub = a[p].x - a[p].y;
}
for(int k = 1; (1 << k) <= n; k++) {
for(int p = 1; (p + (1 << k - 1)) <= n; p++) {
tree[p][k] = PushUp(tree[p][k - 1], tree[p + (1 << k - 1)][k - 1]);
}
}
}
Tree Query(int l, int r) {
int t = floor(log2(r - l + 1));
return PushUp(tree[l][t], tree[r - (1 << t) + 1][t]);
}
int Check(int w, int x, int y) {
int l = 1, r = n, flag = 0;
while(l < r) {
int mid = l + r - 1 >> 1;
if(a[mid].w >= w) r = mid;
else l = mid + 1;
}
Tree res = Query(l, n);
int maxx = max({
res.maxadd - (x + y),
(x + y) - res.minadd,
res.maxsub - (x - y),
(x - y) - res.minsub
});
if(maxx >= w) flag = 1;
return flag;
}
int Calc(int i, int x, int y) {
Tree res = Query(i, n);
int maxx = max({
res.maxadd - (x + y),
(x + y) - res.minadd,
res.maxsub - (x - y),
(x - y) - res.minsub
});
return maxx;
}
int Maxx(int i, int x, int y) {
Tree res = Query(i, n);
int maxx = max({
res.maxadd - (x + y),
(x + y) - res.minadd,
res.maxsub - (x - y),
(x - y) - res.minsub
});
return maxx;
}
void Solve() {
read(n); read(q); INF = 0;
for(int i = 1; i <= n; ++i) {
read(a[i].x); read(a[i].y); read(a[i].w);
INF = max(INF, a[i].w);
}
sort(a + 1, a + 1 + n, [&](const Node& a, const Node& b) { return a.w < b.w; });
Build();
Tree res = Query(1, n);
while(q--) {
int cx, cy; read(cx); read(cy);
//int l = 1, r = INF;
/*
for(int i = 1; i <= n; i++) cout << Calc(i, cx, cy) << " "; cout << "calc\n";
while(l < r) {
int mid = l + r + 1 >> 1;
if(Check(mid, cx, cy)) l = mid;
else r = mid - 1;
}
*/
int l = 1, r = n;
while(l < r) {
int mid = l + r + 1 >> 1;
//cout << l << " " << Maxx(l, cx, cy) << " " << a[l].w << " cur\n";
if(Maxx(mid, cx, cy) >= a[mid].w) l = mid;
else r = mid - 1;
}
//cout << l << " res\n";
int ans = min(Maxx(l, cx, cy), a[l].w);
if(l + 1 <= n) ans = max(ans, min(Maxx(l + 1, cx, cy), a[l + 1].w) );
write(ans); putchar('\n');
}
}
signed main()
{
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; read(T);
while(T--) Solve();
return 0;
}
L. Two Permutations
设 \(dp_{i,j}\) 为枚举到S串的第 \(i\) 位,匹配了P串的前 \(j\) 位时的答案。
有
且dp值不为0的状态数为 \(O(n)\),直接转移即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;
const int MAXN = 6e5 + 5;
const int MOD = 998244353;
int p[MAXN], q[MAXN], s[MAXN], n;
unordered_map<int, int> dp[MAXN];
queue<pii> Q;
void Solve() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> p[i];
for(int i = 1; i <= n; i++) cin >> q[i];
for(int i = 1; i <= 2 * n; i++) cin >> s[i];
for(int i = 1; i <= 2 * n; i++) dp[i].clear();
dp[0][0] = 1;
for(int i = 0; i < 2 * n; i++) {
for(auto& j : dp[i]) {
int x = j.fi, y = i - j.fi, pos = i + 1;
if(s[pos] == p[x + 1]) dp[pos][x + 1] = (dp[pos][x + 1] + dp[i][x]) % MOD;
if(s[pos] == q[y + 1]) dp[pos][x] = (dp[pos][x] + dp[i][x]) % MOD;
}
}
cout << dp[n << 1][n] << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}