文章目录
感想
其实我不会写D。我一看出题人BledDest,我就知道我完了。
A、Game with Board
1.题目内容
2.个人思路
在n大于4之后,alice只要给bob留两个1就必胜,否则就是bob必胜(可以手推一下)。
3.代码
int main() {
cin >> t;
while (t--) {
cin >> n;
if (n <= 4) {
cout << "Bob\n";
}
else {
cout << "Alice\n";
}
}
return 0;
}
B、Keep it Beautiful
1.题目内容
2.个人思路
暴力处理,第一个数字肯定要加入,然后如果维持单调递增就一直加入,当出现小于当前数字的值之后进行判断,如果它小于等于第一个数字就加入,否则就不加入。
加入第一个非递增数字后,后面的数字也应该单调递增且小于等于第一个数字并大于等于第一个非递增数字。
3.代码
这个写的比较难看。
ll t, n, m, p = 998244353, k;
ll arr[200005], cz[200005];
int main() {
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf_s("%lld", &arr[i]);
}
vector<ll> ans;
ll mn = arr[1], len = 0, stg = 0;
string s;
s += '1';
ans.push_back(arr[1]);
for (int i = 2; i <= n; i++) {
if (arr[i] >= ans[len]) {
if (!stg) {
ans.push_back(arr[i]);
s += '1';
len++;
}
else {
if (arr[i] >= mn && arr[i] <= ans[0]) {
ans.push_back(arr[i]);
s += '1';
len++;
}
else s += '0';
}
}
else if(arr[i] <= ans[0]) {
if (!stg) {
stg = 1;
ans.push_back(arr[i]);
s += '1';
len++;
}
else s += '0';
}
else s += '0';
mn = min(arr[i], mn);
}
cout << s << "\n";
}
return 0;
}
C、Ranom Numbers
1.题目内容
2.个人思路
tn保存倒序处理从n到i的表示的数字, mn表示倒叙从n到i的最大值。
如果最大值与当前值不一样就减掉,否则就加上。
然后正序处理nn。
nn[i][j]表示到第i位的时候,后面的最大值为j的结果。
转移需要找到第一个大于等于j的数字位置pos,然后将(pos, i - 1)的区间和全部减掉,再加上pos处取对应字母的结果。
最后枚举每一位从a取到e的结果,
答案就是nn[i][max(j, mn[i + 1])] + tn[i + 1]的最大值。
3.代码
这个写的更难看了。
struct com {
ll d;
ll h;
ll p;
bool operator < (const com& b) const {
if (b.d == d && b.h == h) return p < b.p;
if (b.d == d) return h < b.h;
return d < b.d;
}
}lis[300005];
ll t, n, m, p = 998244353;
ll arr[200005], tn[200005], mn[200005];
ll nn[200005][5], sum[200005];
ll pp[5] = { 1, 10, 100, 1000, 10000 };
int main() {
cin >> t;
while (t--) {
string s;
cin >> s;
n = s.size();
ll ans = -2147483647ll * 100000ll;
ll st = 0, tp[5] = {-1, -1, -1, -1, -1};
for (int i = 0; i <= n; i++) {
for (int j = 0; j < 5; j++) {
nn[i][j] = 0;
}
mn[i] = 0;
tn[i] = 0;
sum[i] = 0;
}
for (int i = n - 1; i >= 0; i--) {
mn[i] = max(mn[i + 1], (ll)s[i] - 'A');
if (mn[i] != (s[i] - 'A')) {
tn[i] = tn[i + 1] - pp[s[i] - 'A'];
}
else tn[i] = tn[i + 1] + pp[s[i] - 'A'];
}
for (int i = 0; i < n; i++) {
ll tmp = 0, t1 = 10000, t2 = 0;
for (int j = 0; j < 5; j++) {
ll pos = -1, tar = -1;
for (int k = 0; k < 5; k++) {
if (k >= j) {
if (pos < tp[k]) {
pos = tp[k];
tar = k;
}
}
}
if (pos != -1) {
nn[i][j] = pp[j] - sum[i - 1] + sum[pos] + nn[pos][tar];
}
else nn[i][j] = pp[j] - sum[i - 1];
}
tp[s[i] - 'A'] = i;
if (i > 0) sum[i] = sum[i - 1];
sum[i] += pp[(s[i] - 'A')];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < 5; j++) {
ll t1 = max(mn[i + 1], (ll)j);
ans = max(ans, tn[i + 1] + nn[i][t1]);
}
}
cout << ans << "\n";
}
return 0;
}
D、Pairs of Segments
1.题目内容
2.个人思路
我不会,打acm的大佬同学教我的。
这个题的好像思路很多,怎么处理都可以,我这里只提供个人理解的方法。
按照右端点排序,dp[i]为到第i位为止的最大数量。
状态转移肯定就是下面这两种情况。
dp[i] = max(dp[j] + 2, dp[i]);
dp[i] = max(dp[j], dp[i]);
考虑什么时候+2,什么时候不+2。
很显然,如果有区间能和i构成+2,且这个区间的左端点和i的左端点都不和j相交,就可以转移并+2。
我画了几个图,可以参考下,假设i = 3,j = 1。
只需要处理出i - 1到j + 1之间跟i相交的最大左端点距离,然后判断这个最大左端点是否大于j的右端点即可。最优状态一定会移动到n。
时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)
3.代码
struct com {
ll d;
ll h;
ll p;
bool operator < (const com& b) const {
if (b.d == d && b.h == h) return p < b.p;
if (b.d == d) return h < b.h;
return d < b.d;
}
}lis[200005], ord[200005];
ll t, n, m, p = 998244353;
vector<ll> gra[2005];
unordered_set<ll> st;
ll dp[2005], mn[2005][2005];
int main() {
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> lis[i].d >> lis[i].h;
lis[i].p = i;
ord[i] = { lis[i].h , lis[i].d , lis[i].p };
dp[i] = 0;
mn[i][i + 1] = -1;
}
mn[0][1] = -1;
for (int i = n; i >= 0; i--) {
for (int j = i; j >= 0; j--) {
mn[i][j] = -1;
}
}
sort(ord + 1, ord + 1 + n);
ord[0] = { -1, -1, 0 };
for (int i = n; i > 0; i--) {
for (int j = i; j > 0; j--) {
if (ord[j].d >= ord[i + 1].h) {
mn[i][j] = max(mn[i][j + 1], ord[j].h);
}
else mn[i][j] = mn[i][j + 1];
}
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (ord[j].d < mn[i - 1][j + 1] && ord[j].d < ord[i].h) {
dp[i] = max(dp[j] + 2, dp[i]);
}
else {
dp[i] = max(dp[j], dp[i]);
}
}
}
ll ans = 2147483647;
ans = n - dp[n];
cout << ans << "\n";
}
return 0;
}
E. Fill the Matrix
1.题目内容
2.个人思路
硬找所有可以插数字的区间,vector用来记录到第i行时可以开始插入数字的列下标。le,re分别记录区间的左端点和右端点。
每个列下标只会修改最多两个区间,然后分情况讨论。
两边都没区间,那么新增一个长度为1的区间。
左边是右边不是,和左边的合并。
右边是左边不是,和右边的合并。
两边都是,两边合并。
每次都需要记录当前行所有区间的长度,并将结果加到总记录cnt上。
最后按长度从大到小往里放数字就可以。
其实就是看[1, m]个区间最少被成多少份的问题。
理论最差时间复杂度为
O
(
n
n
)
O(n\sqrt{n})
O(nn)。
因为同时出现的不同区间长度最多为
n
\sqrt{n}
n个。
3.代码
废话文学代码,其实可以合并优化优化.
ll t, n, m, p = 998244353;
ll arr[200005], tmp[200005], cnt[200005];
unordered_map<ll, ll> le, re;
vector<ll> all[200005];
int main() {
cin >> t;
while (t--) {
scanf_s("%lld", &n);
for (int i = 1; i <= n + 1; i++) {
all[i].clear();
tmp[i] = 0;
cnt[i] = 0;
}
for (int i = 1; i <= n; i++) {
scanf_s("%lld", &arr[i]);
all[arr[i] + 1].push_back(i);
}
cin >> m;
le.clear();
re.clear();
unordered_map<ll, ll> len;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < all[i].size(); j++) {
ll x = all[i][j];
ll ttt = i, i = x;
if (tmp[i - 1] == 0 && tmp[i + 1] == 0) {
le[i] = i;
re[i] = i;
len[1]++;
}
else if (tmp[i - 1] == 1 && tmp[i + 1] == 0) {
ll t1 = re[(ll)i - 1], l = (ll)i - t1;
re.erase((ll)i - 1);
re[i] = t1;
le[t1] = i;
len[l]--;
if (len[l] == 0) len.erase(l);
len[l + 1]++;
}
else if (tmp[i - 1] == 0 && tmp[i + 1] == 1) {
ll t1 = le[(ll)i + 1], l = t1 - i;
le.erase((ll)i + 1);
le[i] = t1;
re[t1] = i;
len[l]--;
if (len[l] == 0) len.erase(l);
len[l + 1]++;
}
else {
ll t2 = le[(ll)i + 1], t1 = re[(ll)i - 1];
ll l1 = (ll)i - t1, l2 = t2 - i;
re.erase((ll)i - 1);
le.erase((ll)i + 1);
le[t1] = t2;
re[t2] = t1;
len[l1]--;
if (len[l1] == 0) len.erase(l1);
len[l2]--;
if (len[l2] == 0) len.erase(l2);
len[t2 - t1 + 1]++;
}
i = ttt;
tmp[x] = 1;
}
for (auto x : len) {
cnt[x.first] += x.second;
}
}
ll ans = m, tt = m;
for (int i = n; i > 0; i--) {
if (!tt) break;
ans -= min(cnt[i], tt / i + (tt % i != 0));
tt -= min(tt, cnt[i] * i);
}
cout << ans << "\n";
}
return 0;
}
总结
同学真的好强。