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号