更好的阅读体验请转移至我的博客
1002 Boss Rush
题意
有n ( ≤ 18 ) (\le18) (≤18)个技能和一个有H血量的boss,每个技能在释放后的 t i t_i ti秒内不能释放别的技能(若在 x x x秒释放技能,则在 x + t i x+t_i x+ti秒时可以释放下一个技能),每个技能释放后的 l e n i len_i leni秒后每秒都会对boss造成一定的伤害,问至少多少秒能击败boss,若不能击败输出-1
思路
比赛期间写了很久发现思路错了,正解是二分答案+状压
枚举T秒是否能击败boss,再将问题转化为T秒内能打出的最大伤害,通过状压枚举,感觉像是dp
ll t[N], len[N], d[N], n, H, sum[N];
vector<ll> tot[N];
bool check(int T) {
for (int i = 0; i < (1 << n); i++) d[i] = -1;
//d[i]表示i状态下T时间内的最大伤害
d[0] = 0;
for (int i = 0; i < (1 << n); i++) {
if (d[i] >= H) return 1;//能击败
if (sum[i] > T) continue;
//状态释放总时间大于T
assert(d[i] >= 0);
int now = sum[i];//now为当前状态释放的时间
for (int j = 0; j < n; j++) {
if (!(i >> j & 1)) {//在加上第i个技能
if (now + len[j] - 1 <= T) d[i | (1 << j)] = max(d[i | (1 << j)], d[i] + tot[j].back());
else d[i | (1 << j)] = max(d[i | (1 << j)], d[i] + tot[j][T - now]);
//类似dp
}
}
}
return 0;
}
int main() {
IOS;
int T;
cin >> T;
while (T--) {
cin >> n >> H;
for (int i = 0; i < n; i++) {
cin >> t[i] >> len[i];
for (int j = 0; j < len[i]; j++) {
int x;
cin >> x;
tot[i].push_back(x);
}
for (int j = 1; j < len[i]; j++) tot[i][j] += tot[i][j - 1];
}
sum[0] = 0;
for (int i = 1; i < (1 << n); i++)//每个状态的技能释放总时间
sum[i] = sum[i - lowbit(i)] + t[__lg(lowbit(i))];
int l = 0, r = 1e6, ans = -1;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans << endl;
for (int i = 0; i <= n; i++) tot[i].clear();
}
return 0;
}
1009 Package Delivery
题意
一共有n件快递,每个快递都必须在 [ l i , r i ] [l_i,r_i] [li,ri]时间内领取,小Q一次最多可以拿k件,问小Q最少可以跑几次
思路
按照每个区间的右端点排序,对于某个快递,若其左端点大于前面所有的右端点,则其必须单独取件,答案+1,并将其右端点插入mp中,否则,则意味着该快点后面有某个区间的右端点,此快递可以与之一起取,记录此时取的快递数量+1,若数量达到k则从mp中删除。
int main() {
int T;
cin >> T;
while (T--) {
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i].first >> a[i].second;
sort(a + 1, a + 1 + n, [](pii A, pii B) {
if (A.second == B.second) return A.first < B.first;
return A.second < B.second;
});
map<int, int> mp;
int ans = 0;
for (int i = 1; i <= n; i++) {
auto pos = mp.lower_bound(a[i].first);
if (pos == mp.end()) {
if (k == 1) {
ans++;
continue;
}
mp[a[i].second] = 1;
ans++;
}
else {
mp[pos->first]++;
if (mp[pos->first] == k) mp.erase(pos);
}
}
cout << ans << endl;
}
return 0;
}
1011 Taxi
题意
给定n个点的坐标,每个点都有个点权 w i w_i wi,定义两点间的路径长度为 m i n ( w k , ∣ x i − x ′ ∣ + ∣ y i − y ′ ∣ ) min(w_k,|x_i-x'|+|y_i-y'|) min(wk,∣xi−x′∣+∣yi−y′∣),每次询问一个点 ( x ′ , y ′ ) (x',y') (x′,y′),求距离它最远的点的距离。
思路
不考虑 w i w_i wi 的影响下,题目便是询问最远点的曼哈顿距离,感性理解一下便是询问和左上角,右上角,左下角,右下角的点中最远的一个。详细可以通过枚举展开 ∣ x i − x ′ ∣ + ∣ y i − y ′ ∣ |x_i-x'|+|y_i-y'| ∣xi−x′∣+∣yi−y′∣,发现只要维护每个点的 x i + y i , x i − y i , − x i + y i , − x i − y i x_i+y_i,x_i-y_i,-x_i+y_i,-x_i-y_i xi+yi,xi−yi,−xi+yi,−xi−yi即可,分别对应该点是询问点的右上角,右下角,左上角,左下角。每次询问输出最大值即可。
但是题目中加入了 w i w_i wi,选项,我们把所有点按照 w i w_i wi排序并维护每个点的后缀最大 x i + y i , x i − y i , − x i + y i , − x i − y i x_i+y_i,x_i-y_i,-x_i+y_i,-x_i-y_i xi+yi,xi−yi,−xi+yi,−xi−yi,便可以二分寻找答案了:
以下用 d d d代表两点间曼哈顿距离。
若 m i d mid mid点处 w i w_i wi> d d d,则 d d d产生贡献,更优的答案只会出现在左边
若 m i d mid mid点处 d > w i d>w_i d>wi,则 w i w_i wi产生贡献,更优的答案只会出现在右边
由此便可确定最优解。
struct node {
ll x, y, w;
bool operator<(const node A) {
return w < A.w;
}
}a[N];
ll zs[N], zx[N], ys[N], yx[N];
int main() {
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y >> a[i].w;
sort(a + 1, a + 1 + n);
zs[n + 1] = ys[n + 1] = zx[n + 1] = yx[n + 1] = -INF;
for (int i = n; i >= 1; i--) {
ll x = a[i].x, y = a[i].y;
zs[i] = max(zs[i + 1], -x - y);
ys[i] = max(ys[i + 1], y - x);
zx[i] = max(zx[i + 1], x - y);
yx[i] = max(yx[i + 1], x + y);
}
while (m--) {
int x, y;
cin >> x >> y;
int l = 1, r = n; ll ans = -INF;
while (l <= r) {
int mid = l + r >> 1;
ll mx = max({ zs[mid] + x + y,ys[mid] - y + x,zx[mid] - x + y,yx[mid] - x - y });
if (a[mid].w > mx) r = mid - 1, ans = max(ans, mx);
else l = mid + 1, ans = max(ans, a[mid].w);
}
cout << ans << endl;
}
}
return 0;
}
1012 Two Permutations
题意
给定两个排列 P , Q P,Q P,Q和一个数组 S S S,每次可以进行如下操作
- 将 P P P中第一个数字弹出插入 S ′ S' S′中
- 将 Q Q Q中第一个数字弹出插入 S ′ S' S′中
询问最终有多少方案可以使 S ′ = = S S'==S S′==S
思路
d p [ i ] [ p o s ] dp[i][pos] dp[i][pos]表示 S S S数组中第 i i i个数字的前一位(也就是第 i − 1 i-1 i−1位)取自第 p o s pos pos个排列时的方案数( p o s = = 1 ∣ ∣ p o s = = 2 pos==1||pos==2 pos==1∣∣pos==2),使用记忆化搜索降低复杂度。
int a[N], b[N], c[N], n;
ll dp[N][2];
int dfs(int x, int y, int pos) {
if (x > n && y > n) return 1;
if (dp[x + y - 1][pos] != -1) return dp[x + y - 1][pos];
ll res = 0;
if (x <= n && a[x] == c[x + y - 1]) {
res += dfs(x + 1, y, 0);
}
if (y <= n && b[y] == c[x + y - 1]) {
res += dfs(x, y + 1, 1);
}
dp[x + y - 1][pos] = res;
return res % MOD;
}
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
for (int i = 1; i <= n + n; i++) cin >> c[i];
for (int i = 0; i <= n + n; i++) dp[i][0] = dp[i][1] = -1;
ll ans = 0;
if (a[1] == c[1]) ans += dfs(2, 1, 0);
if (b[1] == c[1]) ans += dfs(1, 2, 1);
cout << ans % MOD << endl;
}
return 0;
}
隔壁大佬的双指针写法,若当前两排列指针指向的数字不同,则方案是唯一的,直到某刻两指针指向的数字相同,判断出相同多少位,再判断出中间是否在 S S S中是连续出现的,若是连续出现的,则两指针的移动顺序是可以相互交换的。
这个方法的代码看起来虽然脑溢血,但是实际效率要更高一些。
int n, a[N], b[N], c[N];
void so() {
int l1 = 1, l2 = 1, l3 = 1; ll ans = 1;
while (l1 <= n || l2 <= n) {
if (l1 > n) {
while (l2 <= n && b[l2] == c[l3]) l2++, l3++;
if (l2 != n + 1) {
cout << 0 << endl; return;
}
else {
cout << ans << endl; return;
}
}
else if (l2 > n) {
while (l1 <= n && a[l1] == c[l3]) l1++, l3++;
if (l1 != n + 1) {
cout << 0 << endl; return;
}
else {
cout << ans << endl; return;
}
}
else if (a[l1] != b[l2]) {
if (l1 <= n && a[l1] == c[l3]) l1++, l3++;
else if (l2 <= n && b[l2] == c[l3]) l2++, l3++;
else {
cout << 0 << endl;
return;
}
}
else {
int ed1 = l1, ed2 = l2, init1 = l1, init2 = l2;
map<int, int> mp;
while (ed1 <= n && ed2 <= n && a[ed1] == b[ed2]) ed1++, ed2++;
int id = 0;
while (l1 <= ed1 && l2 <= ed2) {
if (l1 <= n && a[l1] == c[l3]) {
mp[a[l1]]++;
if (mp[a[l1]] % 2 == 0) id--;
else id++;
if (id == 0) ans = ans * 2 % MOD;
l1++; l3++;
}
else if (l2 <= n && b[l2] == c[l3]) {
mp[b[l2]]++;
if (mp[b[l2]] % 2 == 0) id--;
else id++;
if (id == 0) ans = ans * 2 % MOD;
l2++; l3++;
}
else {
if (l1 == ed1) {
int len1 = l1 - init1, len2 = l2 - init2;
l1 = init1 + len2; l2 = init2 + len1;
}
break;
}
}
}
}
cout << ans << endl;
}
int main() {
IOS;
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int j = 1; j <= n; j++) cin >> b[j];
map<int, int> mp;
for (int i = 1; i <= n * 2; i++) cin >> c[i], mp[c[i]]++;
for (int i = 1; i <= n; i++) {
if (mp[i] != 2) {
cout << 0 << endl;
goto h;
}
}
so();
h:;
}
return 0;
}