这几天又忘记每天复习了,以后在实验室复习完再回去好了
最近做1800的题目好多dp啊太ex了
文章目录
- 牛客 练习赛122D 圆
- CF 1396B Stoned Game
- CF 1355C Count Triangles
- CF 1437C Chef Monocarp
- CF 271D Good Substrings
- CF 1475D Cleaning the Phone
- CF 1362D2 Prefix-Suffix Palindrome (Hard version)
- CF 722C Destroying Array
牛客 练习赛122D 圆
题目链接
虽然没能自己做出来但是还是挺高兴的,首先是因为用到了之前在atc碰到过然后记录下来的一个trick,就是把圆拉成一条线,弦看作曲线来判断有没有交点,然后也自己想到了转换成求区间最大的思路
剩下没想到的点就是:要记录下来以每个点结束的弦的起始位置,之后更新的时候会用到
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;void solve()
{int n, m;cin >> n >> m;int sum = 0;vector<vector<int>> w(n + 1, vector<int>(n + 1));vector<set<int>> eg(n + 1);for (int i = 0; i < m; i ++ ){int a, b, c;cin >> a >> b >> c;sum += c;w[min(a, b)][max(a, b)] = max(w[min(a, b)][max(a, b)], c);eg[max(a, b)].insert(min(a, b));}vector<vector<int>> dp(n + 1, vector<int>(n + 1));for (int len = 2; len <= n; len ++ ){for (int l = 1; l + len - 1 <= n; l ++ ){int r = l + len - 1;dp[l][r] = max(dp[l][r], dp[l + 1][r - 1]);dp[l][r] = max({dp[l][r], dp[l + 1][r], dp[l][r - 1], dp[l + 1][r - 1] + w[l][r]});for (auto ver : eg[r]){if (ver < l) continue;dp[l][r] = max({dp[l][r], dp[l][ver - 1] + dp[ver][r]});}}}cout << sum - dp[1][n] << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;// cin >> t;while (t -- ){solve();}
}
CF 1396B Stoned Game
题目链接
感觉没有1800 虚高了
优先队列模拟一下,每次取能取的最多的就可以
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;void solve()
{int n;cin >> n;vector<int> a(n);priority_queue<PII> q;for (int i = 0; i < n; i ++ ){cin >> a[i];q.push({a[i], i});}int idx1 = -1, idx2 = -1;bool flag = true; // true表示T的回合 反之while (q.size()){auto t = q.top();q.pop();if (flag){if (t.second == idx2){if (q.size() == 0){cout << "HL\n";return;}auto tt = q.top();q.pop();q.push(t);idx1 = tt.second;tt.first -- ;if (tt.first != 0) q.push(tt);}else{t.first -- ;idx1 = t.second;if (t.first != 0) q.push(t);}flag = false;}else{if (t.second == idx1){if (q.size() == 0){cout << "T\n";return;}auto tt = q.top();q.pop();q.push(t);idx2 = tt.second;tt.first -- ;if (tt.first != 0) q.push(tt);}else{t.first -- ;idx2 = t.second;if (t.first != 0) q.push(t);}flag = true;}}if (flag) cout << "HL\n";else cout << "T\n";
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t -- ){solve();}
}
CF 1355C Count Triangles
题目链接
组合数学
枚举x+y的值,因为x最小是a,y最小是b,所以x+y最小是a+b,同时z最小是c,所以x+y最小要是c+1,因此x+y的最小值为max(a+b, c+1)
,最大值是c+b
现在x+y=i,有多少符合条件的z呢?z的最大值是d和 i - d 里小的那个,最小值是c,所以z有min(i - 1, d) - c + 1
种情况
再看对于每个y有多少个符合条件的x呢?
y的取值范围是bc之间,所以x的取值范围是[i-c, i-b]
,同时x还有[a,b]
的限制,于是x的取值情况min(i - b, b) - max(i - c, a) + 1
乘法原理
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;void solve()
{int a, b, c, d;cin >> a >> b >> c >> d;int ans = 0;for (int i = max(a + b, c + 1); i <= c + b; i ++ ) // 枚举x+y{ans += (min(i - 1, d) - c + 1) * (min(i - b, b) - max(i - c, a) + 1);}cout << ans << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;// cin >> t;while (t -- ){solve();}
}
CF 1437C Chef Monocarp
题目链接
dp
dp[i][j]
表示第 i 盘菜在第 j 时间拿出来的最小值
转移方程:dp[i][j] = min(dp[i][j - 1], dp[i - 1][j - 1] + abs(t[i] - j))
(dp真的好难想到啊…
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;void solve()
{int n;cin >> n;vector<int> t(n + 1);for (int i = 1; i <= n; i ++ ) cin >> t[i];sort(t.begin(), t.end());vector<vector<int>> dp(n + 1, vector<int>(2 * n + 1, INF));for (int i = 0; i <= 2 * n; i ++ ) dp[0][i] = 0;for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= n * 2; j ++ ){dp[i][j] = min(dp[i][j - 1], dp[i - 1][j - 1] + abs(t[i] - j));}}int ans = INF;for (int i = 1; i <= n * 2; i ++ ) ans = min(ans, dp[n][i]);cout << ans << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t -- ){solve();}
}
CF 271D Good Substrings
题目链接
第一次写出来1800的字符串!
一开始想单纯枚举左右端点利用前缀和,再放到set里判断,但是substr的复杂度肯定就爆了
然后想到trie树,枚举每个位置为开头,把从该位置一直到末尾的字符串存进trie树就可以啦,然后在每个符合条件的地方都打上标记,最后dfs一下整个树,记录ans
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 2e6 + 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;int k;
map<char, bool> mp;
int nxt[N][26], cnt;
bool st[N]; // 该结点结尾的字符串是否存在
int num[N];
int ans = 0;
void insert(string s, int l) // 插入字符串,l是字符串长度
{ int p = 0;for (int i = 0; i < l; i++){int c = s[i] - 'a';if (!nxt[p][c]) nxt[p][c] = ++ cnt; // 如果没有,就添加结点if (!mp[s[i]]) num[nxt[p][c]] = num[p] + 1;else num[nxt[p][c]] = num[p];p = nxt[p][c];if (num[p] <= k) st[p] = true;}
}void dfs(int u)
{if (st[u]) ans ++ ;for (int i = 0; i < 26; i ++ ){if (!nxt[u][i]) continue;dfs(nxt[u][i]);}
}void solve()
{string s;cin >> s;for (int i = 0; i < 26; i ++ ){char c;cin >> c;if (c == '1') mp[(char)(i + 'a')] = true;else mp[(char)(i + 'a')] = false;}cin >> k;for (int i = 0; i < s.size(); i ++ ){string ns = s.substr(i, s.size() - i);insert(ns, s.size() - i);}dfs(0);cout << ans << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;// cin >> t;while (t -- ){solve();}
}
CF 1475D Cleaning the Phone
题目链接
因为看到b只可能为1/2,所以肯定是有啥特殊的地方
于是想到把b为1和2的分开算,每次枚举在b为1的集合中取了几个(肯定是从大到小取),二分查找b为2的集合中需要取几个,更新答案就好啦
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 2e6 + 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;void solve()
{int n, m;cin >> n >> m;vector<int> a(n + 1), b(n + 1);for (int i = 1; i <= n; i ++ ) cin >> a[i];for (int i = 1; i <= n; i ++ ) cin >> b[i];vector<int> c1, c2;c1.push_back(0), c2.push_back(0);for (int i = 1; i <= n; i ++ ){if (b[i] == 1) c1.push_back(a[i]);else c2.push_back(a[i]);}sort(c1.begin() + 1, c1.end(), greater<int>());sort(c2.begin() + 1, c2.end(), greater<int>());vector<int> pre1(c1.size()), pre2(c2.size());for (int i = 1; i < c1.size(); i ++ ) pre1[i] = pre1[i - 1] + c1[i];for (int i = 1; i < c2.size(); i ++ ) pre2[i] = pre2[i - 1] + c2[i];int ans = INF;for (int i = 0; i < c1.size(); i ++ ){int save = m - pre1[i];int pos = lower_bound(pre2.begin(), pre2.end(), save) - pre2.begin();if (pos == c2.size()) continue;ans = min(ans, pos * 2 + i);}if (ans == INF) ans = -1;cout << ans << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t -- ){solve();}
}
CF 1362D2 Prefix-Suffix Palindrome (Hard version)
题目链接
今天学到的新算法:Manacher马拉车算法
这一题只需要先判断原字符串的前缀和后缀有多少对称的,如果完全对称直接输出原字符串,如果还有剩下的不对称,就求剩下的部分中最长前缀/后缀回文子串(哪个长要哪个),套个板子就可以
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 2e6 + 10;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;int flag;string Manacher(string s)
{int sl = s.size(); // 原字符串长度if (sl == 0 || sl == 1) return s;// 构建新串string ns = "$#";for (int i = 0; i < sl; i ++ ){ns += s[i];ns += '#';}int len = ns.size();int c = 0; // 最靠右的回文子串的中心点下标int m = 0; // 最靠右的回文子串的右端点下标int pos = 0; // 最长回文子串的中心点int maxlen = 0; // 最长回文子串的半径(不包括中心点)(新字符串中)vector<int> p(len); // p[i]表示以i为中心点的回文子串的半径(包括i)for (int i = 1; i < len; i ++ ){if (i < m) p[i] = min(p[c - (i - c)], m - i + 1); // c-(i-c)是i关于c的对称点 当前情况表示i在目前最靠右侧的回文子串中else p[i] = 1 + (ns[i] != '#'); // 当前不是#的话 其两侧就是# 所以半径可以加1if (i - p[i] >= 0 && i + p[i] < ns.size())while (ns[i - p[i]] == ns[i + p[i]]) p[i] ++ ; // 对半成品的i位置进行中心扩散if (i + p[i] - 1 > m) // 产生了比以c为中心时更靠右的回文子串{c = i;m = i + p[i] - 1;}if (p[i] == i && maxlen < p[i]){maxlen = p[i] - 1;pos = i;// flag = 1;}if (p[i] + i == len && maxlen < p[i]){maxlen = p[i] - 1;pos = i;// flag = 2;}}string ans = "";char tmp;for (int i = 0; i < 2 * maxlen * 1; i ++ ) // 遍历最长字串的每个位置 得出原字符串中的最长字串{tmp = ns[pos - (maxlen - 1) + i];if (tmp != '#') ans += tmp;}return ans;
}void solve()
{string s;cin >> s;int idx1 = 0, idx2 = s.size() - 1;while (idx1 < idx2){if (s[idx1] != s[idx2]) break;idx1 ++ , idx2 -- ;}if (s[idx1] != s[idx2]){string ss = s.substr(idx1, idx2 - idx1 + 1);string ans = Manacher(ss);string tmp1 = s.substr(0, idx1), tmp2 = s.substr(idx2 + 1, s.size() - idx2 - 1);ans = tmp1 + ans + tmp2;cout << ans << '\n';}else cout << s << '\n';
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t -- ){solve();}
}
CF 722C Destroying Array
题目链接
没脑子了,用线段树写的,写完一搜题解原来是并查集
#include <bits/stdc++.h>using namespace std;#define int long long
using i64 = long long;typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;const int N = 100010;
const int mod = 998244353;
const int maxn = 1e6 + 10;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;int a[N];struct Node
{int l, r;int maxx, maxl, maxr, sum;
} tr[N * 4];void pushup(Node& u, Node& l, Node& r)
{u.l = l.l, u.r = r.r;u.maxl = l.maxl;if (l.maxl == l.sum && r.maxl > 0) u.maxl += r.maxl;u.maxl = max(-INF, u.maxl);u.maxr = r.maxr;if (r.sum == r.maxr && l.maxr > 0) u.maxr += l.maxr;u.maxr = max(-INF, u.maxr);u.maxx = max({l.maxx, r.maxx, l.maxr + r.maxl, -INF});u.sum = l.sum + r.sum;
}void pushup(int u)
{pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}void build(int u, int l, int r)
{tr[u] = {l, r};if (l == r){tr[u].maxx = tr[u].maxl = tr[u].maxr = tr[u].sum = a[l];return;}int mid = l + r >> 1;build(u << 1, l, mid);build(u << 1 | 1, mid + 1, r);pushup(u);
}void modify(int u, int pos, int x)
{if (tr[u].l == pos && tr[u].r == pos){tr[u].maxx = tr[u].maxl = tr[u].maxr = x;return;}int mid = tr[u].l + tr[u].r >> 1;if (pos <= mid) modify(u << 1, pos, x);else modify(u << 1 | 1, pos, x);pushup(u);
}void solve()
{int n;cin >> n;for (int i = 1; i <= n; i ++ ) cin >> a[i];build(1, 1, n);for (int i = 0; i < n; i ++ ){int pos;cin >> pos;modify(1, pos, -INF);cout << max((i64)0, tr[1].maxx) << '\n';}
}signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int t = 1;// cin >> t;while (t -- ){solve();}
}