NOIP2023模拟7,8联测28,29

NOIP2023模拟7联测28

T1

看到了有向无环图,很容易让人想到拓扑。

\(f_i\) 表示经过节点 \(i\) 路径,这条路径上的关键点个数的最大值。

如果有一个点满足 \(f_i=k\), 那么答案就是 \(Yes\),否则就是 \(No\),这个显然。

转移就是从所有能到达 \(i\) 的节点转移,取 \(\max\)。这个可以通过拓扑实现。

复杂度为 \(\Theta(\sum m)\)

Code
#include <iostream>
#include <cstdio>
#include <queue>

const int N=1e6+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(bool x) {
    if(x) cout<<"Yes";
    else cout<<"No";
    putchar('\n');
}

int T;
int n, m, k, cnt_edge;
struct edge {
    int next, to;
}e[N<<1];
int head[N], in[N];
int vis[N], val[N];

void add_edge(int u,int v) {
    e[++cnt_edge].to=v;
    e[cnt_edge].next=head[u];
    head[u]=cnt_edge;
    in[v]++;
}

inline void init() {
    for(int i=1;i<=n;i++) {
        head[i]=in[i]=0;
        vis[i]=val[i]=0;
    }
    for(int i=1;i<=cnt_edge;i++) {
        e[i].to=e[i].next=0;
    }
    n=cnt_edge=0;
}

queue <int> q;

void topu() {

    for(int i=1;i<=n;i++) {
        if(!in[i]) q.push(i);
    }

    while(!q.empty()) {
        int now=q.front(); q.pop();
        vis[now]+=val[now];
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            vis[v]=max(vis[v], vis[now]);
            in[v]--;
            if(!in[v]) q.push(v);
        }
    }

    bool flag=0;
    for(int i=1;i<=n;i++) {
        if(vis[i]==k) {
            flag=1;
            break;
        } 
    }
    write(flag);
}

int main() {

    T=read();
    while(T--) {
        init();

        n=read(), m=read();
        for(int i=1;i<=m;i++) {
            int u=read(), v=read();
            add_edge(u, v);
        }
        k=read();
        for(int i=1;i<=k;i++) {
            int x=read();
            val[x]=1;
        }
        topu();
    }
 
    return 0;
}

T2

Part1

区间修改不考虑,考虑差分,原序列为 \(a_i\), 设 \(b_i=a_i \oplus a_{i-1}\), 那么易证当且仅当所有的 \(b_i\) 都为零时,所有的 \(a_i\) 也等于零。对 \(a_i\) 的区间修改,在差分数组上就是单点修改。一次修改一个值或者两个值。

Part2

我们把 \(n\) 个数看成 \(n\) 个点,两个数的修改看成两个点之间的连边,一个点的修改看成自环。那么最终的答案就是总边数,我们想让边数最小。

我们选 \(x\) 个点组成一个连通块,考虑最优策略下要连多少条边,发现上界为 \(x\)(即 \(x\) 个自环),下界为 \(x-1\),组成了一棵树,考虑什么情况下可以达到下界。有结论:

  • 当且仅当连通块内的点一开始的异或和为 \(0\) 时,才可以达到下界。

必要性:因为进行了 \(x-1\) 次操作,那么每次操作必然是修改两个,总的异或和不变,要想通过修改使其都变成 \(0\), 那么这几个数一开始的异或和必然为 \(0\)

充分性:如果异或和为 \(0\),那么可以把这几个点穿成一条链,每次把链头通过异或自己的方式把自己变为 \(0\),这样最后一个数就变成了这几个数的异或和,为 \(0\)

那么我们就是想让这样的连通块最多,设其为 \(x\),答案就是 \(n-x\)

Part3

现在问题变成了:给定一个序列,把其划分成若干个子序列,最大化异或和为 \(0\) 的个数。可以状压解决,每次转移枚举子集。用到了一个枚举子集的技巧。

复杂度证明

考虑每个状态被枚举了几次。

设当前状态集合为 \(T\),那么被枚举的次数就是 \(2^{n-|T|}\)

由此可得总的次数为:

\[\sum_{i=0}^{n}\dbinom{n}{i}2^{n-i} \]

二项式反演即为:\(3^n\)

复杂度为 \(\Theta(3^n)\)

Code
#include <iostream>
#include <cstdio>

#define int long long

const int N=18;
const int M=(1<<N)+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int n, ans;
int a[N], b[N], dp[M];
bool f[M];

signed main() {

    n=read();
    for(int i=1;i<=n;i++) {
        a[i]=read(); b[i]=a[i]^a[i-1];
    }
    
    for(int s=0;s<(1<<n);s++) {
        int sum=0;
        for(int i=1;i<=n;i++) {
            if(s & (1<<(i-1))) sum^=b[i];
        }
        if(!sum) f[s]=1;
    }

    for(int s=1;s<(1<<n);s++) {
        for(int t=s;t;t=(t-1)&s) {
            if(f[t]) {
                dp[s]=max(dp[s], dp[s^t]+1);
            }
        }
    }
    
    write(n-dp[(1<<n)-1]);
 
    return 0;
}

T3

Part1

先考虑特殊性质 \(A\),发现 \(b\) 是给定的,说明答案的后半部分的贡献是固定不变的,那么只要保证 \(dis(a,x)\) 最小即可,直接点分树即可,在每个重心维护其点分树子树内具其最近的点的距离,暴力跳父亲修改并查询即可,复杂度为 \(\Theta(n \log n)\)

Part2

原题又多了一部分贡献,我们可以考虑固定一部分贡献,另一部分取 \(\min\),考虑点分治。把询问和修改离线下来。点分树作为辅助。

设当前点分治的分治重心为 \(u\),我们把答案分为 \(4\) 部分,\(dis(x,u)\)\(dis(a,u)\),还有两部分为 \(dis(y,b)\),在点分树上把它拆成了 \(2\) 部分。其中 \(dis(x,u)\)\(dis(a,u)\) 是固定的。

对于修改操作 \((a,b)\),让 \(b\) 在点分树上暴力跳父亲,设当前跳到了 \(P\),那么就用 \(dis(u,a)+dis(P,b)\) 更新 \(P\) 节点的权值 \(val_P\),这相当于在给 其中一部分取 \(\min\)

对于查询操作 \((x,y)\),让 \(y\) 在点分树上暴力跳父亲,设当前跳到了 \(P\),那么就用 \(dis(y,P)+val_P\) 更新答案,最后加上已经固定 \(dis(x,u)\) 就行了。

因为是取 \(\min\) 操作,而且还没有负边权,所以不用容斥,直接算就可以了。

复杂度为 \(\Theta(n\log^2n)\)

Code
#include <iostream>
#include <cstdio>
#include <vector>

#define int long long

const int N=2e5+10;
const int inf=1e18;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int n, m, cnt_edge;
struct edge {
    int next, to, val;
}e[N<<1];
int head[N];

struct node {
    int opt, x, y;
}a[N];
vector <int> q[N];
int ans[N];

inline void add_edge(int u,int v,int w) {
    e[++cnt_edge].to=v;
    e[cnt_edge].val=w;
    e[cnt_edge].next=head[u];
    head[u]=cnt_edge;
}

int dep[N];
struct Heavy {
    int size[N], son[N], fa[N], top[N];
    void dfs1(int now,int father) {
        fa[now]=father, size[now]=1;
        int maxson=-1;
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==father) continue;
            dep[v]=dep[now]+e[i].val;
            dfs1(v, now);
            size[now]+=size[v];
            if(maxson < size[v]) {
                maxson=size[v], son[now]=v;
            }
        }
    }

    void dfs2(int now,int topf) {
        top[now]=topf;
        if(son[now]) dfs2(son[now], topf);
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==fa[now] || v==son[now]) continue;
            dfs2(v, v);
        }
    }

    void prework() {
        dfs1(1, 0);
        dfs2(1, 1);
    }

    inline int LCA(int x,int y) {
        while(top[x]!=top[y]) {
            if(dep[top[x]] < dep[top[y]]) swap(x, y);
            x=fa[top[x]];
        }
        if(dep[x]>dep[y]) swap(x, y);
        return x;
    }

    inline int Dis(int x,int y) {
        int lca=LCA(x, y);
        int res=dep[x]+dep[y]-2*dep[lca];
        return res;
    }
}H;

struct Part {
    int root, sum_root;
    int maxp[N], size[N], fa[N], mi[N], bin[N];
    bool vis[N];

    void get_root(int now,int fa) {
        maxp[now]=0, size[now]=1;
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==fa || vis[v]) continue;
            get_root(v, now);
            size[now]+=size[v];
            maxp[now]=max(maxp[now], size[v]);
        }
        maxp[now]=max(maxp[now], sum_root-maxp[now]);
        if(maxp[now] < maxp[root]) root=now;
    }

    void build(int now) {
        vis[now]=1;
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;
            sum_root=size[v], root=0;
            get_root(v, now);
            fa[root]=now;
            build(root);
        }
    }
    
    void build() {
        maxp[0]=n+1, sum_root=n;
        root=0, get_root(1, 0);
        build(root);
    }

    inline void up_date(int x,int y,int rt) {
        int len=H.Dis(x, rt);
        for(int i=y;i;i=fa[i]) {
            int dis=H.Dis(i, y)+len;
            mi[i]=min(mi[i], dis);
        }
    }

    inline int query(int x,int y,int rt) {
        int len=H.Dis(x, rt), res=inf;
        for(int i=y;i;i=fa[i]) {
            int val=mi[i]+H.Dis(i, y);
            res=min(res, val+len);
        }
        return res;
    }

    void clear(int now) {
        for(int i=now;i;i=fa[i]) mi[i]=inf;
    }

    void calc(int now) {
        int tot=0;
        for(auto &p : q[now]) {
            if(a[p].opt==1) {
                bin[++tot]=a[p].y;
                up_date(a[p].x, a[p].y, now);
            }
            if(a[p].opt==2) {
                int res=query(a[p].x, a[p].y, now);
                ans[p]=min(ans[p], res);
            }
        }
        for(int i=1;i<=tot;i++) clear(bin[i]);
    }

    void solve(int now) {
        vis[now]=1;
        calc(now);
        for(int i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(vis[v]) continue;
            root=0, sum_root=size[v];
            get_root(v, now);
            solve(root);
        }
    }

    void work() {
        for(int i=1;i<=n;i++) vis[i]=0;
        sum_root=n, maxp[0]=n+1;
        root=0, get_root(1, 0);
        solve(root);
    }
}P;

void prework() {
    H.prework();
    P.build();
    int lim=max(m, n);
    for(int i=1;i<=lim;i++) {
        ans[i]=P.mi[i]=inf;
    }
}

void insert(int id) {
    int tmp=a[id].x;
    while(tmp) {
        q[tmp].push_back(id);
        tmp=P.fa[tmp];
    }
}

signed main() {

    n=read(), m=read();
    for(int i=1;i<n;i++) {
        int u=read(), v=read(), w=read();
        add_edge(u, v, w);
        add_edge(v, u, w);
    }

    prework();

    for(int i=1;i<=m;i++) {
        a[i]=(node){read(), read(), read()};
        insert(i);
    }

    P.work();

    for(int i=1;i<=m;i++) {
        if(a[i].opt==1) continue;
        if(ans[i]>=inf) write(-1);
        else write(ans[i]);
    }

    return 0;
}

T4

不会,咕了。

NOIP2023模拟8联测29

T1

发现加入一个数一定不优,删去一个数一定不劣,那么考虑维护一个双指针,在移动左指针时移动右指针,发现右指针一定单调向右移。

维护一个线段树,维护值域上的最长连续段,指针边移边修改即可。

复杂度为 \(\Theta(n\log n)\)

Code
#include <iostream>
#include <cstdio>

#define int long long

const int N=2e5+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int n, k;
int a[N];

struct Tree {
    int sum, lc, rc, num;
}t[N<<2];

struct Seg_Tree {

    inline void push_up(int k,int l,int r) {
        int mid=(l+r)>>1;
        int len=t[k<<1].rc+t[k<<1|1].lc;
        t[k].sum=max(len, max(t[k<<1].sum, t[k<<1|1].sum));
        if(t[k<<1].lc==(mid-l+1)) t[k].lc=t[k<<1].lc+t[k<<1|1].lc;
        else t[k].lc=t[k<<1].lc;
        if(t[k<<1|1].rc==(r-mid)) t[k].rc=t[k<<1|1].rc+t[k<<1].rc;
        else t[k].rc=t[k<<1|1].rc;
    }

    void change(int k,int l,int r,int pos,int val) {
        if(l==r) {
            t[k].num+=val;
            if(t[k].num) t[k].sum=t[k].lc=t[k].rc=1;
            else t[k].sum=t[k].lc=t[k].rc=0;
            return;
        } 
        int mid=(l+r)>>1;
        if(pos<=mid) change(k<<1, l, mid, pos, val);
        else change(k<<1|1, mid+1, r, pos, val);
        push_up(k, l, r);
    }
}T;

signed main() {

    n=read(), k=read();
    for(int i=1;i<=n;i++) a[i]=read();

    if(k==0) {
        write(0); return 0;
    }

    int r=0, ans=0;
    for(int i=1;i<=n;i++) {
        while(t[1].sum<=k && r<=n) {
            r++; 
            if(r>n) break;
            T.change(1, 1, n ,a[r], 1);
        }
        ans+=(r-i);
        T.change(1, 1, n, a[i], -1);
    }
    write(ans);

    return 0;
}

T2

没意思,咕了。

T3

不会,咕了。

T4

没看懂题,咕了。

posted @ 2023-10-31 16:44  Trmp  阅读(23)  评论(2编辑  收藏  举报
-->