• ZROI1153 【线上训练3】数个数


    ZROI1153 【线上训练3】数个数

    传送门

    一道非常有意思的题,涵盖了各种知识点。

    首先,很显然,这是个容斥。容斥可以过掉(30pts)

    这里我们考虑容斥+DP。

    我们令(dp[i][j])代表对于前(i)个区间(区间排过序),上一个取的区间为(j)的方案数。

    那么每次转移就是:

    [dp[i+1][i+1]+=-dp[i][k]*(s^{len})space (k<i+1) ]

    其中(len)是这两个区间的中间部分的长度。而负号代表容斥系数(长度+1,奇偶性改变,所以变成负的)。

    这个(dp)相当于考虑了第(i+1)个区间接在以不同的区间结尾的方案后面,所以可以做到不重不漏。因为在一种方案后接上一个区间对答案多出的贡献取决于这个区间和上个区间之间的距离,所以只用像这样记录上个区间即可。

    下面这份代码来自An_Account,能展示出(n^2)(dp)过程:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 200010, mod = 998244353;
    struct Query {
    	int l, r, b;
    	bool operator < (const Query &b) const {
    		return r < b.r || (r == b.r && l < b.l);
    	} 
    } l[N];
    int dp[N];
    typedef long long LL;
    inline int Pow(int x, int y) {
    	int res = 1;
    	for (; y; y >>= 1, x = (LL)x * x % mod) if (y & 1) res = (LL)res * x % mod;
    	return res;
    }
    int main() {
    	int n, m, s; scanf("%d%d%d", &n, &m, &s);
    	for (int i = 1; i <= m; i++) scanf("%d%d%d", &l[i].l, &l[i].r, &l[i].b);
    	sort(l + 1, l + m + 1);
    	dp[0] = 1;
    	for (int i = 1; i <= m; i++)
    		for (int j = 0; j < i; j++) {
    			if (l[j].r < l[i].l || l[j].b == l[i].b) {
    				int t = (LL)dp[j] * Pow(s, max(0, l[i].l - l[j].r - 1)) % mod;
    				dp[i] = (dp[i] - t + mod) % mod;
    			}
    		}
    	int res = Pow(s, n);
    	for (int i = 1; i <= m; i++) 
    		res = (res + (LL)dp[i] * Pow(s, n - l[i].r)) % mod;
    	printf("%d
    ", res);
    }
    

    而正解就是在他的基础上用线段树优化转移。

    首先我们发现满足转移要求有两种情况:

    1. 颜色不相同,区间不相交
    2. 颜色相同

    我们考虑分开考虑两种情况,

    对于第一种情况,我们可以开一个前缀和。

    [令i,j为两个区间的编号,i在j之后。\ c[i]=sum _{j.r<i.l} frac{dp[j]}{s^{j.r+1}} ]

    这是因为上一个方程中的(s^{len})可以这样转化:

    (s^{len}=s^{r-l+1}=frac{s^{r+1}}{s^l})

    因为对于一次转移,(s^{r+1})都是一样的(都是当前区间的起始点),所以我们只要在(dp[i]=-c[i]*s^{i.l})

    然后把这个值插入在区间的右端点+1即可(查询的时候查询左端点)。

    对于第二种情况,我们可以使用动态开点线段树。

    因为在第一种情况里只少统计了颜色相等,区间重叠的情况。所以我们用线段树统计这种情况。

    两个区间相交当且仅当第一个区间(前面的区间)的终点被第二个区间包含。那我们统计一下第二个区间内有多少个其他区间的右端点即可。因为两个区间重叠,所以在这一步只有一种方案,所以直接加上上个区间的(dp)值即可。(别忘了要取相反数)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #define ll long long
    using namespace std;
    const int mod=998244353;
    const int maxn=2e5+1000;
    int n,m,s,ls[maxn*80],rs[maxn*80],idx,rt[maxn];
    struct gg {
        int l,r,b;
    }q[maxn];
    ll c[maxn],sum[maxn*80],dp[maxn];
    void update_t(int loc,int val) {
        for(int i=loc;i<=n;i+=i&-i)c[i]+=val,c[i]%=mod;
    }
    void push_up(int root) {
        sum[root]=0;
        if(ls[root])sum[root]+=sum[ls[root]];
        if(rs[root])sum[root]+=sum[rs[root]];
        sum[root]%=mod;
    }
    void update_s(int l,int r,int root,int val,int tar) {
        if(l==r){sum[root]=val;sum[root]%=mod;return;}
        int mid=(l+r)>>1;
        if(tar<=mid) {
            if(!ls[root])ls[root]=++idx;
            update_s(l,mid,ls[root],val,tar);
        }
        if(tar>=mid+1) {
            if(!rs[root])rs[root]=++idx;
            update_s(mid+1,r,rs[root],val,tar);
        }
        push_up(root);
    }
    ll query_t(int loc) {
        ll tot=0;for(int i=loc;i;i-=i&-i)tot+=c[i],tot%=mod;
        return tot;
    }
    ll query_s(int l,int r,int root,int tl,int tr) {
        if(tl<=l&&r<=tr)return sum[root];
        int mid=(l+r)>>1;ll tot=0;
        if(l<=mid&&ls[root])tot+=query_s(l,mid,ls[root],tl,tr);
        if(r>=mid+1&&rs[root])tot+=query_s(mid+1,r,rs[root],tl,tr);
        return tot%mod;
    }
    int ksm(int num,int t){
        int res=1;
        for(;t;t>>=1,num=(ll)num*num%mod) {
            if(t&1)res=(ll)res*num%mod;
        }
        return res;
    }
    vector<gg>col[maxn];
    bool cop1(gg x,gg y){if(x.l==y.l)return x.r<y.r;return x.l>y.l;}
    bool cop2(gg x,gg y){return x.l<y.l;}
    int main() {
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<=m;i++) {
            scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].b);
            col[q[i].b].push_back(q[i]);
        }
        m=0;
        for(int i=1;i<=s;i++) {
            sort(col[i].begin(),col[i].end(),cop1);
            int now=maxn;
            for(int j=0;j<col[i].size();j++) {
                if(col[i][j].r<now){q[++m]=col[i][j];now=col[i][j].r;}
            }
        }
        sort(q+1,q+1+m,cop2);
        dp[0]=1*ksm(s,mod-2);
        update_t(1,dp[0]);//初始化
        for(int i=1;i<=m;i++) {
            ll t=query_t(q[i].l)*ksm(s,q[i].l);t%=mod;
            if(rt[q[i].b])t+=query_s(1,n,rt[q[i].b],q[i].l,q[i].r);
            dp[i]=(-t)%mod;
            update_t(q[i].r+1,(dp[i]*ksm(ksm(s,q[i].r+1),mod-2)%mod));
            if(!rt[q[i].b])rt[q[i].b]=++idx;
              update_s(1,n,rt[q[i].b],dp[i],q[i].r);
        }
        ll ans=0;
        for(int i=1;i<=m;i++) {
            ans+=dp[i]*ksm(s,n-q[i].r);ans%=mod;
        }
        ans+=ksm(s,n);
        ans=(ans%mod+mod)%mod;
        printf("%lld",ans);
        return 0;
    }
    

  • 相关阅读:
    centos7安装nginx
    linux经常使用的命令
    linux 安装 VNC
    linux配置yum源
    docker服务器、以及容器设置自动启动
    docker初步学习以及常用命令
    Docker命令详解(run篇)
    Linux scp命令
    Linux常用命令学习31个
    Linux下解压tar.xz文件
  • 原文地址:https://www.cnblogs.com/GavinZheng/p/11693105.html
Copyright © 2020-2023  润新知