杂题小记(2023.02.22)

杂题小记(2023.02.22)

更好的阅读体验戳此进入

好吧这一天刷了好几道大水题,大概就是写某道题的时候延申延申又延申出来一大堆奇怪的没怎么写过的小知识点,就丢到杂题里了。

HDU-3038 How Many Answers Are Wrong

题面

存在 n 个数,给定 m 个区间限制:给定 l,r,s 表示 i=lr=s,你需要判断有多少限制与其之前的限制矛盾。

Solution

由题意不难想到带权并查集实现,首先将闭区间转换为左闭右开区间,然后维护带权并查集的 dis,每次判断是否合法即可。

然后 HDU 默认多测题面里不写,我还以为代码写错了找了半天。。。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N, M;
int ans(0);

class UnionFind{
private:
    int fa[210000];
    ll dis[210000];
public:
    UnionFind(void){for(int i = 1; i <= 201000; ++i)fa[i] = i, dis[i] = 0;}
    void Clear(void){for(int i = 1; i <= 201000; ++i)fa[i] = i, dis[i] = 0;}
    int Find(int x){
        if(x == fa[x])return x;
        int cfa = fa[x];
        fa[x] = Find(fa[x]);
        dis[x] += dis[cfa];
        return fa[x];
    }
    void Union(int s, int t, ll d){
        int fs = Find(s), ft = Find(t);
        if(fs == ft)return;
        fa[fs] = ft;
        dis[fs] = d + dis[t] - dis[s];
    }
    bool CheckDis(int s, int t, ll d){
        return dis[s] != d + dis[t];
    }
}uf;

int main(){
    while(true){
        ans = 0, uf.Clear();
        N = read(), M = read();
        while(M--){
            int s = read(), t = read() + 1, d = read();
            if(uf.Find(s) != uf.Find(t))uf.Union(s, t, d);
            else ans += uf.CheckDis(s, t, d);
        }printf("%d\n", ans);
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    int c = getchar();
    if(!~c)exit(0);
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P1525 [NOIP2010 提高组] 关押罪犯

题面

存在 n 个人和 m 条限制,每条限制为 s,t,w 表示若 st 在同一监狱内将会贡献 w,你需要将 n 个人分到两个监狱中,以最小化最大贡献,输出最小值,若不存在贡献则输出 0

Solution

扩展域并查集。与其说扩展域并查集,也可以认为其就是利用了 2-SAT 的思想,每个点拆成两个,然后贪心地优先取更大的限制,尽量去安排即可。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N, M;
struct Node{int s, t, w;};
basic_string < Node > rels;

class UnionFind{
private:
    int fa[410000];
public:
    UnionFind(void){for(int i = 1; i <= 401000; ++i)fa[i] = i;}
    int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
    void Union(int s, int t){
        int fs = Find(s), ft = Find(t);
        if(fs == ft)return;
        fa[fs] = ft;
    }
}uf;

int main(){
    N = read(), M = read();
    for(int i = 1; i <= M; ++i){
        int s = read(), t = read(), w = read();
        rels += Node{s, t, w};
    }sort(rels.begin(), rels.end(), [](const Node &a, const Node &b)->bool{return a.w > b.w;});
    for(auto rel : rels){
        if(uf.Find(rel.s << 1) != uf.Find(rel.t << 1))uf.Union(rel.s << 1, (rel.t << 1) | 1), uf.Union((rel.s << 1) | 1, rel.t << 1);
        else printf("%d\n", rel.w), exit(0);
    }printf("0\n");
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P2024 [NOI2001] 食物链

题面

存在 ABC 的食物链关系,存在 n 个动物,m 条限制,每条限制为 s,t 为同类或 st,求有多少条与之前矛盾(包括本身不合法)。

Solution

考虑扩展域并查集,将 Pi 分解成 Pi,A,Pi,B,Pi,C 然后从意义上处理即可,同样类似于 2-SAT 的思想。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
// #define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define SON(i, x) (i + N * x)

template < typename T = int >
inline T read(void);

int N, M;
int ans(0);

class UnionFind{
private:
    int fa[210000];
public:
    UnionFind(void){for(int i = 1; i <= 201000; ++i)fa[i] = i;}
    int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);}
    void Union(int s, int t){
        int fs = Find(s), ft = Find(t);
        if(fs == ft)return;
        fa[fs] = ft;
    }
}uf;

int main(){
    N = read(), M = read();
    while(M--){
        int opt = read(), s = read(), t = read();
        if(s > N || t > N){++ans; continue;}
        if(opt == 1){
            if(
                uf.Find(SON(s, 0)) == uf.Find(SON(t, 1)) ||
                uf.Find(SON(s, 0)) == uf.Find(SON(t, 2)) ||
                uf.Find(SON(s, 1)) == uf.Find(SON(t, 0)) ||
                uf.Find(SON(s, 1)) == uf.Find(SON(t, 2)) ||
                uf.Find(SON(s, 2)) == uf.Find(SON(t, 0)) ||
                uf.Find(SON(s, 2)) == uf.Find(SON(t, 1))
            )++ans;
            else uf.Union(SON(s, 0), SON(t, 0)), uf.Union(SON(s, 1), SON(t, 1)), uf.Union(SON(s, 2), SON(t, 2));
        }else{
            if(s == t){++ans; continue;}
            if(
                uf.Find(SON(s, 0)) == uf.Find(SON(t, 0)) ||
                uf.Find(SON(s, 0)) == uf.Find(SON(t, 2)) ||
                uf.Find(SON(s, 1)) == uf.Find(SON(t, 1)) ||
                uf.Find(SON(s, 1)) == uf.Find(SON(t, 0)) ||
                uf.Find(SON(s, 2)) == uf.Find(SON(t, 2)) ||
                uf.Find(SON(s, 2)) == uf.Find(SON(t, 1))
            )++ans;
            else uf.Union(SON(s, 0), SON(t, 1)), uf.Union(SON(s, 1), SON(t, 2)), uf.Union(SON(s, 2), SON(t, 0));
        }
    }printf("%d\n", ans);

    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P5787 二分图 /【模板】线段树分治

题面

在共 k 时间中,有 m 条边每条边都有其对应时效,求对于每一段时间中该 n 个点的图是否为二分图。

Solution

首先二分图可以通过扩展与并查集判断,同时并查集需要支持可撤销并进行按秩合并,对于询问进行线段树分治即可。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N, M, K;

class UnionFind{
private:
    int fa[410000], siz[410000];
    struct Mdf{int s; int fs; int t; int sizt;};
    stack < Mdf > mdfs;
public:
    UnionFind(void){for(int i = 0; i <= 401000; ++i)fa[i] = i, siz[i] = 1;}
    auto GetTop(void){return mdfs.empty() ? Mdf() : mdfs.top();}
    int Find(int x){return x == fa[x] ? x : Find(fa[x]);}
    void Union(int s, int t){
        int fs = Find(s), ft = Find(t);
        if(siz[fs] > siz[ft])swap(fs, ft);
        if(fs == ft)return;
        mdfs.push(Mdf{fs, fa[fs], ft, siz[ft]});
        siz[ft] += siz[fs];
        fa[fs] = ft;
    }
    void Repeal(Mdf endp){
        while(!mdfs.empty() && (mdfs.top().s != endp.s || mdfs.top().t != endp.t))
            fa[mdfs.top().s] =mdfs.top().fs, siz[mdfs.top().t] = mdfs.top().sizt, mdfs.pop();
    }
}uf;

class SegTree{
private:
    struct Line{int s, t;};
    basic_string < Line > tr[110000 << 2];
    #define LS (p << 1)
    #define RS (LS | 1)
    #define MID ((gl + gr) >> 1)
public:
    void Insert(int l, int r, int s, int t, int p = 1, int gl = 0, int gr = K - 1){
        if(l <= gl && gr <= r)return tr[p] += Line{s, t}, void();
        if(l <= MID)Insert(l, r, s, t, LS, gl, MID);
        if(r >= MID + 1)Insert(l, r, s, t, RS, MID + 1, gr);
    }
    void dfs(int p = 1, int gl = 0, int gr = K - 1){
        bool legal(true);
        auto lst = uf.GetTop();
        for(auto line : tr[p]){
            int fs = uf.Find(line.s << 1), ft = uf.Find(line.t << 1);
            if(fs == ft){
                legal = false;
                for(int i = gl; i <= gr; ++i)printf("No\n");
                break;
            }uf.Union(line.s << 1, (line.t << 1) | 1), uf.Union((line.s << 1) | 1, line.t << 1);
        }
        if(legal){
            if(gl != gr)dfs(LS, gl, MID), dfs(RS, MID + 1, gr);
            else printf("Yes\n");
        }uf.Repeal(lst);
    }
}st;

int main(){
    N = read(), M = read(), K = read();
    while(M--){
        int s = read(), t = read(), l = read(), r = read() - 1;
        st.Insert(l, r, s, t);
    }st.dfs();
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P2627 [USACO11OPEN]Mowing the Lawn G

题面

给定序列 An,你要在其中选择数个数删除,使得最大的连续的保留的数不超过 k 个。

Solution

考虑 DP,令 dp(i) 表示考虑前 i 个数的最大值,有转移:

dp(i)=max{dp(j1)+k=j+1iEk,j[ik,i]}

预处理前缀和后有:

dp(i)=max{dp(j1)+sumisumj}

sumi 提出:

dp(i)=sumi+max{dp(j1)sumj}

于是维护长度为 k 的区间最大 dp(i1)sumi 即可。

Tips:一个我调了半天的问题就是需要注意初始时应该考虑到 j=0:dp(0)sum0 的情况。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N, K;
ll A[110000];
ll sum[110000];
ll dp[110000];
deque < pair < int, ll > > cur;

int main(){
    N = read(), K = read();
    for(int i = 1; i <= N; ++i)sum[i] = sum[i - 1] + (A[i] = read());
    cur.push_back({0, 0});
    for(int i = 1; i <= N; ++i){
        while(!cur.empty() && cur.front().first < i - K)cur.pop_front();
        while(!cur.empty() && cur.back().second < dp[i - 1] - sum[i])cur.pop_back();
        cur.push_back({i, dp[i - 1] - sum[i]});
        dp[i] = sum[i] + (cur.empty() ? 0 : cur.front().second);
    }printf("%lld\n", dp[N]);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

LG-P4954 [USACO09OPEN]Tower of Hay G

题面

存在 n 个砖块,每个有宽度 wi,按序给定每个砖块,你需要按序将其搭高,必须满足上层的宽度不小于下层,且所有砖块都必须被使用。

Solution

由于均使用的限制存在,不难想到所有方案中最优方案一定满足最底层最小。同时我们一定也需要满足尽量使高层更小,并且不难发现一般的 DP 的维度难以降低主要都是在讨论最后一层的高度等,而我们此时可以考虑将砖块逆序翻转,然后做前缀和,令其为 si,然后令 f(i) 表示前 i 个最高能搭建多少层,g(i) 表示当用到第 i 块时最优方案中 wi 所在层的宽度,转移显然为当我们找到最大的一个 j 满足 sisjg(j),则 f(i)=f(j)+1,g(i)=sisj。移项得到 sisj+g(j),而我们需要的是 j 最大的,如果存在 k<jsk+g(k)sj+g(j) 那么 k 一定不优,可以直接忽略,以此维护递增的单调队列并转移即可。

Code

#define _USE_MATH_DEFINES
#include <bits/stdc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}

using namespace std;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

template < typename T = int >
inline T read(void);

int N;
int w[110000];
ll s[110000];
ll F[110000], G[110000];
deque < pair < int, ll > > cur;

int main(){
    N = read();
    for(int i = 1; i <= N; ++i)w[i] = read();
    reverse(w + 1, w + N + 1);
    for(int i = 1; i <= N; ++i)s[i] = s[i - 1] + w[i];
    cur.push_back({0, 0});
    for(int i = 1; i <= N; ++i){
        pair < int, ll > lst = {-1, -1ll};
        while(!cur.empty() && cur.front().second <= s[i])lst = cur.front(), cur.pop_front();
        if(~lst.first)cur.push_front(lst);
        F[i] = F[cur.empty() ? 0 : cur.front().first] + 1;
        G[i] = s[i] - s[cur.empty() ? 0 : cur.front().first];
        while(!cur.empty() && cur.back().second > s[i] + G[i])cur.pop_back();
        cur.push_back({i, s[i] + G[i]});
    }printf("%lld\n", F[N]);
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    int flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

UPD

update-2023_02_22 初稿

本文作者:tsawke

本文链接:https://www.cnblogs.com/tsawke/p/17180241.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Tsawke  阅读(44)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起