因为疫情推迟返校,在家上网课。
在家的话,自律非常重要,开启自律模式
周四
F. Subsequences of Length Two(dp)
一开始想的是贪心,然后发现不太对。
突然醒悟到dp,显然状态和前i个,用了多少此有关
当前是t[1]的话,还和前面有多少个t[0]有关
于是把有多少个t[0]也加入状态
最后就是一个n^3的dp
一开始给200的数据量,就提示时间复杂度了,做了那么多题,一般没有不拉满的
注意特判一下t的两个字母一样的情况
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 200 + 10;
int dp[N][N][N], n, k, ans;
string s, T;
int main()
{
cin >> n >> k >> s >> T;
s = " " + s;
if(T[0] == T[1])
{
int cnt = 0, len = s.size();
_for(i, 1, len) cnt += (s[i] == T[0]);
cnt = min(cnt + k, n);
printf("%d\n", cnt * (cnt - 1) / 2);
return 0;
}
memset(dp, -0x3f, sizeof dp);
dp[0][0][0] = 0;
_for(i, 1, n)
_for(j, 0, k)
_for(t, 0, i)
{
if(s[i] == T[0])
{
if(t > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j][t - 1]);
if(j > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t] + t);
}
else if(s[i] == T[1])
{
dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j][t] + t);
if(j > 0 && t > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t - 1]);
}
else
{
dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j][t]);
if(j > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t] + t);
if(j > 0 && t > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t - 1]);
}
ans = max(ans, dp[i][j][t]);
}
printf("%d\n", ans);
return 0;
}
周五
keep going 不要停止提升自己
B. Present(异或)
这道题自己想没什么思路,只知道每一位拆开来考虑,然后看最后这一位的奇偶,只能想到这
其实比较靠近了,正解是直接考虑答案的某一位是否为1
比如当前看第k位,那么显然要考虑哪些数对加起来第k位有1,要统计这个个数。
如果两个数加起来第k位有1,那么起码是大于2^k的 但是上限很大,而且大于也不一定这一位为1
那么我们加一个限制条件,每一个数模2 ^ (k + 1) 这样子第一不会影响答案,更高位不影响低位,第二限制了上限
这样处理了之后,两个数加起来第k位有1的话,可以写出取值的区间。
所以枚举每一个数,看谁和它加起来在这个区间里面,这个可以用二分来求,因为要二分所以要先排序。最后要注意去掉自己和自己,以及重复统计。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 4e5 + 10;
int a[N], b[N], n, ans;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(k, 0, 25)
{
_for(i, 1, n) b[i] = a[i] % (1 << (k + 1));
int cnt = 0;
sort(b + 1, b + n + 1);
_for(i, 1, n)
{
int l = lower_bound(b + 1, b + n + 1, (1 << k) - b[i]) - b;
int r = upper_bound(b + 1, b + n + 1, (1 << (k + 1)) - 1 - b[i]) - b - 1;
cnt += r - l + 1;
l = lower_bound(b + 1, b + n + 1, (1 << (k + 1)) + (1 << k) - b[i]) - b;
r = upper_bound(b + 1, b + n + 1, (1 << (k + 2)) - 2 - b[i]) - b - 1;
cnt += r - l + 1;
if((b[i] + b[i]) & (1 << k)) cnt--;
}
if((cnt / 2) & 1) ans += 1 << k;
}
printf("%d\n", ans);
return 0;
}
D. Segment Tree(set+暴力优化)
核心思想是暴力建边,但边数小于等于n - 1的,所以可以保证复杂度。
那么怎么优化呢,先排序,对于当前区间,要看前面区间有多少个右端点在当前区间的
我自己想的时候想到了暴力遍历,树状数组统计个数,但应该用set存右端点二分来找。
提前预处理一下每个右端点对应的标号。用并查集维护一下成不成环。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e5 + 10;
pair<int, int> a[N];
unordered_map<int, int> mp;
int f[N], n, cnt;
set<int> s;
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
void Union(int x, int y) { f[find(x)] = find(y); }
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int l, r;
scanf("%d%d", &l, &r);
a[i] = {l, r};
}
sort(a + 1, a + n + 1);
_for(i, 1, n) mp[a[i].second] = i, f[i] = i;
s.insert(a[1].second);
_for(i, 2, n)
{
auto itl = s.lower_bound(a[i].first);
auto itr = s.lower_bound(a[i].second);
for(auto it = itl; it != itr; it++)
{
if(++cnt >= n || find(i) == find(mp[*it]))
{
puts("NO");
return 0;
}
Union(i, mp[*it]);
}
s.insert(a[i].second);
}
puts(cnt == n - 1 ? "YES" : "NO");
return 0;
}
E. Soldier and Traveling(最大流+输出方案)
最大流题的数据范围只有几百,而且这道题很明显一个士兵看作一股流,也就是最大流问题。
分两个部分,建图和输出方案
建图的话,一个点拆成两个点,源点给入点ai的流量,出点给汇点bi的流量。
然后,有连边以及自己对自己就入点向出点连容量为无限的边。
满流了就符合条件,输出的时候遍历每一条边。
记住有正向边和反向边,因为正向边流了多少,反向边就加多少,所以遍历反向边就可以知道流了多少。
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
typedef long long ll;
const int N = 210;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N], ans[N][N];
int a[N], b[N], n, m, s, t, suma, sumb;
void add(int from, int to, int flow)
{
edge.push_back(Edge{from, to, flow});
g[from].push_back(edge.size() - 1);
edge.push_back(Edge{to, from, 0});
g[to].push_back(edge.size() - 1);
}
bool bfs()
{
memset(d, 0, sizeof(d));
queue<int> q;
q.push(s);
d[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop();
for(auto x: g[u])
{
Edge e = edge[x];
if(!d[e.to] && e.flow)
{
d[e.to] = d[u] + 1;
q.push(e.to);
}
}
}
return d[t];
}
ll dfs(int u, ll in)
{
if(u == t) return in;
ll out = 0;
for(int& i = cur[u]; i < g[u].size(); i++)
{
Edge& e = edge[g[u][i]];
if(d[u] + 1 == d[e.to] && e.flow)
{
ll f = dfs(e.to, min((ll)e.flow, in));
e.flow -= f;
edge[g[u][i] ^ 1].flow += f;
out += f; in -= f;
if(in == 0) break;
}
}
return out;
}
void build()
{
scanf("%d%d", &n, &m);
s = 0; t = 201;
_for(i, 1, n) scanf("%d", &a[i]), add(s, i, a[i]), add(i, i + n, 1e9), suma += a[i];
_for(i, 1, n) scanf("%d", &b[i]), add(i + n, t, b[i]), sumb += b[i];
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
add(u, v + n, 1e9);
add(v, u + n, 1e9);
}
}
int main()
{
build();
ll maxflow = 0;
while(bfs())
{
memset(cur, 0, sizeof(cur));
maxflow += dfs(s, 1e18);
}
if(suma != sumb || suma != maxflow)
{
puts("NO");
return 0;
}
puts("YES");
for(auto e: edge)
if(e.to < e.from && e.from > n)
ans[e.to][e.from - n] = e.flow;
_for(i, 1, n)
{
_for(j, 1, n)
printf("%d ", ans[i][j]);
puts("");
}
return 0;
}
周六
B. Destroying Roads(最短路+分类讨论)
这道题受之前一道题的起发,之前那道题是暴力枚举相交点。
这题类似,暴力枚举路径重叠的部分。
注意重叠的时候是有方向性的,也是有两种情况,我开始WA了一发。
#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;
const int N = 3000 + 10;
int s1, t1, s2, t2, l1, l2, n, m;
vector<int> g[N];
int dis[N][N];
void bfs(int s, int d[])
{
_for(i, 1, n) d[i] = 1e9;
d[s] = 0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int v: g[u])
if(d[u] + 1 < d[v])
{
d[v] = d[u] + 1;
q.push(v);
}
}
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, m)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
scanf("%d%d%d%d%d%d", &s1, &t1, &l1, &s2, &t2, &l2);
_for(i, 1, n) bfs(i, dis[i]);
int ans = -1;
if(dis[s1][t1] <= l1 && dis[s2][t2] <= l2) ans = max(ans, m - dis[s1][t1] - dis[s2][t2]);
_for(i, 1, n)
_for(j, 1, n)
if(dis[s1][i] + dis[i][j] + dis[j][t1] <= l1 && dis[s2][i] + dis[i][j] + dis[j][t2] <= l2)
ans = max(ans, m - (dis[s1][i] + dis[i][j] + dis[j][t1] + dis[s2][i] + dis[j][t2]));
swap(s1, t1);
_for(i, 1, n)
_for(j, 1, n)
if(dis[s1][i] + dis[i][j] + dis[j][t1] <= l1 && dis[s2][i] + dis[i][j] + dis[j][t2] <= l2)
ans = max(ans, m - (dis[s1][i] + dis[i][j] + dis[j][t1] + dis[s2][i] + dis[j][t2]));
printf("%d\n", ans);
return 0;
}
Golf Bot(FFT)
FFT就是多项式乘积优化成nlogn
这类题一般是构造多项式相乘
这道题就是给两个序列a,b
求b序列中有多少个数可以表示为a序列中的一个数,或者两个数之和(可以相同)
可以构造两个一样的多项式,a序列有相应的数的项的系数为1,否则为0
因为还有1个数的情况,所以看作a中有一个0。
这样用多项式乘法,就可以得出所有的和。
注意数据
此题代码可用作模板
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 8e5 + 10; //最高次数4倍
const double pi = acos(-1.0);
struct Complex
{
double x, y;
}a[N], b[N], c[N];
int ans[N], r[N], n, m, l, limit;
Complex operator + (Complex a, Complex b) { return Complex{a.x + b.x, a.y + b.y}; }
Complex operator - (Complex a, Complex b) { return Complex{a.x - b.x, a.y - b.y}; }
Complex operator * (Complex a, Complex b) { return Complex{a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }
void FFT(Complex *A, int type)
{
rep(i, 0, limit)
if(i < r[i])
swap(A[i], A[r[i]]);
for(int mid = 1; mid < limit; mid <<= 1)
{
Complex Wn{cos(pi / mid), type * sin(pi / mid)};
for(int R = mid << 1, j = 0; j < limit; j += R)
{
Complex w{1, 0};
for(int k = 0; k < mid; k++, w = w * Wn)
{
Complex x = A[j + k], y = w * A[j + mid + k];
A[j + k] = x + y;
A[j + mid + k] = x - y;
}
}
}
}
void solve()
{
for(limit = 1, l = 0; limit <= n + m; limit <<= 1, l++);
rep(i, 0, limit) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
FFT(a, 1); FFT(b, 1);
_for(i, 0, limit) c[i] = a[i] * b[i];
FFT(c, -1);
_for(i, 0, n + m) ans[i] = (int)(c[i].x / limit + 0.5);
}
int main()
{
while(~scanf("%d", &n))
{
memset(a, 0, sizeof a);
memset(b, 0, sizeof b);
a[0].x = b[0].x = 1;
_for(i, 1, n)
{
int x; scanf("%d", &x);
a[x].x = b[x].x = 1;
}
m = n = 2e5;
solve();
int cnt = 0;
scanf("%d", &m);
while(m--)
{
int x; scanf("%d", &x);
cnt += (ans[x] > 0);
}
printf("%d\n", cnt);
}
return 0;
}
FFT模板
整理模板
/*
FFT模板 以此题为例
这道题就是给两个序列a,b
求b序列中有多少个数可以表示为a序列中的一个数,或者两个数之和(可以同一个数)
可以构造两个一样的多项式,a序列有相应的数的项的系数为1,否则为0
因为还有1个数的情况,所以看作a中有一个0。
这样用多项式乘法,就可以得出所有的和。
*/
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 8e5 + 10; //答案多项式次数的两倍 也是limit的理论最大值
const double pi = acos(-1.0);
struct Complex
{
double x, y;
}a[N], b[N], c[N];
int ans[N], r[N], n, m, l, limit;
Complex operator + (Complex a, Complex b) { return Complex{a.x + b.x, a.y + b.y}; }
Complex operator - (Complex a, Complex b) { return Complex{a.x - b.x, a.y - b.y}; }
Complex operator * (Complex a, Complex b) { return Complex{a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }
void FFT(Complex *A, int type)
{
rep(i, 0, limit)
if(i < r[i])
swap(A[i], A[r[i]]);
for(int mid = 1; mid < limit; mid <<= 1)
{
Complex Wn{cos(pi / mid), type * sin(pi / mid)};
for(int R = mid << 1, j = 0; j < limit; j += R)
{
Complex w{1, 0};
for(int k = 0; k < mid; k++, w = w * Wn)
{
Complex x = A[j + k], y = w * A[j + mid + k];
A[j + k] = x + y;
A[j + mid + k] = x - y;
}
}
}
}
void solve()
{
for(limit = 1, l = 0; limit <= n + m; limit <<= 1, l++); //FFT只能处理2^t的多项式 limit表示最高次数 n + m即答案多项式次数
rep(i, 0, limit) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
FFT(a, 1); FFT(b, 1); //FFT 系数表示法转化为点值表示法
_for(i, 0, limit) c[i] = a[i] * b[i]; //点值表示法直接相乘
FFT(c, -1); //逆FFT 点值表示法转化为系数表示法 最后要多除个limit(下一行)
_for(i, 0, n + m) ans[i] = (int)(c[i].x / limit + 0.5);
}
int main()
{
while(~scanf("%d", &n))
{
memset(a, 0, sizeof a); //多组数据只用清空a b两个多项式的数组
memset(b, 0, sizeof b);
a[0].x = b[0].x = 1;
_for(i, 1, n)
{
int x; scanf("%d", &x);
a[x].x = b[x].x = 1; //a[i].x表示x^i项的系数
}
m = n = 2e5; //n表示a多项式的次数 m是b多项式的次数
solve();
int cnt = 0;
scanf("%d", &m);
while(m--)
{
int x; scanf("%d", &x);
cnt += (ans[x] > 0);
}
printf("%d\n", cnt);
}
return 0;
}
周日
B. Connecting Universities(贪心)
这题秒啊
我只能想到配对时要尽可能两端的配对,然后就没什么想法了
其实直接想点的配对很难想,考虑配对方案很难想
换一种想法,考虑一条边对答案的贡献。
贪心来讲,一条边左边有x个大学,右边有y个大学,那么它的最大贡献显然是min(x, y)
每条边都这么贪心,就是正确答案,很妙。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int a[N], n, k;
vector<int> g[N];
ll ans;
void dfs(int u, int fa)
{
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
a[u] += a[v];
ans += min(a[v], 2 * k - a[v]);
}
}
int main()
{
scanf("%d%d", &n, &k);
_for(i, 1, 2 * k)
{
int x; scanf("%d", &x);
a[x] = 1;
}
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
printf("%lld\n", ans);
return 0;
}