CF1700板刷
CF1700板刷
前言:
vp edu的时候逐渐力不从心,于是滚回来板刷1700 qwq
https://codeforces.com/problemset?order=BY_RATING_ASC&tags=1700-
log
3.5 466C
3.6 474D
3.7 339D,126B
3.8 118D,1365D,977F,467C,349B
466C - Number of Ways
前缀和 + STL
小贪心
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5e5 + 5;
ll a[N], pre[N], suf[N], sum, n, ans;
int main () {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], sum += a[i];
if (sum % 3) {
cout << 0;
return 0;
}
sum /= 3;
for (int i = 1; i <= n; i++) pre[i] = pre[i-1] + a[i];
set<int> s;
for (int i = n; i >= 1; i--) {
suf[i] = suf[i+1] + a[i];
if (suf[i] == sum) s.insert (i);
}
for (int i = 2; i < n; i++) {
if (pre[i-1] != sum) continue;
while (s.size() && *s.begin () <= i) s.erase (s.begin());
ans += s.size ();
s.erase (i);
// cout << i << ": ";
// for (auto j : s) cout << j << ' ';
// cout << endl;
}
cout << ans << endl;
}
474D - Flowers
dp + 前缀和
比较浅显的线性dp,转移式子也非常明示。但是最开始做的时候下意识往组合数学推式子方面去想,最终自然是推出了错误的式子。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 5, mod = 1000000007;
int t, k;
ll f[N], sum[N];
int main () {
scanf ("%d%d", &t, &k);
f[0] = 1;
for (int i = 1; i <= 1e5; i++) {
if (i >= k) f[i] = (f[i-1] + f[i-k]) % mod;
else f[i] = f[i-1];
}
for (int i = 1; i <= 1e5; i++) sum[i] = (sum[i-1] + f[i]) % mod;
while (t --) {
int a, b;
scanf ("%d%d", &a, &b);
printf ("%lld\n", (sum[b] - sum[a-1] + mod) % mod);
}
}
339D - Xenia and Bit Operations
线段树基础
啊啊主要是读题读不懂
这个式子大概是逐层合并向上(两两合成),类似线段树的分层过程,只需记录深度单点修改就可。关于记录深度这里:先递归!想不通就类比树的记录深度的dfs过程。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 131072 + 5;
ll a[N * 4], dep[N * 4], n, q, sum;
struct Node {
int l, r;
ll sum;
}st[N * 4]; //千万别忘了 * 4
void pushup (int u){
if (dep[u] & 1) st[u].sum = (st[u<<1].sum | st[u<<1|1].sum);
else st[u].sum = (st[u<<1].sum ^ st[u<<1|1].sum);
}
void modify (int u, int x, int v){
//从u结点开始查询,在x处插入,插入的值为v
if (st[u].l == st[u].r){
st[u].sum = v;
return;
}
int mid = st[u].l + st[u].r >> 1;
if (x <= mid) modify (u << 1, x, v);
else modify (u << 1 | 1, x, v);
pushup (u);//以子更新父
}
void build (int u, int l, int r){
if (l == r) {
st[u] = {l, r, a[r]};
return;
}
st[u] = {l, r};
int mid = l + r >> 1;
build (u << 1, l, mid);
build (u << 1 | 1, mid + 1, r);
dep[u] = dep[u<<1] + 1; //深度更新的顺序: 先建树再更新深度,与dfs跟更新类似
pushup (u);
}
int main () {
//cout << pow (2, 17);
cin >> n >> q;
n = pow (2, n);
for (int i = 1; i <= n; i++) cin >> a[i];
build (1, 1, n);
//cout << sum << endl;
while (q--) {
ll id, x;
cin >> id >> x;
//a[id]修改为x
modify (1, id, x);
cout << st[1].sum << endl;
}
}
//啊啊原来改变是会保留的。。麻了
//线段树性质
126B - Password
字符串基础
加深对于kmp next数组的理解,参考博客
数组一直往前跳(具体看注释)
字符串哈希也可以搞,二分一下
#include <bits/stdc++.h>
#define vi vector<int>
using namespace std;
const int N = 1e6 + 5;
int n, ne[N];
void Next(string &p){
for (int i = 1, j = 0; i < n; i ++){
while (j && p[i] != p[j]) j = ne[j - 1];
if (p[i] == p[j]) j ++;
ne[i] = j;
}
}
int main () {
string s;
cin >> s;
n = s.size ();
Next (s);
//for (int i = 0; i < n; i++) cout << ne[i] << ' ';cout << endl;
int len = ne[n-1], maxn = 0;
for (int i = 1; i < n - 1; i++) maxn = max (maxn, ne[i]); //中间的最长公共前后缀
while (len > maxn) len = ne[len-1]; //后缀出发,找到中间第一个最长公共子串
if (len) cout << s.substr (0, len); //确保该子串不是前缀
else cout << "Just a legend";
}
//既是前缀又是后缀又是中缀的最长子串
//最长公共前后缀next[]\neq答案, 再去check中间是否存在相应串(hash??)
//深刻理解next数组含义!!
//找到的第二个(特判是否在结尾)
//hash: [1,a],[b,c],[d,n] 且 a-1=c-b=n-d 且 a,c < n; b,d > 1
//根据len存值?
118D - Caesar's Legions
线性dp
总结:
转移式子想了很久,究其根本是递推的边界没想明白。最开始写的版本里面都是只继承了相同的数字,并没有考虑新开一段要怎么转移,所以总是少一些数字。
还有就是状态的设计,最开始的状态设计是 表示填了 个0, 个1,当前有连续 个0, 个1。然而这样设计状态的后果就是,最后两维总有一个是0,而这其实可以优化成这样一个状态 :当前有连续 个0/1。
还有就是初始化问题!!考虑一种一个都不放时另一个种的状态(直接转移转移不到)
// LUOGU_RID: 104014869
#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 15, mod = 1e8;
int f[N][N][M][2]; //f[i][j][k][0/1]: 0放了i个,1放了j个,连续k个0/1
int main () {
int n1, n2, k1, k2;
cin >> n1 >> n2 >> k1 >> k2;
//初始化也非常关键
for (int i = 0; i <= k1; i++) f[i][0][i][0] = 1;
for (int i = 0; i <= k2; i++) f[0][i][i][1] = 1;
for (int i = 1; i <= n1; i++) {
for (int j = 1; j <= n2; j++) {
//init: 开新的要从上一个不同的继承
for (int x = 1; x <= min (j, k2); x++) (f[i][j][1][0] += f[i-1][j][x][1]) %= mod;
for (int x = 1; x <= min (i, k1); x++) (f[i][j][1][1] += f[i][j-1][x][0]) %= mod;
//连续
for (int k = 2; k <= min (i, k1); k++) (f[i][j][k][0] += f[i-1][j][k-1][0]) %= mod;
for (int k = 2; k <= min (j, k2); k++) (f[i][j][k][1] += f[i][j-1][k-1][1]) %= mod;
}
}
int ans = 0;
for (int i = 0; i <= k1; i++) (ans += f[n1][n2][i][0]) %= mod;
for (int i = 0; i <= k2; i++) (ans += f[n1][n2][i][1]) %= mod;
cout << ans << endl;
}
1365D - Solve The Maze
小贪心 + bfs + 一丢丢分类讨论
为了让所有的坏人不能到达,所以直接在坏人四周放上墙,看看从终点走能否走到所有好人。
注意特判一些边界(比如没有坏人,好人原本就走不到之类的,可以看看代码)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N = 55;
char a[N][N];
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
int n, m;
bool vis[N][N];
bool Range (int x, int y) {
if (x < 1 || x > n || y < 1 || y > m) return false;
return true;
}
void bfs (int x, int y) {
memset (vis, false, sizeof vis);
queue <pii> q;
q.push ({x, y});
while (!q.empty ()) {
auto t = q.front ();
q.pop ();
int x = t.first, y = t.second;
for (int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
if (!Range (xx, yy) || vis[xx][yy] || a[xx][yy] == '#') continue;
q.push ({xx, yy});
vis[xx][yy] = true;
}
}
}
void solve () {
set<pii> G, B;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
if (a[i][j] == 'G') G.insert ({i, j});
if (a[i][j] == 'B') B.insert ({i, j});
}
}
if (!G.size ()) {
cout << "Yes\n";
return ;
}
//check原有G可达否(终点可达的点是否包含所有G)
bfs (n, m);
// for (int i = 1; i <= n; i++) {
// for (int j = 1; j <= m; j++) {
// cout << vis[i][j] << ' ';
// }
// cout << endl;
// }
// cout << endl;
for (auto i : G) {
int x = i.first, y = i.second;
if (!vis[x][y]) {
cout << "No\n";
return ;
}
}
if (!B.size ()) {
cout << "Yes\n";
return ;
}
// for (auto i : B) {
// if (!vis[i.first][i.second]) B.erase (i);
// }
// cout << "G: \n";
// for (auto i : G) cout << i.first << ' ' << i.second << endl;
// cout << "B: \n";
// for (auto i : B) cout << i.first << ' ' << i.second << endl;
//所有的B四周放上围墙
for (auto i : B) {
int x = i.first, y = i.second;
for (int j = 0; j < 4; j++) {
int xx = x + dx[j], yy = y + dy[j];
if (!Range (xx, yy) || a[xx][yy] == 'B') continue;
if (a[xx][yy] == 'G' || (xx == n && yy == m)) {
cout << "No\n";
return ;
}
a[xx][yy] = '#';
}
}
// for (int i = 1; i <= n; i++) {
// for (int j = 1; j <= m; j++) {
// cout << a[i][j];
// }
// cout << endl;
// }
//再check一遍是不是所有G都可达
bfs (n, m);
for (auto i : G) {
int x = i.first, y = i.second;
if (!vis[x][y]) {
cout << "No\n";
return ;
}
}
cout << "Yes\n";
}
int main () {
int t;
cin >> t;
while (t--) {
solve ();
}
}
//预处理一遍B能到的所有路径,拿墙堵上之后看G能不能到
977F - Consecutive Subsequence
线性dp + map
题意:输出最长连续上升子序列(可断)
也是非常基础的线性dp,dp的STL小应用
map记录ai上一个离他最近的ai-1所在位置
注意一定是边读入边进行!!不然就会出现1 2 3 4 3
这种更新不到4的情况(类比爬山)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
const int N = 2e5 + 5;
int a[N], f[N], pre[N], n; //f[i]: 以i结尾的最长连续上升子序列
int main () {
cin >> n;
map<int, int> mp;
//for (int i = 1; i <= n; i++) cin >> a[i], mp[a[i]] = i;
for (int i = 1; i <= n; i++) {
cin >> a[i]; //一定要边读入边进行: 1 2 3 4 3
if (mp[a[i]-1]) {
//f[i] = max (f[i], f[mp[a[i]-1]] + 1);
if (f[i] < f[mp[a[i]-1]] + 1) {
pre[i] = mp[a[i]-1];
f[i] = f[mp[a[i]-1]] + 1;
}
}
else f[i] = 1;
mp[a[i]] = i;
}
int ans = 1, id = 1;
//for (int i = 1; i <= n; i++) cout << f[i] << ' ';cout << endl;
for (int i = 1; i <= n; i++) {
//cout << i << ' ' << pre[i] << endl;
if (f[i] > ans) ans = f[i], id = i;
}
cout << ans << endl;
vector<int> v;
while (ans--) {
v.push_back (id);
id = pre[id];
}
reverse (v.begin (), v.end ());
for (auto i : v) cout << i << ' ';
}
467C - C. George and Job
线性dp
也是非常裸的dp!!!状态设计出来就没了
表示选到第 个数,选了 段;选与不选这样转移即可
// LUOGU_RID: 104045069
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 5005;
ll a[N], s[N], f[N][N]; //f[i][j]: 选到i选了j段
int n, m, k;
int main () {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) cin >> a[i], s[i] = s[i-1] + a[i];
//for (int i = 1; i <= n; i++) f[i][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
f[i][j] = f[i-1][j]; //不选
if (i >= m) f[i][j] = max(f[i][j], f[i-m][j-1] + (s[i] - s[i-m]));
}
}
cout << f[n][k] << endl;
}
349B - B. Color the Fence
贪心
先确定位数再贪心替换最大的
#include <bits/stdc++.h>
using namespace std;
int a[15], m, n = 9;
int minn = 1e9, id = 0;
int main () {
cin >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
minn = min (minn, a[i]);
}
for (int i = 1; i <= n; i++) {
if (a[i] == minn) id = max (id, i);
}
//先确定位数
int cnt = m / minn;
if (cnt == 0) {
cout << -1;
return 0;
}
if (m % minn == 0) {
while (cnt--) cout << id;
}
else {
m -= minn * cnt;
while (cnt-- > 0) {
m += minn;
//找到<=m的下标最大的
int pos = id;
for (int i = 1; i <= n; i++) {
if (a[i] <= m) pos = i;
}
cout << pos;
m -= a[pos];
if (m <= 0) break;
}
while (cnt-- > 0) cout << id;
}
}
//贪心吗...
//确定位数之后贪心替换