1. 程式人生 > 實用技巧 >php基礎-14

php基礎-14

A-All with Pairs

題面

樣例

輸入

3
ab
ba
aba

輸出

29

說明

題意

給出n個字串,求每個字串和其他字串(包括自己)的字首和字尾相同的最大的長度,答案為所有長度的平方和。

題解

如果單純求出所有的字首和字尾相同的長度平方和,那麼我們可以列舉字首,利用hash儲存所有的字尾,每次看看有多少相同的,統計答案即可。

但是由於我們要的是最大的長度,所以有一些是計算重複的,比如兩個字串“aba“,”aba”,會有兩個字首a和aba被算到,實際上a不是最長的,是不應計算的。所以我們要去重。

去重的方法就是利用kmp的nxt陣列,nxt陣列的含義是:nxt[i]表示最大的x,滿足s[1 : x] 是s[1 : i] 的字尾,這樣我們將cnt[nxt[j]]-=cnt[j]

即可,因為nxt[j]代表的位置是當前列舉的字首的字尾,所以一定被多餘計算了,多餘計算的值就是當前的cnt[j]。

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 1e6 + 50;
const int M = 1e5 + 50;
const int mod = 998244353;
const int base = 233;
char s[N];
vector<long long> h[M];
vector<int> nxt[M];
ll p[N];
int cnt[N];
void getNxt(int id) {
    int m = strlen(s + 1);
    nxt[id].resize(m + 1);
    int j = 0;
    for (int i = 2; i <= m; i++) {
        while (j && s[i] != s[j + 1]) j = nxt[id][j];
        if (s[i] == s[j + 1]) j++;
        nxt[id][i] = j;
    }
}
int main() {
    p[0] = 1;
    for (int i = 1; i < N; i++) p[i] = p[i - 1] * base;
    map<ll, int> mp;
    int n; scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s + 1);
        int len = strlen(s + 1);
        h[i].resize(len + 1);
        h[i][0] = 0;
        for (int j = 1; j <= len; j++) h[i][j] = h[i][j - 1] * base + s[j] - 'a' + 1;
        for (int j = 0; j < len; j++) mp[h[i][len] - h[i][j] * p[len - j]]++;
        getNxt(i);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < h[i].size(); j++) {
            cnt[j] = mp[h[i][j]];
            cnt[nxt[i][j]] -= cnt[j];
        }
        for (int j = 1; j < h[i].size(); j++) 
            ans = (ans + 1ll * cnt[j] * j % mod * j % mod) % mod;
    }
    printf("%d\n", ans);
    return 0;
}

B-Boundary

題面

樣例

輸入

4
1 1
0 2
2 0
2 2

輸出

3

題意

給出n個點,每個圓都過(0,0),找出邊界上點最多的圓,輸出最多的點數

題解

3點確定一個圓,列舉兩個點,我們可以用兩條不共線的弦中垂線的交點求出圓心,如果一個圓上含有x個點,那麼有\(C(x,2)=x*(x-1)/2\)次列舉時都會求出這個圓心,所以求出最多的圓心出現次數,也就求出了答案

兩條中垂線分別為

\[y-\frac{y_i}2=-\frac{x_i}{y_i}(x-\frac{x_i}2)\\ y-\frac{y_j}2=-\frac{x_j}{y_j}(x-\frac{x_j}2) \]

\[2x_ix+2y_iy=x_i^2+y_i^2\\ 2x_jx+2y_jy=x_j^2+y_j^2 \]

\[a11=2x_i,a12=2y_i\\ a21=2x_j,a22=2y_j\\ a13=x_i^2+y_i^2\\ a14=x_j^2+y_j^2 \]

使用克萊姆法則求方程組的解即可

程式碼

#include <bits/stdc++.h>
#define pdd pair<double, double>
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;
 
const int N = 2e5 + 50;
const double eps = 1e-8;
int x[N], y[N];
template <typename T> T cross(T a, T b, T c, T d) {
    return a * d - b * c;
}
bool check(pdd a, pdd b) {
    if (fabs(a.first - b.first) > eps) return false;
    if (fabs(a.second - b.second) > eps) return false;
    return true;
}
int main() {
    int n; in >> n;
    for (int i = 1; i <= n; i++) in >> x[i] >> y[i];
    
    int mx = 0;
    vector<pdd> s;
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            if (x[i] * y[j] - x[j] * y[i] == 0) continue;
            ll a11 = 2 * x[i], a12 = 2 * y[i], a13 = x[i] * x[i] + y[i] * y[i];
            ll a21 = 2 * x[j], a22 = 2 * y[j], a23 = x[j] * x[j] + y[j] * y[j];
            ll d0 = cross(a11, a12, a21, a22);
            ll d1 = cross(a13, a12, a23, a22);
            ll d2 = cross(a11, a13, a21, a23);
            s.push_back(pdd((double)d1 / d0, (double)d2 / d0));
        }
    }
    sort(s.begin(), s.end());
    int now = 1;
    for (int i = 1; i < s.size(); i++) {
        if (check(s[i], s[i - 1])) now++;
        else now = 1;
        mx = max(mx, now);
    }
    for (int i = 1; i <= 2000; i++) {
        if ((i - 1) * i / 2 == mx) {
            printf("%d\n", i);
            break;
        }
    }
    return 0;
}

C-Cover the Tree

題面

樣例

輸入

5
1 2
1 3
2 4
2 5

輸出

2
2 3
4 5

說明

題意

給一棵樹,求最少的鏈覆蓋所有的邊,輸出最少的鏈的數目和方案

題解

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;

const int N = 2e5 + 50;
int ind[N];
vector<int> G[N];
vector<int> s;
void dfs(int u, int f) {
    if (ind[u] == 1) s.push_back(u);
    for (auto v : G[u]) {
        if (v != f) dfs(v, u);
    }
}
int main() {
    int n; in >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        in >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
        ind[u]++; ind[v]++;
    }
    int rt = 1;
    for (int i = 1; i <= n; i++) {
        if (ind[i] > 1) rt = i;
    }
    dfs(rt, 0);
    int m = s.size();
    printf("%d\n", (m + 1) / 2);
    for (int i = 1; i * 2 <= m; i++) printf("%d %d\n", s[i - 1], s[i - 1 + (m + 1) / 2]);
    if (m & 1) printf("%d %d\n", rt, s[(m + 1) / 2 - 1]);
    return 0;
}

D-Duration

簽到題

E-Exclusive Or

等待填坑,這種題不適合我

F-Fake Maxpooling

題面

樣例

輸入

3 4 2

輸出

38

說明

題意

給出一個n*m的矩陣,矩陣中的值\(a[i][j]=lcm(i,j)\),求所有k*k子矩陣的最大值和

題解

可以使用下面的方法O(n*m)的求出矩陣的值

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        if (!Gcd[i][j]) {
            for (int k = 1; k * i <= n && k * j <= m; k++) {
                Gcd[k * i][k * j] = k;
                Lcm[k * i][k * j] = i * j * k;
            }
        }
    }
}

此題n*mlog也可以過

求出值後,使用單調佇列O(n*m)的求出最大值相加即可

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;

const int N = 5050;
int Lcm[N][N];
int q[N];
int mn[N][N];
int main() {
    int n, m, k;
    in >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (!Lcm[i][j]) {
                for (int k = 1; k * i <= n && k * j <= m; k++) {
                    Lcm[k * i][k * j] = i * j * k;
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        int l = 1, r = 0;
        for (int j = 1; j <= m; j++) {
            while (l <= r && j - q[l] >= k) l++;
            while (l <= r && Lcm[i][j] >= Lcm[i][q[r]]) r--;
            q[++r] = j;
            if (j >= k) mn[i][j] = Lcm[i][q[l]];
        }
    }
    ll ans = 0;
    for (int i = k; i <= m; i++) {
        int l = 1, r = 0;
        for (int j = 1; j <= n; j++) {
            while (l <= r && j - q[l] >= k) l++;
            while (l <= r && mn[j][i] >= mn[q[r]][i]) r--;
            q[++r] = j;
            if (j >= k) ans += mn[q[l]][i];
        }
    }
    printf("%lld\n", ans);
    return 0;
}

G-Greater and Greater

題面

樣例

輸入

6 3
1 4 2 8 5 7
2 3 3

輸出

2

說明

The two subintervals are [2,8,5],[8,5,7]

題意

給出兩個序列A,B,求A有多少長度為\(len(B)\)的子序列,滿足子序列每一項都大於B

題解

這種資料範圍可以考慮使用bitset卡常,使用bitset後,複雜度會等於\(\frac{n*m}w\)

w一般取32,跟計算機位數有關。

讓每一個b[i]對應一個bitset,這個bitset的第j位為1表示a[j]>b[i],把所有bitset構造出來後,如果b[0]對應的第i位,b[1]對應的第i+1位...b[m-1]對應的第i+m-1位均為1,則說明a陣列從i開始的一段長為m的子區間是滿足答案的,也就對答案貢獻1。

為了方便統計,可以讓b[i]對應的bitset右移i位(i從0開始),這樣如果有一列的與為1,則說明從這一列開始的m長度區間是滿足答案的。

還可以發現,如果把a,b分別排序,同時記錄原位置,開一個pair記錄,first為值,second為位置。那麼這m個bitset是存在遞推關係的,從b最大的開始,較小的數可以直接繼承較大數的bitset,然後將\(a[j].first>b[i].first\)\(a[j].second\)位置1,就得到了\(b[i].second\)對應的bitset,然後把這m個bitset做&操作,統計最後1的個數即為答案。

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;

const int N = 2e5 + 50;
bitset<N> ans, bs;
pair<int, int> a[N], b[N];
int main() {
    int n, m; in >> n >> m;
    for (int i = 0; i < n; i++) {
        in >> a[i].first;
        a[i].second = i;
    }
    for (int i = 0; i < m; i++) {
        in >> b[i].first;
        b[i].second = i;
    }
    sort(a, a + n);
    sort(b, b + m);
    ans.set();
    for (int i = m - 1, j = n - 1; i >= 0; i--) {
        while (j >= 0 && a[j].first >= b[i].first) {
            bs.set(a[j].second);
            j--;
        }
        ans &= bs >> b[i].second;
    }
    printf("%d\n", ans.count());
    return 0;
}

H-Happy Triangle

題面

樣例

輸入

8
1 1
3 1
1 1
3 2
3 1
1 2
2 1
3 1

輸出

No
No
Yes
No

題意

q次操作,操作1代表向multiset中插入一個x,操作2代表從multiset中刪除x,操作3詢問multiset中是否存在兩個數與x可以構成三角形,對於每個操作3,輸出yes/no

題解

首先,在回答詢問的時候可以只考慮相鄰的兩個數。因為對於一組a,b,x,不妨設\(a \le b\),如果能構成三角形,a可以換成b的前驅而不影響答案。

對於3操作,分類討論

  • x是最大邊,那麼找到x的兩個前驅a,b,判斷\(a+b>x\)是否成立即可

  • x不是最大邊,那麼需要找到相鄰的兩個數a,b,\(b \ge x, a+x \ge b\)\(x \ge b - a\),所以對於每個數維護它和它前驅的差,查詢時找到大於x的差的最小值與x比較。

對於第一種情況,維護一個multiset即可,對於第二種情況,可以使用動態開點線段樹,每個點的下標是它的值,權值為它與他前驅的差,使用一個map來判斷增加的值是否重複,刪除是否把一個值刪除完。

程式碼

#include <bits/stdc++.h>
#define mid (l+r>>1)
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;

const int N = 2e6 + 50;
const int inf = 0x3f3f3f3f;
multiset<int> st;
map<int, int> mp;
int mn[N << 2], L[N << 2], R[N << 2], tot, rt;
void pushup(int o) {
    mn[o] = min(mn[L[o]], mn[R[o]]);
}
void update(int &o, int l, int r, int x, int v) {
    if (!o) o = ++tot;
    if (l == r) {
        mn[o] = v;
        return;
    }
    if (x <= mid) update(L[o], l, mid, x, v);
    else update(R[o], mid + 1, r, x, v);
    pushup(o);
}
int query(int o, int l, int r, int ql, int qr) {
    if (!o) return inf;
    if (l == ql && r == qr) return mn[o];
    if (qr <= mid) return query(L[o], l, mid, ql, qr);
    else if (ql > mid) return query(R[o], mid + 1, r, ql, qr);
    else return min(query(L[o], l, mid, ql, mid), query(R[o], mid + 1, r, mid + 1, qr));
}
void ins(int x) {
    mp[x]++;
    if (!st.size()) {
        st.insert(x);
        return;
    }
    if (mp[x] == 1) {
        auto it = mp.lower_bound(x);
        ++it;
        if (it != mp.end() && it->second == 1) update(rt, 1, 1e9, it->first, it->first - x);
        --it;
        int y = -1e9;
        if (it != mp.begin()) y = prev(it)->first;
        update(rt, 1, 1e9, x, x - y);
    }
    else if (mp[x] == 2) update(rt, 1, 1e9, x, 0);
    st.insert(x);
}
void del(int x) {
    auto it = mp.lower_bound(x);
    mp[x]--;
    int y = -1e9;
    if (it != mp.begin()) y = prev(it)->first;
    if (mp[x] == 0) {
        if ((++it) != mp.end() && it->second == 1) {
            update(rt, 1, 1e9, it->first, it->first - y);
        }
        update(rt, 1, 1e9, x,  inf);
        mp.erase(x);
    }
    else if (mp[x] == 1) update(rt, 1, 1e9, x, x - y);
    st.erase(st.lower_bound(x));
}
void calc(int x) {
    bool ok = false;
    if (st.size() < 2) {
        puts("No");
        return;
    }
    auto it = st.upper_bound(x);
    int a = -inf, b = -inf;
    if (it != st.begin()) a = *(--it);
    if (it != st.begin()) b = *(--it);
    if (a + b > x) ok = true;
    if (query(rt, 1, 1e9, x, 1e9) < x) ok = true;
    if (ok) puts("Yes");
    else puts("No");
}
int main() {
    int q; in >> q;
    memset(mn, inf, sizeof(mn));
    while (q--) {
        int op, x; in >> op >> x;
        if (op == 1) ins(x);
        else if (op == 2) del(x);
        else calc(x);
    }
    return 0;
}

I-Interval

待填坑

J-Just Shuffle

題面

樣例

輸入

3 998244353
2 3 1

輸出

3 1 2

題意

給出一個長度為n的排列\(A\)和k,求一個置換,將$ {1,2,\dots n} \(做k次這個置換後可以得到排列A,求將\) {1,2,\dots n} $做1次這個置換後的值

題解

首先把A所有環求出來,設環長為\(l\),由於k是大質數,所以可以求出k在模\(l\)意義下的逆元\(inv_i\),這樣把這個環逆向轉\(inv_i\)次就可以得到旋轉1次的環,也就是所要求的答案。

逆元可以用尤拉定理求

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct READ {
    inline char read() {
    #ifdef _WIN32
        return getchar();
    #endif
        static const int IN_LEN = 1 << 18 | 1;
        static char buf[IN_LEN], *s, *t;
        return (s == t) && (t = (s = buf) + fread(buf, 1, IN_LEN, stdin)), s == t ? -1 : *s++;
    }
    template <typename _Tp> inline READ & operator >> (_Tp&x) {
        static char c11, boo;
        for(c11 = read(),boo = 0; !isdigit(c11); c11 = read()) {
            if(c11 == -1) return *this;
            boo |= c11 == '-';
        }
        for(x = 0; isdigit(c11); c11 = read()) x = x * 10 + (c11 ^ '0');
        boo && (x = -x);
        return *this;
    }
} in;

const int N = 2e5 + 50;
int a[N];
int f[N], p[N];
int tot;
void euler(int n) {
    f[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!f[i]) p[++tot] = i, f[i] = i - 1;
        for (int j = 1; j <= tot && i * p[j] <= n; j++) {
            if (i % p[j] == 0) {
                f[i * p[j]] = f[i] * p[j];
                break;
            }
            f[i * p[j]] = f[i] * (p[j] - 1);
        }
    }
}
int vis[N];
int qpow(int a, int b, int p) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1ll * ans * a % p;
        a = 1ll * a * a % p;
        b >>= 1;
    }
    return ans;
}
int ans[N];
int main() {
    int n, k; in >> n >> k;
    euler(n);
    for (int i = 1; i <= n; i++) in >> a[i];
    for (int i = 1; i <= n; i++) {
        if (vis[i]) continue;
        vis[i] = 1;
        int j = i;
        vector<int> b;
        b.push_back(j);
        while (a[j] != i) {
            b.push_back(a[j]);
            j = a[j];
            vis[j] = 1;
        }
        int r = qpow(k % b.size(), f[b.size()] - 1, b.size());
        for (int i = 0; i < b.size(); i++) {
            ans[b[i]] = b[(i + r) % b.size()];
        }
    }
    for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
    return 0;
}

K-Keyboard Free

待填坑