中考後刷題補題合集

hhhhhua發表於2024-06-18

T1(莫隊,增量式維護答案)

https://www.luogu.com.cn/problem/P1494
1731。
看上一篇總結的莫隊。雙倍經驗。

QAQ
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long LL;
typedef pair<LL, LL> PLL;
const int N = 50010;

int n, m, L;
int cnt[N], w[N];
struct Query { int l, r, bcnt, id;
bool operator< (const Query& W) const
    {
        if (bcnt != W.bcnt) return bcnt < W.bcnt;
        return r > W.r;
}}q[N]; 
PLL ans[N];

void work(int x, int type, LL& res)
{
    res -= (LL)cnt[x] * (cnt[x] - 1) / 2; //無論加還是減,之前的都沒了
    cnt[x] += type;
    if (cnt[x] >= 2) res += (LL)cnt[x] * (cnt[x] - 1) / 2;
}

LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    scanf("%d%d", &n, &m);
    L = sqrt(n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    
    for (int i = 1; i <= m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i] = {l, r, (l - 1) / L + 1, i};
    }
    sort(q + 1, q + m + 1);
    LL res = 0;
    for (int i = 1, l = 1, r = 0; i <= m; i ++ )
    {
        while (l > q[i].l)  -- l, work(w[l], 1, res);
        while (r < q[i].r)  ++ r, work(w[r], 1, res);
        while (l < q[i].l) work(w[l], -1, res), l ++ ;
        while (r > q[i].r) work(w[r], -1, res), r -- ;
        
        if (!res) ans[q[i].id] = {0, 1};
        else
        {
            LL a = q[i].r - q[i].l + 1;
            LL k = gcd(res, a * (a - 1) / 2);
            ans[q[i].id] = {res / k, a * (a - 1) / 2 / k};
        }
    }
    for (int i = 1; i <= m; i ++ )
        if (!ans[i].first) puts("0/1");
        else printf("%lld/%lld\n", ans[i].first, ans[i].second);
    
    return 0;
}

T2(斜率最佳化dp模板,費用提前計算思想)

814。
https://www.acwing.com/problem/content/303/
首先做出dp.dp完了最佳化轉移

由於我們知道分了幾批就是為了算啟動代價,不如直接累加到值裡面,把後面的提前算了!
費用提前計算:把以後啟動的代價都算進來。

這樣狀態就變成了:f[i],前i個物品選若干次,已經考慮當前批代價和對以後啟動時間的代價的最小值。

還可以這樣理解:

。這樣,以c為橫座標,f為縱座標,我們發現這是個斜率相同的直線,現在所決策應該是一個點,這個點所形成的的直線的截距最小。這是啥?直接凸包維護就好了,取的值就是遍維護凸包邊算就行,最後加進來(加進來一定橫座標單調)。

擴充套件:t不單調(也就是t可以為負):那就沒有辦法利用詢問斜率的單調性了,只能維護整個凸包。(原先可以邊問邊刪),二分就好。c不單調:那就套一層平衡樹維護凸包。

QAQ
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 3e5 + 10;

int n, s;
LL c[N], t[N];
LL f[N];
int q[N];

int main()
{
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%lld%lld", &t[i], &c[i]);
        t[i] += t[i - 1];
        c[i] += c[i - 1];
    }
    
    int hh = 0, tt = 0;
    q[0] = 0;
    
    for (int i = 1; i <= n; i ++ )
    {
        while (hh < tt && (f[q[hh + 1]] - f[q[hh]]) <= (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) hh ++ ;
        int j = q[hh];
        f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
        while (hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt -- ;
        q[ ++ tt] = i;
    }
    
    printf("%lld\n", f[n]);
    
    return 0;
}

T3(反悔式貪心及部分貪心的證明)

我的比賽思路:
U題,首先考慮dp,肯定開的狀態是:前i個,時間,收益。時間當然開不下,考慮最佳化。發現對於每一秒,都必須打一隻。這樣大於i的時間就沒有用了,一定不優。這樣按照當前選不選就能做。不過複雜度是n^2。只能貪心。考慮留一維。必須有時間,於是留下時間t,就是每個物品看看咋選,發現d>t的沒必要考慮,只需要留到後面打掉就行了。如果後面打掉不優,還可以調整。這樣我排個序,開了個堆維護當前最優的。然後也是寫掛了。
V題,消除一定是區間,考慮區間dp。轉移就是要麼中間消掉,再打兩邊,要麼列舉斷點。沒錯又寫掛了。
W題,安排m個樹上不相交路徑,求最小值最大。當然二分,然後後面咋做沒想好。感覺好像可以把所有路徑按照根節點分類?寫了45分的鏈和菊花分。
X題,我的想法,可能一定是刪掉原圖中某些冗餘邊?然而對於如何判斷這個冗餘和上述結論,都沒想太好。
在做前兩題的時候,u題寫了個對拍,是a的。不知道怎麼掛了。v題自己也是手捏了很多資料,然而也是wa。


我的思路,維護前i個老鼠的最優解。
實際上要求的其實就是最優解唯一。


兩個極值點,一定能分出高下。
我們的貪心就是不斷探極值的情況。

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
struct Node { int d, v;
bool operator< (const Node& W) const
{
    if (d != W.d) return d < W.d;
    return v > W.v;
}
} a[N];
priority_queue<int, vector<int>, greater<int> > q;
LL res;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d%d", &a[i].d, &a[i].v);
    
    sort(a + 1, a + n + 1);
    int tim = 1;
    q.push(a[1].v);
    res += a[1].v; //特判,防止裡面沒東西,一定加進去
    for (int i = 2; i <= n; i ++ )
    {
        if (tim < a[i].d) tim ++ , res += a[i].v, q.push(a[i].v);
        else
        {
            int t = q.top(); //取出來的過期時間一定小於i,既然他能放進去,那i也能放進去。
            if (t < a[i].v)
            {
                q.pop();
                res = res - t + a[i].v;
                q.push(a[i].v);
            }
        }
    }
    
    cout << res << endl;
    return 0;
}

T4

區間dp。f[i][j]表示i~j最大。
決策:最後一段:f[i][j - 1]
or f[i][p]???與中間一段接上再一塊消。
這裡狀態沒辦法描述了。我們考慮加一維。既然要加上一塊消,我們就直接f[i][j][k],k是向右延拓k個格子和最後一段一塊消除。
o。

QAQ
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int n, cnt;
int f[N][N][N];
int a[N], c[N], w[N];

int dp(int x, int y, int k)
{
    if (x > y) return -0x3f3f3f3f;
    if (f[x][y][k]) return f[x][y][k];
    
    f[x][y][k] = max(f[x][y][k], dp(x, y - 1, 0) + f[y][y][k]);
    for (int i = x; i < y - 1; i ++ )
        if (c[i] == c[y]) f[x][y][k] = max(f[x][y][k], dp(x, i, w[y] + k) + dp(i + 1, y - 1, 0));
    return f[x][y][k];
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
    for (int i = 1, j = i; i <= n; i = j)
    {
        j = i;
        while (j <= n && a[j] == a[i]) j ++ ;
        int len = j - i;
        c[ ++ cnt] = a[i];
        w[cnt] = len;
    }
    for (int i = 1; i <= cnt; i ++ )
        for (int k = 0; i + k <= n; k ++ ) //有可能處理n個,這裡注意
            f[i][i][k] = (w[i] + k) * (w[i] + k);
    
    cout << dp(1, cnt, 0) << endl;
    
    return 0;
}

T5(樹形dp,二分,樹上不相交路徑dp套路)

安排m個樹上不相交路徑,求最小值最大。有最優子結構:全域性最優,子樹就最優。
考慮二分判斷+dp.


最佳化的關鍵在於只有一條邊傳上去。這使得我們可以一個個子樹考慮。
子樹最優嘛,要是不最優,那最多多向上的一條,所以子樹多選一定不劣。那現在問題就是當前點最多貢獻多少個答案?
答案就是處理傳上來的這些邊,排個序,貪心匹配。(為啥?最優子結構啊,當前最優就一定不劣),剩下的最大值再傳上去。這樣就最優了。o。


偷個懶,這裡用multiset。
思想的本質
其實本質還是dp套路:f[u] = f[son] + ().(son,u之間的貢獻)
只不過這裡的貢獻不僅僅是貢獻,還要上傳最大的邊來維護遞推。其實只要知道子樹傳上來最大的邊,son和u的問題就變成貪心匹配的問題了。
所以最佳化的關鍵在於只有一條邊傳上去。這使得我們可以一個個子樹考慮。
子樹內的路徑已經考慮完了,經過根的還沒考慮呢,但是經過根的路徑每個子節點最多傳上來一條~,當然傳最大的,我們維護一下就行了。

T6(傳遞閉包,最優決策分析,貪心,無向圖拓撲考慮)

最優解唯一,就可以不斷探向最優解了,不斷維護區域性最優解。考慮遞推。

無環圖按拓撲排序考慮算是常用思路吧。
做法已經寫的很明白了。看思路,首先拓撲圖,這樣我們就能想到,如果只維護後面的最值就可以了,這樣就可以遞推了。發現最優解唯一
ok,我們考慮保留u的鄰邊,我們可以想到,編號小的理應不選就沒得選了,因此我們考慮不斷取小的。
按v從小到大,如果當前過不去,後面的只能更往後,不可能走回到v來
ok,那這樣遞推就解決了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int res, n, m;
bool g[N][N];
bool s[N][N];
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) g[i][i] = true;
    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        s[a][b] = true;
    }
    
    int res = 0;
    for (int i = n; i; i -- )
        for (int j = i + 1; j <= n; j ++ )
            if (s[i][j] && !g[i][j]) //能加
            {
                res ++ ;
                for (int k = j; k <= n; k ++ )
                    g[i][k] |= g[j][k];
            }
    
    cout << res << endl;
    return 0;
}

T7(樹上最小支配集模板)