2021杭电多校第三场
1003:Forgiving Matching
https://codeforces.com/problemset/problem/528/D 这道题和这个差不多
\(a, b\) 串,\(|a| = n, |b| = m\), 求 \(a\) 的所有长为 \(m\) 的子串有多少位置与 \(b\) 相同
由于字符集很小,考虑每个字符对答案的影响。
考虑\(a_i=b_j\), 那么在 \(a\) 中以 \(i − j + m\) 的位置为结尾的长度为 \(m\) 的子串中,他的贡献是 \(1\),由于其满足 \(i + m − j = k\),k就是位置
所以想到用 \(FFT\) 计算。为了处理 \(m − j\) ,考虑将其 \(reverse\) 一下,这样其实就变成了 \(i + j = k\) ,直接 \(FFT\) 卷。
假设我们已经卷出来 \(f\) 数组了
先考虑没有通配符的情况,我们可以发现 \(m − f[i]\) 就是他不能匹配的数量,所以给 $ans[m − f[i]]++ $。
对于通配符,我们考虑容斥的来求,即 \(a_{cnt}+b_{cnt}-f_{cnt}\)
\(f_{cnt}\)即对 \(*\) 卷一下得到的答案。
我们发现最终卷完只有指数为 \(n\) 以内的项有用,所以在卷的时候卷到 \(n\) 即可。
#include<bits/stdc++.h>
//#define int long long
using namespace std;
struct Complex{
double x, y;
Complex(double x = 0, double y = 0) : x(x), y(y){}
Complex conj(){return Complex(x,-y);}
Complex operator * (const Complex a) {
return Complex(x * a.x - y * a.y, x * a.y + y * a.x);
}
Complex operator + (const Complex a) {
return Complex(x + a.x, y + a.y);
}
Complex operator - (const Complex a) {
return Complex(x - a.x, y - a.y);
}
Complex operator / (const double a) {
return Complex(x / a, y / a);
}
Complex operator * (const double a) {
return Complex(x * a, y * a);
}
};
class FFT{
public:
int l, bit;
const double PI = acos(-1.0);
vector<int> rev;
vector<Complex> w, ww;
inline void init(int n) {
l = 1; bit = 0;
while(l < n) l <<= 1, ++bit;
rev.resize(l + 1);
w.resize(l + 1);
ww.resize(l + 1);
//for(int i = 0; i <= l; ++ i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
for(int i = 0; i <= l; ++ i) {
rev[i] = rev[i >> 1] >> 1 | (i & 1 ? l >> 1 : 0);
//ww[i] = w[i] = {cos(PI * i / l), sin(PI * i / l)};
//ww[i].y = -ww[i].y;
}
}
void fft(vector<Complex> &a, int type) {
a.resize(l);
for(int i = 0; i < l; ++ i) if(i < rev[i]) swap(a[i], a[rev[i]]);//蝴蝶变换
for(int mid = 1, d = 0, z = __builtin_ctz(l); mid < l; mid <<= 1, ++ d) {
Complex wn(cos(PI / mid), type * sin(PI / mid));
for(int i = mid << 1, pos = 0; pos < l; pos += i) {
Complex w(1, 0);
for(int k = 0; k < mid; ++ k, w = w * wn) {
/*Complex &x = a[pos + k + mid];
Complex &y = a[pos + k];
Complex t = (type == 1 ? w[k<<(z-d)] : ww[k<<(z-d)]) * x;
x = y - t; y = y + t;*/
Complex x = a[pos + k];
Complex y = w * a[pos + k + mid];
a[pos + k] = x + y;
a[pos + k + mid] = x - y;
}
}
}
if(type == -1) for(int i = 0; i < l; ++ i) a[i].x /= l;//a[i].y /= len;
}
};
FFT tt;
const int maxn = 3e5 + 10;
char s[maxn], p[maxn];
int n1[maxn], g[maxn];
void solve() {
int n, m; scanf("%d %d", &n, &m);
scanf("%s %s", s, p);
reverse(p, p + m);
for(int i = 0; i < n; ++ i) {
n1[i] = 0;
if(i) n1[i] = n1[i - 1];
if(s[i] == '*') n1[i] ++;
} int cnt = 0;
for(int i = 0; i < m; ++ i) if(p[i] == '*') cnt ++;
for(int i = m - 1; i < n; ++ i) {
g[i] = cnt + n1[i];
if(i >= m) g[i] -= n1[i - m];
}
tt.init(n);//卷到n行了,不需要n+m,因为只要n
vector<Complex> a, b;
for(int i = 0; i <= 10; ++ i) {
char id = '0' + i;
if(i == 10) id = '*';
a.resize(n); b.resize(m);
for(int j = 0; j < n; ++ j) a[j] = s[j] == id ? Complex(1, 0) : Complex(0, 0);
for(int j = 0; j < m; ++ j) b[j] = p[j] == id ? Complex(1, 0) : Complex(0, 0);
tt.fft(a, 1); tt.fft(b, 1);
for(int j = 0; j < tt.l; ++ j) a[j] = a[j] * b[j];
tt.fft(a, -1);
for(int j = m - 1; j < n; ++ j) {
int add = (int)(a[j].x + 0.5);
if(i != 10) g[j] += add;
else g[j] -= add;
}
}
/*
//看不懂的标程
for(o=0;o<=10;o++){
char target=o+'0';
if(o==10)target='*';
for(i=0;i<k;i++)A[i]=comp(0,0);
for(i=0;i<n;i++)if(a[i]==target)A[i].r=1;?//
for(i=0;i<m;i++)if(b[i]==target)A[i].i=1;?
FFT(A,k,1);//实部和虚部一起卷?
for(i=0;i<k;i++){
j=(k-i)&(k-1);?//这又是啥?
B[i]=(A[i]*A[i]-(A[j]*A[j]).conj())*comp(0,-0.25);?
}
FFT(B,k,-1);
for(i=m-1;i<n;i++){
int tmp=((int)(B[i].r+0.5));
if(o<10)f[i]+=tmp;else f[i]-=tmp;
}
}
*/
vector<int> ans(m * 2);
for(int i = m - 1; i < n; ++ i) ans[m - g[i]] ++;
for(int i = 0; i <= m; ++ i) {
if(i) ans[i] += ans[i - 1];
printf("%d\n", ans[i]);
}
return ;
}
signed main() {
int t = 1;
scanf("%d", &t);
while(t--)
solve();
return 0;
}
1004:斜率模拟一下
void run() {
int n, x1, x2, y1, y2, x, y; scanf("%d", &n);
map<pair<int, int>, int> mp;
for(int i = 1; i <= n; ++ i) {
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
x = x1 - x2; y = y1 - y2;
if(x == 0) mp[{1, 0}] ++;
else if(y == 0) mp[{0, 1}] ++;
else {
int g = gcd(abs(x), abs(y));
x /= g; y /= g;
if(x < 0) x = -x, y = -y;
mp[{x, y}] ++;
}
}
queue<pair<int, int>> q;
for(auto it : mp) q.push({it.second, 1});
for(int i = 1; i <= n; ++ i) {
pair<int, int> top = q.front();
q.pop();
printf("%d\n", i - top.second);
if(top.first > 1) q.push({top.first - 1, top.second + 1});
}
return ;
}
1007:前缀和预处理
也可以二分
const int maxn = 1e6 + 10;
int pre1[maxn], pre2[maxn], pre3[maxn], f[maxn];
char s[20]; int c[200];
void run() {
for(int i = '0'; i <= '9'; ++ i) c[i] = i - 48;
for(int i = 'A'; i <= 'F'; ++ i) c[i] = i - 65 + 10;
int n, m, md; scanf("%d %d", &n, &m);
for(int i = 1; i <= n; ++ i) {
pre1[i] = pre1[i - 1];
pre2[i] = pre2[i - 1];
pre3[i] = pre3[i - 1];;
f[i] = f[i - 1];
scanf("%d %s", &md, s);
pre1[i] += c[s[0]] * 16 + c[s[1]];
pre2[i] += c[s[2]] * 16 + c[s[3]];
pre3[i] += c[s[4]] * 16 + c[s[5]];
if(md == 1) f[i] = i;
}
for(int i = 1; i <= m; ++ i) {
int l, r; scanf("%d %d", &l, &r);
l = max(l, f[r]);
printf("%02X%02X%02X\n", min(255, pre1[r] - pre1[l - 1]), min(255, pre2[r] - pre2[l - 1]), min(255, pre3[r] - pre3[l - 1]));
}
}
1009: 数据随机化 = 乱搞dp贪心取前k大,无需全部状态转移可达到最优
这里是取了前100的a * b最大的转移
#define pi pair<int, int>
#define vec vector<pi>
vec f[101][101];
int a[101][101], b[101][101];
void merge(vec &a, vec &b, vec &c) {
int fa = a.size(), fb = b.size();
for(int i = 0; i < fa; ++ i) c.push_back(a[i]);
for(int i = 0; i < fb; ++ i) c.push_back(b[i]);
sort(c.begin(), c.end(), [](const pi &x, const pi &y){return x.first * x.second > y.first * y.second;});
while(c.size() > 100) c.pop_back();
}
void run() {
int n; scanf("%lld", &n);
for(int i = 1; i <= n; ++ i) for(int j = 1; j <= n; ++ j) scanf("%lld", &a[i][j]);
for(int i = 1; i <= n; ++ i) for(int j = 1; j <= n; ++ j) scanf("%lld", &b[i][j]);
f[1][1].clear(); f[1][1].push_back({a[1][1], b[1][1]});
for(int i = 1; i <= n; ++ i) for(int j = 1; j <= n; ++ j) {
if(i == 1 && j == 1) continue;
f[i][j].clear();
if(i == 1) f[i][j] = f[i][j - 1];
else if(j == 1) f[i][j] = f[i - 1][j];
else merge(f[i - 1][j], f[i][j - 1], f[i][j]);
int sz = f[i][j].size();
for(int k = 0; k < sz; ++ k) f[i][j][k].first += a[i][j], f[i][j][k].second += b[i][j];
}
sort(f[n][n].begin(), f[n][n].end(), [](const pi &x, const pi &y){return x.first * x.second > y.first * y.second;});
printf("%lld\n", f[n][n][0].first * f[n][n][0].second);
}
1010:Road Discount
据说这是一个经典模型,这个题的原题在 https://www.luogu.com.cn/problem/P2619
\(wqs\) 二分用于求解 在n个物品中选最优解,有约束条件必须拿m个这样的
这种模型一般都是一个凸函数,枚举复杂度达到 \(n^2\) ,而通过 \(wqs\) 二分可优化到 \(nlogn\)
图上有黑色边和白色边,求出刚好拿 \(k\) 条黑边的最小生成树,每条边的权值范围是 \([-1000, 1000]\)
考虑就是给每条黑边加上一个固定权值 \(c\),然后求解最小生成树,求出权值相同时优先黑边的 \(r(c)\),优先白边的 \(l(c)\)
如果k在范围 \(l(c), r(c)\) 内,那么显然解就求出来了, \(ans = sum - k * c\),有优化是对于黑边求最小生成树,对白边求最小生成树,显然最终的最小生成树只可能由两棵树中的边组成,将边的数量减到 \(O(n)\),还有桶排序的kruscal优化
int n, m, V = 1000, k;
struct edge{
int u, v, w;
edge(int u = 0, int v = 0, int w = 0) : u(u), v(v), w(w) {}
bool operator < (const edge &a) const {
return w < a.w;
}
}a[maxn], b[maxn];
pair<int, int> fl[maxn];
int fa[maxn];
inline int get(int x) {
return x == fa[x] ? x : fa[x] = get(fa[x]);
}
inline int un(int x, int y) {
if(get(x) == get(y)) return 0;
fa[fa[x]] = fa[y];
return 1;
}
vector<pair<int, int>> v1[1001], v2[1001];
inline int reduce(vector<pair<int, int>> *v, edge *a) {
for(int i = 1; i <= n; ++ i) fa[i] = i;
int cnt = 0;
for(int i = 1; i <= 1000 && cnt < n; ++ i)
for(auto it : v[i])
if(un(it.first, it.second)) a[cnt++] = {it.first, it.second, i};
return cnt;
}
int acnt, bcnt;
edge calc(int c) {
int sum = 0, cnt = 0, a1 = 0, b1 = 0;
for(int i = 1; i <= n; ++ i) fa[i] = i;
while(a1 < acnt && b1 < bcnt) {
if(b[b1].w + c <= a[a1].w) {//最多
if(un(b[b1].u, b[b1].v)) sum += b[b1].w + c, cnt ++;
b1 ++;
}
else {
if(un(a[a1].u, a[a1].v)) sum += a[a1].w;
a1 ++;
}
}
while(b1 < bcnt) {
if(un(b[b1].u, b[b1].v)) sum += b[b1].w + c, cnt ++;
b1 ++;
}
while(a1 < acnt) {
if(un(a[a1].u, a[a1].v)) sum += a[a1].w;
a1 ++;
}
return edge(0, cnt, sum);//(l(c), r(c), sum)
}
void run() {
scanf("%d %d", &n, &m); int u, v, w1, w2;
for(int i = 0; i < m; ++ i) {
scanf("%d %d %d %d", &u, &v, &w1, &w2);
v1[w1].push_back({u, v});
v2[w2].push_back({u, v});
}
acnt = reduce(v1, a);
bcnt = reduce(v2, b);
for(int i = 1; i <= 1000; ++ i) {
v1[i].clear(); v2[i].clear();
}
for(int k = 0; k < n; ++ k) {
int l = -1000, r = 1000, ans = 0x7fffffff;
while(l <= r) {
int mid = l + r >> 1;
edge res = calc(mid);
if(res.v >= k) l = mid + 1;
else r = mid - 1;
}
edge res = calc(l - 1);
ans = res.w - (l - 1) * k;
printf("%d\n", ans);
}
}
很好,这是过不了的!复杂度是 \(O(T * n * m * log(2000)) -> O(10 * 1000 * 2000 * log(2000)) -> 2e8!\)
既然要求这么多次的 \(c\),那么先预处理出所有的 \(c\)
vector<pair<int, int>> v1[1001], v2[1001];
inline int reduce(vector<pair<int, int>> *v, edge *a) {
for(int i = 1; i <= n; ++ i) fa[i] = i;
int cnt = 0;
for(int i = 1; i <= 1000 && cnt < n; ++ i)
for(auto it : v[i])
if(un(it.first, it.second)) a[cnt++] = {it.first, it.second, i};
return cnt;
}
int acnt, bcnt;
edge val[maxn];
edge calc(int c) {
int sum = 0, cnt = 0, a1 = 0, b1 = 0;
for(int i = 1; i <= n; ++ i) fa[i] = i;
while(a1 < acnt && b1 < bcnt) {
if(b[b1].w + c < a[a1].w) {//最少
if(un(b[b1].u, b[b1].v)) sum += b[b1].w + c, cnt ++;
b1 ++;
}
else {
if(un(a[a1].u, a[a1].v)) sum += a[a1].w;
a1 ++;
}
}
while(b1 < bcnt) {
if(un(b[b1].u, b[b1].v)) sum += b[b1].w + c, cnt ++;
b1 ++;
}
while(a1 < acnt) {
if(un(a[a1].u, a[a1].v)) sum += a[a1].w;
a1 ++;
}
return edge(cnt, 0, sum);//(l(c), r(c), sum)
}
void run() {
scanf("%d %d", &n, &m); int u, v, w1, w2;
for(int i = 0; i < m; ++ i) {
scanf("%d %d %d %d", &u, &v, &w1, &w2);
v1[w1].push_back({u, v});
v2[w2].push_back({u, v});
}
acnt = reduce(v1, a);
bcnt = reduce(v2, b);
for(int i = 1; i <= 1000; ++ i) {
v1[i].clear(); v2[i].clear();
}
for(int i = -1000; i <= 1000; ++ i) val[i + 1000] = calc(i);
for(int i = 0; i < n; ++ i) {
int l = 0, r = 2000;
while(l <= r) {
int mid = l + r >> 1;
if(val[mid].u > i) l = mid + 1;
else r = mid - 1;
}
printf("%d\n", val[r + 1].w - i * (r - 999));
}
}
这样复杂度是 \(O(2000 * m) + nlog(2000) = O(2000 * 2000) + 1000 * log(2000)\) 是ok的
\(~\)
1011:记忆化
对于每一层每一段,要么是n,要么是n+1,有大量重复的,长度相同等价,所以记忆化log的复杂度
map<int, int> dp;
int build(int l, int r) {
if(r - l + 1 <= k) return 1;
if(dp.count(r - l + 1)) return dp[r - l + 1];
int mid = l + r >> 1;
return dp[r - l + 1] = build(l, mid) + build(mid + 1, r) + 1;
}

浙公网安备 33010602011771号