Codeforces Round #625 (Div. 2, based on Technocup 2020 Final Round)
前言
- 第二次打cf,又掉分了
比賽AC
A. Contest for Robots
簡明題意
- 一共有n道題,做出第i道題可以得分。現在給出數組r[]和b[],分別表示兩個人是否做對第i題。
- 是不知道的。現在需要求出r[]這個人獲勝的情況下,的最小值。最小爲1的正整數
正文
- 兩人共同答對的題就沒有區分度,不管設置多少分,兩人還是平局,因此兩人都答對的題設置1分就好了。
- b答對的題r沒有答對。這樣會使得b的得分增高,想要r分數增高,只能從r答對而b沒有答對的題入手。
- 因此要使b答對的題分數儘可能少,就全設爲1分。而r答對b每答對的題,用來中和前面那部分分數。
代碼
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<string>
using namespace std;
const int maxn = 110;
int r[maxn], b[maxn];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> r[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
bool can_win = 0;
for (int i = 1; i <= n; i++)
{
if (r[i] == 1 && b[i] == 0)
can_win = 1;
}
if (!can_win)
{
cout << "-1";
}
else
{
int num1 = 0, num2 = 0;
for (int i = 1; i <= n; i++)
{
if (r[i] == 0 && b[i] == 1)
num2++;
if (r[i] == 1 && b[i] == 0)
num1++;
}
cout << num2 / num1 + 1;
}
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
B. Journey Planning
簡明題意
- 有n個點,每個點有個權w[i],a點走到b點,當且僅當b-a=w[b]-w[a]。現在讓你規劃一條路線,使得這條路線的權值總和最大。這條路線的點必須是單調增的,
正文
- 一開始想了很多,怎麼都想不到。
- 後來突然腦子靈了。直接用w[i]-i,你會發現w[i]-i相等的點可以互相走。那麼直接用一個map,統計看哪一種w[i]-i的w[i]之和最大就可以了。
代碼
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<string>
using namespace std;
const int maxn = 2e5 + 10;
int r[maxn], b[maxn];
map<int, long long> mp;
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int t;
cin >> t;
mp[i - t] += t;
}
long long ans = -1;
for (auto& it : mp)
ans = max(it.second, ans);
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
賽後補題
C. Remove Adjacent
簡明題意
- 給一個字符串s,如果第i的字母旁邊有一個akii碼比他小1的字符,那麼可以移除第i個字符。選取合適的移除順序,問最多可以移多少個。
正文
- 貪心,每次移除最大的能移除的字母就可以了。
代碼
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<string>
#include<vector>
using namespace std;
const int maxn = 2e5 + 10;
vector<char> a;
void solve()
{
int n;
cin >> n;
char las_c;
int las_num = 0;
getchar();
for (int i = 1; i <= n; i++)
{
char t;
scanf("%c", &t);
a.push_back(t);
}
int ans = 0;
for (char i = 'z'; i >= 'a'; i--)
{
for (int m = 1; m <= 100 ; m++)
for (int j = 0; j < a.size(); j++)
if (a[j] == i)
{
if (j - 1 >= 0 && a[j - 1] == i - 1) {
a.erase(a.begin() + j), ans++;
break;
}
else if (j + 1 < a.size() && a[j + 1] == i - 1) {
a.erase(a.begin() + j), ans++;
break;
}
}
}
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
D. Navigation System
簡明題意
- 剛給一個有向圖,再給一條行駛路線(一條路徑,比如1->4->9>10)。
- 現在有一個導航系統,每走到一個點,導航系統會規劃一次最短路。
- 現在問你按照所給的路徑,系統最少/最多進行多少次規劃。
- 假如145是1-5的最短路徑,那麼如果題目所給的路徑就是145,導航系統可以一次都不重新規劃。
- 爲什麼會有最多、最少的說法呢?同上,145是1-5的最短路,但175也是一條1-5的最短路,加入身處1號節點時,系統的規劃爲175,那麼接下來按照行駛路線,會行駛到4號節點,這是系統會規劃成45,所以最終系統會重新規劃一次。而如果在1號節點時,系統的規劃不是175而是145,那麼就不需要系統重新規劃了。因此當圖不變,行駛路線也不變,重新規劃次數還是可能改變。所以有最多、最少的說法。
正文
- 這題讀題太難了。
- 先思考,最多、最少可能的重新規劃次數。重新規劃在什麼時候?在到達一個點時。那麼假設行駛路徑有k個點,最少0次改變,最多,除了第一點,每次都改變,那麼最多k-1次重新規劃。
- 說上面,就是要搞清楚,重新規劃路線,是在每次到達一個點時重新規劃。而且到達一個點時,我們有幾種選擇:1.必須重新規劃 2.必須不重新規劃 3.可以重新規劃也可以不重新規劃。
- 如果我們知道了行駛路線上每個點是以上3種選擇的哪一種,是不是就可以算出來最多/最少重新規劃次數了呢?
- 現在來考慮以上3種情況發生的條件。
1.必須重新規劃路線。給出的路徑爲145,而在1點時,系統的規劃路線是15,那麼走到4時,一定會重新規劃。
2.必須不重新規劃。給出的路徑爲145,在1點時,系統的規劃也是145,那麼走到4時,只能走4-5,那麼就一定不能重新規劃。
3.可重新規劃也可不重新規劃。給出的路徑爲1345,在1點時系統規劃是1345,那麼來到3,最短路可以是345或365,那麼系統規劃可能是這兩種,如果選擇第一種,那麼可以不重新規劃,如果選擇第二種,那麼需要重新規劃。 - 現在來計算最少的重新規劃次數。只需要統計行駛路線上發生情況1的點的數量。怎麼統計?當新的點不在原來點的最短路上時,發生情況1.比如1375這條路,1到5最短路是147,那麼新點就是3,原來點就是1,所以說新點3不在原來點的最短路上。
- 計算最多的重新規劃次數。統計行駛路線上發生情況3的點的數量。情況3:當走到新的點時,比如從1走到3時,存在135和145兩條最短路,那麼我們強行使在1點時選擇145,然後來到3,這樣重新規劃次數會增加。也就是說新點在原來點的最短路上且原來點還存在另一條最短路。
- 接下來就是如何判斷新點是否在原來點的最短路上。我們直接提前算好每個點到終點的最短路(終點就是規劃路線的最後一個點),然後判斷兩個點的最短路之差是否爲1.然後判斷原來點還存在另一條最短路,那麼直接遍歷原來點的所有連接點,看有沒有點的最短路和新點的最短路相等,有的話,條件3就成立。
- 至於怎麼提前算好每個點到終點的最短路,直接反向建圖,以終點爲起點bfs一下就可以了。
代碼
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
const int maxn = 2e5 + 10;
int n, m, k, a[maxn];
vector<int> g[maxn];
vector<int> g1[maxn];
int st, ed;
int dis[maxn];
void bfs()
{
queue<int> que;
que.push(ed);
while (que.size())
{
int u = que.front();
que.pop();
for (auto& v : g[u])
if (!dis[v] && v != ed)
{
que.push(v);
dis[v] = dis[u] + 1;
}
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
g[v].push_back(u);
g1[u].push_back(v);
}
cin >> k;
for (int i = 1; i <= k; i++)
scanf("%d", &a[i]);
st = a[1], ed = a[k];
bfs();
//最少次數
int ans1 = 0;
for (int i = 2; i <= k; i++)
if (dis[a[i]] != dis[a[i - 1]] - 1)
ans1++;
//最多c次數
int ans2 = 0;
for (int i = 2; i <= k; i++)
{
if (dis[a[i]] != dis[a[i - 1]] - 1)
ans2++;
else
{
for (auto& v: g1[a[i - 1]])
if (v != a[i] && dis[a[i - 1]] - 1 == dis[v])
{
ans2++;
break;
}
}
}
cout << ans1 << " " << ans2;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}
E. World of Darkraft: Battle for Azathoth
簡明題意
- 一個人要去打怪。現在又n種武器,m種盾牌,p個怪物。一個人選擇1種武器和一種盾牌,問打怪的收益最多是多少。
- 武器有a[i],ca[i],分別表示武器的攻擊力和武器的價格。
- 盾牌有b[i],cb[i],分別表示盾牌的防禦力和盾牌的價格。
- 怪物有x[i],y[i],z[i],分別表示怪物的防禦力、攻擊力、擊敗時掉落的金幣。
- 選擇一個武器和一個盾牌後,可以得到所有 防禦力<武器攻擊力,攻擊力<盾牌防禦力 的怪物的金幣。
- 問打怪的收益最多是多少。
正文
- 二維偏序。
- 當盾牌確定時,顯然高攻擊力越高,盾牌能打的怪收益越多(不計武器的價格)。我們可以先把盾牌的收益設置成他的-他的價格,那麼當我們遞增這個攻擊力時,盾牌的收益就會加上一些。
- 把盾牌列出來,再按照攻擊力從小到大枚舉武器。顯然武器的攻擊力越高,這個武器能打的怪物就越多。我們假裝有一個怪物集合,那麼當武器的高攻擊力提高了,就會有一些新的怪物加入了這個怪物集合。當有新的怪物加入怪物集合時,那麼對於每個防禦力大於加入的怪物的攻擊力的盾牌,都會獲得一些收益累加。然後在這所有的盾牌中選一個收益值最大的,減去當前武器的價格,就得到選擇某個武器的最高收入了。
- 接下來問題就在於,當確定一個武器後,新增了一些怪物,如何給盾牌累加收益。我們可以暴力枚舉所有的盾牌,把防禦值滿足要求的累加收益。然後找最大值,複雜度不能接受。
- 我們也可以按照防禦值二分出符合要求的盾牌,然後累加,這樣複雜度是會降低,但仍然不穩定。因爲可能跟盾牌的防禦值都很高,怪物的攻擊力都很低,這樣的話每次還是得遍歷所有的盾牌。
-思考,我們每次是給防禦值>怪物攻擊力的盾牌累加收益。那麼這是不是像在區間加和呢? 所以可以用線段樹來維護。如果把盾牌的防禦值設置爲區間,收益設置爲值,可以寫線段樹。支持區間加以及查詢最大值即可。 - 直接從1-1e6建線段樹,不存在盾牌的點,收益設置成功-2e18.
代碼
#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<stack>
#include<map>
#include<queue>
#include<string>
#include<vector>
using namespace std;
const int maxn = 1e6 + 10;
int n, m, p;//武器、盾牌、怪物數
int a[(int)1e6 + 10];
int b[(int)1e6 + 10];
struct Node
{
int l, r;
long long max, tag;
};
Node tree[maxn * 4];
struct Mon
{
int x, y, z;
bool operator < (const Mon& a) const
{
return x < a.x;
}
};
Mon mon[(int)2e5 + 10];
void build(int o, int l, int r)
{
tree[o].l = l, tree[o].r = r;
if (l == r)
{
tree[o].max = (b[l] == 0 ? -1e18 : -b[l]);
return;
}
int mid = (l + r) / 2;
build(o * 2, l, mid);
build(o * 2 + 1, mid + 1, r);
tree[o].max = max(tree[o * 2].max, tree[o * 2 + 1].max);
}
void spread(int o)
{
if (tree[o].tag)
{
if (tree[o].l != tree[o].r)
{
tree[o * 2].tag += tree[o].tag;
tree[o * 2 + 1].tag += tree[o].tag;
}
tree[o].max += tree[o].tag;
tree[o].tag = 0;
}
}
void change(int o, int l, int r, int c)
{
spread(o);
if (tree[o].l == l && tree[o].r == r)
{
tree[o].tag = c;
spread(o);
return;
}
int mid = (tree[o].l + tree[o].r) / 2;
if (r <= mid) change(o * 2, l, r, c);
else if (l > mid) change(o * 2 + 1, l, r, c);
else change(o * 2, l, mid, c), change(o * 2 + 1, mid + 1, r, c);
spread(o * 2), spread(o * 2 + 1);
tree[o].max = max(tree[o * 2].max, tree[o * 2 + 1].max);
}
bool x0;
void solve()
{
cin >> n >> m >> p;
int max_dun = 0;
for (int i = 1; i <= n; i++)
{
int x, cx;
scanf("%d%d", &x, &cx);
if (a[x]) a[x] = min(a[x], cx);
else a[x] = cx;
}
for (int i = 1; i <= m; i++)
{
int x, cx;
scanf("%d%d", &x, &cx);
if (b[x]) b[x] = min(b[x], cx);
else b[x] = cx;
max_dun = max(max_dun, x);
}
for (int i = 1; i <= p; i++)
scanf("%d%d%d", &mon[i].x, &mon[i].y, &mon[i].z);
sort(mon + 1, mon + 1 + p);
build(1, 1, max_dun);
int l = 1;
long long ans = -1e18;
for (int i = 1; i <= 1e6; i++)
if (a[i])//存在攻擊力爲i的武器
{
//攻擊力提升了,相應能打的怪物就會增多幾個
for (int j = l; j <= p; j++)
if (mon[j].x < i)//枚舉能打的怪物
{
if (mon[j].y + 1 <= max_dun)
change(1, mon[j].y + 1, max_dun, mon[j].z);//防禦力在[mon[j].y+1, 1e6]之間的盾牌都能多打一些怪物
l++;
}
else break;
ans = max(ans, tree[1].max - a[i]);
}
cout << ans;
}
int main()
{
//freopen("Testin.txt", "r", stdin);
solve();
return 0;
}