Loading

被死亡凝视前还有一分钟去忘记了一切

[UNR 7]反重:求熵

好怪的题。

我们考虑一个一个消掉变量,现在考虑 \(x_n\),我们会有一堆形如:\(x_n\leq x_i+a_{n,i}\) 或者 \(x_n\geq x_i-a_{i,n}\) 的限制,显然第一类限制给出了 \(x_n\) 的上界,第二类限制给出了 \(x_n\) 的下界,如果已经确定了 \(x_1\cdots x_{n-1}\),只需要考虑两类限制中分别最紧的就行了。

我们枚举第一类限制中最紧的是 \(p\),第二类限制中最紧的是 \(q\) ,那么就是要求:

  • \(x_i+a_{n,i}\geq x_p+a_{n,p}\to x_p-x_i\leq a_{n,i}-a_{n,p}\)

  • \(x_i-a_{i,n}\leq x_q-a_{q,n}\to x_i-x_q\leq a_{i,n}-a_{q,n}\)

  • \(x_p+a_{n,p}\geq x_q-a_{q,n}\to x_q-x_p\leq a_{n,p}+a_{q,n}\)

这样就转化成了一堆和 \(n\) 无关的限制,递归去做,然后乘上 \(n\) 的取值区间大小即可。

我们注意到,每次相当于是乘了一个关于 \(x_p,x_q\) 的多项式,因此直接维护这 \(n\) 个多元多项式,更新的时候需要求自然数幂和,可以用伯努利数预处理。

这样大约是 \(O((n!)^2f(n))\)\(f(n)\) 是维护多项式的复杂度。

考虑优化,注意到我们的 \(p,q\) 其实没啥关系,换言之我们求出 \(p\) 的贡献,再求出 \(q\) 的贡献,再求和即可,这样复杂度变成 \(O(2^nn!f(n))\),可以过了。

注意更新限制的时候要去重,也就是加上一个 \([i<p]\) 之类的东西。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
const int mod = 998244353;
typedef long long LL;
int B[N],C[N][N],F[N][N];
int Pow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)res=1ll*res*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return res;
}
int inv[N];
void init(int n)
{
    inv[1]=1;
    for(int i=2;i<=n;i++)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
    C[0][0]=1;
    for(int  i=1;i<=n;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=i;j++)
        C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    B[0]=1;
    for(int i=1;i<=n;i++)
    for(int j=0;j<i;j++)
    B[i]=(B[i]-1ll*C[i][j]*B[j]%mod*inv[i-j+1]%mod+mod)%mod;
    for(int p=0;p<n;p++)
    {
        for(int j=0;j<=p;j++)
        {
            int v=1ll*inv[p+1]*C[p+1][j]%mod*B[j]%mod;
            if(j&1)v=(mod-v)%mod;
            F[p][p+1-j]=v;
        }
    }
}
LL a[N][N];
#define poly vector<int>
#define deg(x) (int)x.size()
#define up(x,v) x.resize(v)
void ckmin(LL &x,LL v){x=min(x,v);}
poly operator +(poly f,poly g)
{
    if(deg(f)<deg(g))swap(f,g);
    for(int i=0;i<deg(g);i++)f[i]=(f[i]+g[i])%mod;
    return f;
}
poly operator *(poly f,int c)
{
    for(int i=0;i<deg(f);i++)f[i]=1ll*f[i]*c%mod;
	return f;
}
poly operator *(poly f,poly g)
{
    int n=deg(f),m=deg(g);
    poly h;up(h,n+m-1);
    for(int i=0;i<n;i++)for(int j=0;j<m;j++)h[i+j]=(h[i+j]+1ll*f[i]*g[j]%mod)%mod;
	return h;
}
struct node
{
	poly f[N];
}cur;
int pw[N];
poly develop(poly f,LL d)
{
    int n=deg(f);
    d=(d%mod+mod)%mod;
    poly g;up(g,n+1);
    pw[0]=1;for(int i=1;i<=n;i++)pw[i]=1ll*pw[i-1]*d%mod;
	for(int i=0;i<n;i++)
    for(int j=0;j<=i+1;j++)
    for(int k=0;k<=j;k++)
    g[k]=(g[k]+1ll*C[j][k]*f[i]%mod*pw[j-k]%mod*F[i][j]%mod)%mod;
	return g;
}
int ans;
void dfs(int n,int c,node P)
{
    for(int i=0;i<=n;i++)
    for(int j=i;j<=n;j++)
    if(a[i][j]+a[j][i]<0)return;
    if(!n)
    {
        ans=(ans+1ll*c*P.f[0][0]%mod)%mod;
        return;
    }
    LL b[N][N];
    for(int x=0;x<=n;x++)for(int y=0;y<=n;y++)b[x][y]=a[x][y];
    for(int i=0;i<n;i++)
	{
	    for(int x=0;x<=n;x++)for(int y=0;y<=n;y++)a[x][y]=b[x][y];
		node Q=P;
		for(int j=0;j<n;j++)
        {
			ckmin(a[j][i],a[j][n]-a[i][n]-(j<i));
			ckmin(a[i][j],a[i][n]+a[n][j]);
		}
		Q.f[i]=Q.f[i]*develop(P.f[n],-a[i][n]-1);
		dfs(n-1,mod-c,Q);
	}
    for(int i=0;i<n;i++)
	{
	    for(int x=0;x<=n;x++)for(int y=0;y<=n;y++)a[x][y]=b[x][y];
		node Q=P;
		for(int j=0;j<n;j++)
        {
			ckmin(a[i][j],a[n][j]-a[n][i]-(j<i));
			ckmin(a[j][i],a[j][n]+a[n][i]);
		}
		Q.f[i]=Q.f[i]*develop(P.f[n],a[n][i]);
		dfs(n-1,c,Q);
	}
}
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
int main()
{
    init(12);
    int n,m;LL T;
    read(n);read(m);read(T);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    a[i][j]=T;
    for(int i=1;i<=n;i++)
    {
        a[0][i]=0;
        a[i][0]=T;
    }
    for(int i=1;i<=m;i++)
    {
        int x,y;LL v;
        read(x);read(y);read(v);
        a[x][y]=min(a[x][y],v);
    }
    node P;
    for(int i=0;i<=n;i++)P.f[i]={1};
    dfs(n,1,P);
    cout<<ans;
    return 0;
}

[UR #26] 铁轨回收

我们可以考虑倒着来,维护一个 “待匹配的长度集合”,每次到当前位置的时候,枚举当前位置最终长度,然后选择一个集合内的元素减掉这个长度,再把当前位置需要匹配的长度扔进集合里。

但是因为大小要对 \(B\)\(\min\),所以集合的状态数是爆炸的。

我们考虑容斥,对于一个位置,我们发现只有它长度 \(=B\) 时会出现这种情况,那我们考虑用所有情况减掉小于 \(B\) 的情况。

那么我们现在变成这样一个问题:

  • 每个位置要么选择长度 \(<B\),要么选择求任意,要么选择 \(<B\) 的带上 \(-1\) 的容斥系数。同时,如果某个位置的父亲是任意,那么该位置也要求任意。

那么我们可以考虑一个 \(dp\),记录当前的任意个数,以及 “待匹配的长度集合” 中每种长度剩几个。

这样做的好处是:我们不管怎样选,长度集合的总长度是不增的,并且一开始的长度不超过 \(B\),所以总过程中长度集合的状态数是 B 的分拆数级别!

然后再上述 \(dp\) 即可,千万别写记忆化搜索,常数逆天。

code
#pragma GCC optimize(2)
#define GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N = 66;
int A[N],B[N],n;
const int mod = 998244353;
int Pow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)res=1ll*res*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return res;
}
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
typedef unsigned long long ULL;
const ULL base = 1333331;
unordered_map<ULL,int> dp,id;
const int M = 30000;
int c[M][32],cnt[M],C[33],idx=0,ins[M][32],del[M][32],val[M];
int sum[N];
vector<int> pos[M];
void find_state(int x,int tot)
{
    if(x==31)
    {
        ULL h=0;
        ++idx;
        val[idx]=tot;
        for(int j=1;j<=30;j++)
        {
            c[idx][j]=C[j];
            cnt[idx]+=C[j];
            h=h*base+C[j]+1;
            if(C[j])pos[idx].push_back(j);
        }
        id[h]=idx;
        return;
    }
    for(int j=0;j<=n&&tot+x*j<=B[n];j++)
    {
        C[x]=j;
        find_state(x+1,tot+x*j);
    }
}
int H=0;
int f[N][N][M];
int main()
{
    read(n);
    for(int i=1;i<=n;i++)read(A[i]),read(B[i]),sum[i]=sum[i-1]+A[i];
    find_state(1,0);
    for(int i=1;i<=idx;i++)
    {
        ins[i][0]=i;
        del[i][0]=i;
        for(int j=1;j<=30;j++)
        {
            ULL A=0,B=0;
            for(int k=1;k<=30;k++)
            {
                if(k!=j)
                {
                    A=A*base+c[i][k]+1;
                    B=B*base+c[i][k]+1;
                }
                else
                {
                    A=A*base+c[i][k]+2;
                    B=B*base+c[i][k];
                }
            }
            if(id.find(A)!=id.end())ins[i][j]=id[A];
            if(id.find(B)!=id.end())del[i][j]=id[B];
        }
    }
    int mul=1;
    for(int i=1;i<n;i++)mul=1ll*mul*(n-i)%mod;
    for(int j=0;j<A[n];j++)printf("%d ",0);
    for(int k=0;k<=n;k++)
    f[0][k][1]=1;
    for(int i=1;i<n;i++)
    for(int k=0;k<=n-i;k++)
    for(int S=1;S<=idx;S++)
    if(sum[i]>=val[S]&&n-i-k-cnt[S]>=0)
    {
        int res=0;
        res=add(res,1ll*f[i-1][k+1][S]*k%mod);
        if(B[i]==0)
        {
            int u=n-i-k-cnt[S];
            if(u)res=add(res,1ll*u*f[i-1][k+1][S]%mod);
        }
        for(int j:pos[S])if(j>=B[i])
        {
            int u=c[S][j];
            int T=S;
            T=del[T][j];
            T=ins[T][j-B[i]];
            res=add(res,1ll*u*f[i-1][k+1][T]%mod);
            for(int t=A[i];t<B[i];t++)
            res=sub(res,1ll*u*f[i-1][k][ins[T][t-A[i]]]%mod);
        }
        if(A[i]==0&&B[i])
        {
            int u=n-i-k-cnt[S];
            if(u)res=add(res,1ll*u*f[i-1][k][S]%mod);
        }
        for(int j:pos[S])if(j>=A[i])
        {
            int u=c[S][j];
            for(int t=A[i];t<=min(j,B[i]-1);t++)
            {
                int T=S;
                T=del[T][j];
                T=ins[T][j-t];
                T=ins[T][t-A[i]];
                res=add(res,1ll*u*f[i-1][k][T]%mod);
            }
        }
        f[i][k][S]=res;
    }
    int res=0;
    for(int j=A[n];j<B[n];j++)
    {
        int v=f[n-1][0][ins[1][j-A[n]]];
        printf("%lld ",1ll*v*Pow(mul,mod-2)%mod);
        res=add(res,v);
    }
    printf("%lld ",1ll*sub(f[n-1][1][1],res)*Pow(mul,mod-2)%mod);
    return 0;
}

[USACO24OPEN] Identity Theft P

考虑一个无限大的字典树,那么这 \(n\) 个串各代表了一个终止节点,我们每次可以花费一个代价把一个终止节点移动到某个儿子,使得最终任意两个终止节点不构成祖先关系。

考虑自底向上贪心,我们每次可以选择以下两种决策:

  • 注意到每个时刻当前字典树的叶子恰好有一个终止节点,我们可以选择一个子树内的叶子,给这个叶子建两个儿子,并且把当前终止节点和叶子上的终止节点移动上去,代价是 \(d+2\),其中 \(d\) 为到叶子的距离。
  • 选择一个子树内的,恰好有一个儿子的节点,给这点新建一个儿子,然后把当前终止节点移上去,代价是 \(d+1\)

维护所有可能的决策,每次用最小代价的决策更新即可,决策的合并可以用可并堆,复杂度 \(O(N\log N)\),其中 \(N\) 为所有串的总长度和。

code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N = 5e6+7;
int tot=1;
char s[N];
int tr[N][2],cnt[N],d[N];
int ans=0;
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
int rot[N],lson[N],rson[N],idx=0,dis[N];
PII val[N];
int Merge(int x,int y)
{
	if(!x||!y)return x+y;
	if(val[x]>val[y])swap(x,y);
	if(dis[lson[x]]<dis[rson[x]]) swap(lson[x],rson[x]);
	dis[x]=dis[rson[x]]+1;
	rson[x]=Merge(rson[x],y);
	return x;
}
void dfs(int x)
{
    if(!tr[x][0]&&!tr[x][1])
    {
        int u=++idx;
        val[u]=mk(d[x]+2,x);
        rot[x]=u;
    }
    else if(!tr[x][0]||!tr[x][1])
    {
        int u=++idx;
        val[u]=mk(d[x]+1,x);
        rot[x]=u;
    }
    for(int c=0;c<=1;c++)
    if(tr[x][c])
    {
        d[tr[x][c]]=d[x]+1;
        dfs(tr[x][c]);
        rot[x]=Merge(rot[x],rot[tr[x][c]]);
    }
    if(!tr[x][0]&&!tr[x][1])cnt[x]--;
    while(cnt[x])
    {
        cnt[x]--;
        int u=rot[x],p=val[u].second;
        ans+=val[u].first-d[x];
        rot[x]=Merge(lson[u],rson[u]);
        if(!tr[p][0]&&!tr[p][1])
        {
            tr[p][0]=++tot;d[tot]=d[p]+1;
            int v=++idx;
            val[v]=mk(d[tot]+2,tot);
            rot[x]=Merge(rot[x],v);
            tr[p][1]=++tot;d[tot]=d[p]+1;
            v=++idx;
            val[v]=mk(d[tot]+2,tot);
            rot[x]=Merge(rot[x],v);
        }
        else
        {
            if(!tr[p][0])tr[p][0]=++tot,d[tot]=d[p]+1;
            if(!tr[p][1])tr[p][1]=++tot,d[tot]=d[p]+1;
            int v=++idx;
            val[v]=mk(d[tot]+2,tot);
            rot[x]=Merge(rot[x],v);
        }
    }
}
int main()
{
    //freopen("a.in","r",stdin);
    int n;
    read(n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        int p=1,m=strlen(s+1);
        for(int j=1;j<=m;j++)
        {
            int c=s[j]-'0';
            if(!tr[p][c])tr[p][c]=++tot;
            p=tr[p][c];
        }
        cnt[p]++;
    }
    dfs(1);
    cout<<ans;
    return 0;
}
    

[USACO24OPEN] Splitting Haybales P

讲个笑话,这题过了才发现保证了 \(a_1\geq a_2\geq \cdots a_n\) 的性质

\(x\) 为两人干草数量的差,每次相当于:

  • \(x\leq 0\)\(x=x+a_i\)
  • \(x>0\)\(x=x-a_i\)

我们可以先做一步预处理, 因为 \(x\) 在第一次转变正负前是操作固定的,我们可以先二分出第一个转变正负的位置,那么此时必然有 \(|x|\leq 2\times 10^5\),并且之后一直满足。

然后的做法和 P8264 基本相同,我们考虑分块,散块暴力操作,整块维护出每个初始值变成了啥。

我们初始令 \([l,r]=[1,2\times 10^5]\),表示当前还存在的值,那么当遇到一个 \(a_i\) 时,我们发现整体平移等价于平移原点,因此我们维护再维护当前的原点 \(p\)

平移过后,我们考察一下,若两个点关于原点对称,那么他们最终变成的值也只有正负号的区别,因此我们不妨把他们合并成一个,可以把点的个数少的一边向另一边对应的点连边,并把小的一部分删掉,最后只需要遍历一下这个森林就能知道每个点的答案了。

原点的答案需要特殊处理一下,复杂度约为 \(O(qB+\frac{n}{B}(V+q))\),一点不卡常。

code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
int sign(LL x){return x>0;}
int n,m;
const int N = 2e5+7;
int a[N];
LL s[N];
inline LL f(LL x,LL v){return x>0?x-v:x+v;}
int ql[N],qr[N],qx[N];
int bel[N],B;
int fa[N],ans[N],spe[N];
int calc(int x,int l,int r)
{
    for(int i=l;i<=r;i++)x=f(x,a[i]);
    return x;
}
int vis[N],tag=0,tag2=0;
void get(int x)
{
    if(vis[x]==tag||spe[x]==tag2)return;
    vis[x]=tag;
    get(fa[x]);
    if(spe[fa[x]]==tag2)
    {
        ans[x]=ans[fa[x]];
        spe[x]=tag2;
    }
    else
    {
        ans[x]=-ans[fa[x]];
    }
}
int main()
{
    read(n);
    for(int i=1;i<=n;i++)
    {
        read(a[i]);
        s[i]=s[i-1]+a[i];
    }
    read(m);
    for(int i=1;i<=m;i++)
    {
        int L,R;
        LL x;
        read(L);read(R);read(x);
        int l=L,r=R,pos=l-1;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(sign(x)==sign(f(x,s[mid]-s[L-1])))
            {
                pos=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        x=f(x,s[pos]-s[L-1]);
        ql[i]=pos+1;qr[i]=R;qx[i]=x;
    }
    B=sqrt(n);
    for(int al=1;al<=n;al+=B)
    {
        ++tag;
        ++tag2;
        int ar=min(n,al+B-1);
        int l=1,r=2e5,p=0;
        ans[0]=calc(0,al,ar);vis[0]=tag;spe[0]=tag2;
        for(int i=l;i<=r;i++)fa[i]=i;
        for(int i=al;i<=ar;i++)
        {
            int x=a[i];
            if(p<l)p+=x;
            else if(p>r)p-=x;
            if(p<l||p>r)continue;
            ans[p]=calc(0,i+1,ar);
            vis[p]=tag;spe[p]=tag2;
            if(p-l<r-p)
            {
                for(int i=l;i<p;i++)fa[i]=2*p-i;
                l=p+1;
            }
            else
            {
                for(int i=r;i>p;i--)fa[i]=2*p-i;
                r=p-1;
            }
        }
        for(int i=l;i<=r;i++)ans[i]=i-p,vis[i]=tag;
        for(int i=1;i<=m;i++)
        {
            if(qr[i]<al||ql[i]>ar)continue;
            if(ql[i]<=al&&ar<=qr[i])
            {
                if(qx[i]==0)qx[i]=ans[0];
                else
                {
                    int v=abs(qx[i]);
                    get(v);
                    if(spe[v]==tag2)qx[i]=ans[v];
                    else
                    {
                        if(qx[i]<0)qx[i]=-ans[v];
                        else qx[i]=ans[v];
                    }
                }
                continue;
            }
            for(int j=max(al,ql[i]);j<=min(ar,qr[i]);j++)qx[i]=f(qx[i],a[j]);
        }
    }
    for(int i=1;i<=m;i++)printf("%d\n",qx[i]);
    return 0;
}
    

[USACO24OPEN] Activating Robots P

首先有个 naive 的 \(O(R2^RN^2)\) 做法,设 \(dp_{S,i}\) 表示激活了 \(S\) 内的机器人,当前在第 \(i\) 个激活点的最小时间,转移枚举下一个激活点即可。

考虑转化一下,给所有东西加一个顺时针的,\(\frac{1}{K}\) 单位每秒的速度,那么现在,机器人就不会动了,激活点会以该速度移动,人也还可以移动,只是顺时针和逆时针移动速度不一样。

假设我们想要在某个点放一个机器人,因为此时是激活点在动,所以我们的最优策略显然是等到第一个转过来的激活点然后操作,这个可以二分得到。

\(dp_{S,i}\) 为激活了 \(S\) 内的机器人,上一次激活的是 \(i\),转移时枚举下一个激活的机器人,并二分算一下时间,复杂度 \(O(2^RR^2\log n)\)

注意到我们可以先不二分,我们对于每个状态,先算出不考虑等待时间的最小时刻,等到计算完成后,再对着这个最小时刻二分得到 \(dp_{S,i}\)

复杂度 \(O(2^RR(R+\log n))\)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int M = (1<<20)+7;
LL dp[M][22];
LL L,K,D;
int n,m;
LL a[N];
void ckmin(LL &x,LL v){x=min(x,v);}
LL ceil(LL x,LL y)
{
    return (x+y-1)/y;
}
LL dist(LL x,LL y)
{
    LL d=(x-y+L)%L;
    LL ans=ceil(K*d,K+1);
    if(K>1)ans=min(ans,ceil(K*(L-d),K-1));
    return ans;
}
LL solve(int i,LL t)
{
    LL P=(D*(i+1)+ceil(t,K))%L;
    int p=lower_bound(a+1,a+n+1,P)-a;
    LL d=a[p]-P;
    return K*(d+ceil(t,K));
}
LL dis[N];
int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.ans","w",stdout);
    read(L);read(m);read(n);read(K);D=L/m;
    for(int i=1;i<=n;i++)read(a[i]);
    sort(a+1,a+n+1);a[++n]=a[1]+L;
    for(int S=0;S<(1<<(m-1));S++)for(int i=0;i<m-1;i++)dp[S][i]=1e18;
    for(int i=0;i<m-1;i++)dp[(1<<i)][i]=dist(0,D*(i+1));
    for(int i=0;i<m;i++)dis[i]=dist(0,D*i);
    for(int S=0;S<(1<<(m-1));S++)
    for(int i=0;i<m-1;i++)if((S>>i)&1)
    {
        dp[S][i]=solve(i,dp[S][i]);
        for(int j=0;j<m-1;j++)if(!((S>>j)&1))
        ckmin(dp[S^(1<<j)][j],dp[S][i]+dis[(j-i+m)%m]);
    }
    LL mn=1e18;
    for(int i=0;i<m-1;i++)mn=min(mn,dp[(1<<(m-1))-1][i]);
    cout<<mn;
    return 0;
}
    

[PA 2022] Chodzenie po linie

有点牛的题。

首先,我们可以发现,每个连通块是原序列的一个区间,具体的,设 \(mx_i\) 为前 \(i\) 个数的最大值,那么若 \(mx_i=i\)\(i\) 是某个区间的右端点,并且这是充要条件,证明可以看看 CF1270H

然后我们对每个连通块分别处理。

假设现在要计算 \(i\) 的答案。

首先每个位置左边和右边是对称的,翻转序列和值域后再做一遍即可,我们现在只讨论前面。

我们把最短路之和 \(\sum_j dis_j\) 变成 \(\sum_{d}\sum_j[dis_j\geq d]\) ,这样只需要对每个 \(d\) 统计多少节点的 \(dis_j\geq d\) 即可。

  • \(d=1\) ,显然 \(i-1\) 个点都是符合的,因为图连通。
  • \(d=2\),可以一步到的只有 \(p_j>p_i\) 的点,所以贡献为 \(p_j<p_i\) 的点数。
  • \(d=3\),我们考虑一下两步到的路径形态,先考虑第一步走的点向左的情况,则一定走到 \(p_j>p_i\)\(j\) 最小的 \(j\) 最优,因为所有两步到的点一定满足 \(p_k<p_i\)\(j\) 越小显然这样的 \(k\) 越多。类似的,若第一步向右走,一定是走到 \(p_j\) 最小的 \(j\)。记左右两个点分别是 \(l_i,r_i\),此时不能在两步之内到的点是满足 \(k<l_i,p_k<p_{r_i}\) 的所有点,否则一定可以在两步内到达。
  • \(d>3\),推广上述情况,我们可以发现,我们总是可以用一个区间 \([L,R]\) 表示一个状态,初始时 \(L=R=i\),每一次令 \(L=l_{R},R=r_{L}\),就可以求出下一个 \(d\) 的状态,然后它的贡献就是 \(k<L,p_k<p_R\) 的所有点,这个比较容易证。

那么我们把所有区间间的转化建成一个棵树,可以证明这个树的点数是 \(O(n\sqrt n)\) 级别的,那么我们现在就是要查询某个点到根的路径上的权值和,每个权值是一个矩形数点,在线数点应该要写个 \(n^{\frac{1}{4}}\) 叉树,比较麻烦,我们考虑离线。

从左到右扫描,每个点的父亲一定比儿子先扫到,用分块平衡数点复杂度,可以做到 \(O(n\sqrt n)\)

建树的过程可以直接哈希存状态然后 BFS,也可以利用单调性,从右到左维护以每个位置为左端点的区间,每次更新区间的时候,右端点是单调的,因此可以直接判断是不是重复区间。

总复杂度 \(O(n\sqrt n)\)

code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N = 5e5+7;
typedef long long LL;
LL ans[N],dis[N];
int p[N],n=0;
int l[N],r[N];
int m,a[N],pos[N];
int idx=0;
const int M = 3e7+7;
int lp[M],rp[M],fa[M],B;
vector<int> used[N];
LL tag[N],sum[N],val[M];
void upd(int x)
{
    for(int i=x/B+1;i<=n/B;i++)tag[i]++;
    for(int i=x;i/B==x/B;i++)sum[i]++;
}
void calc()
{
    B=sqrt(n);
    int mnv=1e9,mnp=0;
    for(int i=n;i>=1;i--)
    {
        pos[p[i]]=i;
        if(p[i]<mnv)
        {
            mnv=p[i];
            mnp=i;
        }
        r[i]=mnp;
    }
    mnv=1e9;
    for(int i=n;i>=1;i--)
    {
        mnv=min(mnv,pos[i]);
        l[pos[i]]=mnv;
    }
    idx=0;
    for(int i=1;i<=n;i++)
    {
        used[i].clear();
        int x=++idx;
        lp[x]=rp[x]=i;
        used[i].push_back(x);
    }
    for(int i=n;i>=1;i--)
    {
		for(int j=0;j<used[i].size();j++)
        {
            int u=used[i][j];
            int L=l[rp[u]],R=r[i];
            if(used[L].empty()||R!=rp[used[L].back()])
            {
                int x=++idx;
                lp[x]=L;rp[x]=R;
                used[L].push_back(x);
            }
            fa[u]=used[L].back();
        }
    }
    for(int i=0;i<=n;i++)tag[i]=sum[i]=0;
    for(int i=1;i<=n;i++)
    {
        dis[i]=i-1;
        for(int j=used[i].size()-1;j>=0;j--)
        {
            int x=used[i][j],v=p[rp[x]];
            val[x]=val[fa[x]]+sum[v]+tag[v/B];
            if(rp[x]==i)dis[i]+=val[x];
        }
        upd(p[i]);
    }
    for(int i=1;i<=idx;i++)lp[i]=rp[i]=val[i]=fa[i]=0;
}
void solve(int l,int r)
{
    n=0;for(int i=l;i<=r;i++)p[++n]=a[i]-l+1;
    calc();
    for(int i=l;i<=r;i++)ans[i]+=dis[i-l+1];
    reverse(p+1,p+n+1);
    for(int i=1;i<=n;i++)p[i]=n-p[i]+1;
    calc();
    for(int i=l;i<=r;i++)ans[i]+=dis[r-i+1];
}
int main()
{
    //freopen("a.in","r",stdin);
    //freopen("a.ans","w",stdout);
    read(m);
    for(int i=1;i<=m;i++)read(a[i]);
    int mx=0,lst=1;
    for(int i=1;i<=m;i++)
    {
        mx=max(mx,a[i]);
        if(mx==i)
        {
            solve(lst,i);
            lst=i+1;
        }
    }
    for(int i=1;i<=m;i++)printf("%lld ",ans[i]);
    return 0;
}
    

P7811 [JRKSJ R2] 你的名字。

看起来就不太能polylog,直接根分。

  • \(k\leq B\) 是简单的,枚举 \(k\),因为线段树建树是线性的,所以直接线段树就好了。
  • \(k>B\) ,莫队维护处每个询问区间内每个数是否出现的 \(bitset\) ,询问时暴力在$ bitset$ 跳就行了。复杂度应该是 \(O(m(\frac{n}{B}+\frac{n}{w}))\),不过好像跑的还挺快的。

直接这样分据说过不去,我们直接动态分,对于每个 \(k\) 算一下分在哪一种里优,然后就轻松过了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
int n,m,A,M=0;
int a[N];
struct Query
{
    int l,r,k,id;
}Q[N],q[N];
vector<int> qry[N];
bool tag[N];
int K,mn[N*4];
void build(int k,int l,int r)
{
    if(l==r)
    {
        mn[k]=a[l]%K;
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    mn[k]=min(mn[k<<1],mn[k<<1|1]);
}
int ask(int k,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)return mn[k];
    int mid=(l+r)>>1;
    if(R<=mid)return ask(k<<1,l,mid,L,R);
    if(L>mid)return ask(k<<1|1,mid+1,r,L,R);
    return min(ask(k<<1,l,mid,L,mid),ask(k<<1|1,mid+1,r,mid+1,R));
}
int ans[N],blk[N],B;
bool cmp(Query x,Query y)
{
    if(blk[x.l]!=blk[y.l])return x.l<y.l;
    return x.r<y.r;
}
bitset<N> W;
int cnt[N];
inline void ins(int x)
{
    cnt[x]++;
    if(cnt[x]==1)W[x]=1;
}
inline void del(int x)
{
    if(cnt[x]==1)W[x]=0;
    cnt[x]--;
}
int main()
{
    //freopen("a.in","r",stdin);
    read(n);read(m);
    for(int i=1;i<=n;i++)read(a[i]),A=max(A,a[i]);A++;
    for(int i=1;i<=m;i++)
    {
        read(Q[i].l);read(Q[i].r);read(Q[i].k);
        Q[i].id=i;
        Q[i].k=min(Q[i].k,A);
        qry[Q[i].k].push_back(i);
    }
    for(int i=1;i<=A;i++)if(qry[i].size())
    {
        if(qry[i].size()*A/i<=n)
        {
            for(int j:qry[i])tag[j]=1;
            continue;
        }
        K=i;
        build(1,1,n);
        for(int j:qry[i])ans[j]=ask(1,1,n,Q[j].l,Q[j].r);
    }
    for(int i=1;i<=m;i++)if(tag[i])q[++M]=Q[i];
    B=sqrt(n);
    for(int i=1;i<=n;i++)blk[i]=(i-1)/B+1;
    sort(q+1,q+M+1,cmp);
    int l=1,r=0;
    for(int i=1;i<=M;i++)
    {
        int u=q[i].id,k=q[i].k;
        while(r<q[i].r)ins(a[++r]);
        while(l>q[i].l)ins(a[--l]);
        while(r>q[i].r)del(a[r--]);
        while(l<q[i].l)del(a[l++]);
        ans[u]=1e9;
        for(int j=W._Find_first();ans[u]&&j<W.size();j=W._Find_next((j/k+1)*k-1))
        if(j<W.size())ans[u]=min(ans[u],j%k);
    }
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;
}
    

[POI2015] CZA

从小到大插入,注意到我们只关注每种空隙有多少个,并且只需要记录空隙两边的数与当前数的差。

因为 \(p\) 很小,我们暴搜出所有状态,大概只有二十多,然后跑个简单的 dp of dp 就行了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define state vector<PII>
map<state,int> id;
int p,idx=0,te=0;
const int M = 27;
vector<PII> ers[M],cont[M];
int trans[M][M];
void put(state s)
{
    cout<<s.size()<<endl;
    for(auto u:s)printf("(%d,%d)",u.first,u.second);printf("\n");
}
int dfs(state seq)
{
    sort(seq.begin(),seq.end());
    if(id[seq])return id[seq];
    id[seq]=++idx;
    int x=idx;
    cont[x]=seq;
    state newstate;
    for(auto u:seq)
    {
        u.first++;
        u.second++;
        if(u.second<=p&&u.first<=p)newstate.push_back(u);
        else ers[x].push_back(u);
    }
    swap(seq,newstate);
    for(int i=0;i<(int)seq.size();i++)
    {
        auto u=seq[i];
        state now;
        for(int j=0;j<(int)seq.size();j++)if(i!=j)now.push_back(seq[j]);
        now.push_back(mk(u.first,0));
        now.push_back(mk(0,u.second));
        int y=dfs(now);
        trans[x][y]++;
    }
    return x;
}
int n,m;
int dp[N][M];
map<PII,int> ban;
const int mod = 1e9+7;
int main()
{
    read(n);read(m);read(p);
    state now;now.push_back(mk(0,0));
    int r=dfs(now);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        read(x);read(y);
        x=n-x;y=n-y;
        ban[mk(x,y)]=1;
    }
    dp[0][r]=1;
    for(int i=1;i<n;i++)
    for(int j=1;j<=idx;j++)
    if(dp[i-1][j])
    {
        bool flag=1;
        for(auto u:ers[j])
        {
            auto o=mk(i-u.first,i-u.second);
            if(ban.find(o)!=ban.end())
            {
                flag=0;
                break;
            }
        }
        if(!flag)continue;
        for(int k=1;k<=idx;k++)if(trans[j][k])
        dp[i][k]=(dp[i][k]+1ll*trans[j][k]*dp[i-1][j]%mod)%mod;
    }
    int ans=0;
    for(int j=1;j<=idx;j++)if(dp[n-1][j])
    {
        bool flag=1;
        for(auto u:cont[j])
        {
            auto o=mk(n-1-u.first,n-1-u.second);
            if(ban.find(o)!=ban.end())
            {
                flag=0;
                break;
            }
        }
        if(!flag)continue;
        ans=(ans+dp[n-1][j])%mod;
    }
    cout<<ans;
    return 0;
}
    

[USACO21FEB] Minimizing Edges P

会不了一点。

首先,因为一条边可以来回走,所以只要确定了到每个点分别经过奇数/偶数条边的最短路长度,就可以确定 \(f_G(a,b)\)

首先,如果图是一个二分图,那么奇数和偶数最短路一定只存在一种,我们只需要保留图的一棵最短路树就可以满足了,否则每个点一定都有奇数和偶数最短路(因为可以去奇环绕一圈)。

对于一个点,设两种最短路中较小的为 \(a_i\),较大的为 \(b_i\) ,我们就把每个点记为 \((a_i,b_i),a_i<b_i\)

我们发现,对于每个 \(i\),一定至少存在以下两种之一:

  • 存在与 \(i\) 相连的点 \(j\),满足 \((a_j,b_j)=(a_i-1,b_i-1)\)
  • 存在与 \(i\) 相连的点 \(j,k\),满足 \((a_j,b_j)=(a_i-1,b_i+1),(a_k,b_k)=(a_i+1,b_i-1)\) 。注意有一个特殊情况,当 \(a_i=b_i-1\) 时,因为 \(a_i+1>b_i-1\) ,所以相当于是 \((a_k,b_k)=(a_i,b_i)\)

我们把 \((a_i,b_i)\) 相等的点放在一起,记录其个数,如果我们把所有 \((a_i,b_i)\) 扔平面上,那么发现我们可以一条一条主对角线考虑,因为第一种情况连边代价小于第二种,所以我们贪心的选,把所有点以 \(x+y\) 为第一关键字,\(x\) 为第二关键字从小到大排序,然后分类讨论:

  • 如果不存在 \((a_i-1,b_i+1)\) ,那么就全部连接 \((a_i-1,b_i-1)\)

  • 如果 \(a_i\neq b_i-1\) ,那么会有一些从 \((a_i-1,b_i+1)\) 延伸过来的边,记其个数为 \(c\)\((a_i,b_i)\) 个数为 \(t\)

    • 不存在 \((a_i-1,b_i-1)\),那么所有点都只能选择第二类边,有 \(\min (t,c)\) 个已经被连过了,需要再向 \((a_i-1,b_i+1)\)\(\max(t,c)-c\) 条边,并且把 \(t\) 条边传给 \((a_i+1,b_i-1)\)
    • 存在 \((a_i-1,b_i-1)\) , \(c<t\) ,那么还有 \(t-c\) 个点可以连给 \((a_i-1,b_i-1)\) ,然后把 \(c\) 条边传下去。
    • \(c\geq t\),把 \(t\) 条边传下去。
  • 如果 \(a_i=b_i-1\),其实是类似的,只不过我们不能传下去,而是可以在 \((a_i,b_i)\) 内部匹配。方便起见,我们先当成上一种做,求出需要传下去的个数 \(c'\),然后加上 \(\lceil \frac{c'}{2}\rceil\) 条边即可。

这也可以看出来,我们之所以要把之前的 \(c\) 条边传下去而不是连向 \((a_i-1,b_i-1)\),是因为后面有可能有 \(a_i=b_i-1\) 的特殊点,此时只需要一半的代价,因此一定更优。

用个 map 维护 ,复杂度 \(O(n\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N = 2e5+7;
int dis[N*2];
int n,m;
vector<int> G[N*2];
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
struct node
{
    int d,x,y,c;
}seq[N];
bool cmp(node A,node B)
{
    if(A.d!=B.d)return A.d<B.d;
    return A.x<B.x;
}
int rest[N];
int cl(int x,int y)
{
    if(x%y==0)return x/y;
    return x/y+1;
}
void solve()
{
    read(n);read(m);
    for(int i=1;i<=2*n;i++)G[i].clear(),dis[i]=1e9;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        read(x);read(y);
        G[x].push_back(y+n);G[y+n].push_back(x);
        G[y].push_back(x+n);G[x+n].push_back(y);
    }
    queue<int> q;
    q.push(1);
    dis[1]=0;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int y:G[x])
        {
            if(dis[y]==1e9)
            {
                dis[y]=dis[x]+1;
                q.push(y);
            }
        }
    }
    if(dis[1+n]==1e9)//bigraph
    {
        printf("%d\n",n-1);
        return;
    }
    if(dis[1+n]==1)
    {
        printf("%d\n",n);
        return;
    }
    map<PII,int> cnt;
    for(int i=1;i<=n;i++)
    {
        int x=dis[i],y=dis[i+n];
        if(x>y)swap(x,y);
        cnt[mk(x,y)]++;
    }
    int idx=0;
    for(auto u:cnt)seq[++idx]=(node){u.first.first+u.first.second,u.first.first,u.first.second,u.second};
    sort(seq+1,seq+idx+1,cmp);
    int ans=0;
    for(int i=1;i<=idx;i++)
    {
        rest[i]=0;
        int x=seq[i].x,y=seq[i].y;
        int l=(cnt.find(mk(x-1,y+1))!=cnt.end());
        int p=(cnt.find(mk(x-1,y-1))!=cnt.end());
        int c=(y==x+1);
        if(!p&&!l)
        {
            rest[i]=1;
            ans+=1;
            continue;
        }
        if(!l)
        {
            ans+=seq[i].c;
            continue;
        }
        if(!p)
        {
            ans+=max(seq[i].c,rest[i-1])-rest[i-1];
            rest[i]=seq[i].c;
        }
        else
        {
            if(rest[i-1]<seq[i].c)
            {
                rest[i]=rest[i-1];
                ans+=seq[i].c-rest[i-1];
            }
            else rest[i]=seq[i].c;
        }
        if(c)ans+=cl(rest[i],2);
        else ans+=rest[i];
    }
    printf("%d\n",ans);
}
int main()
{
    int t;
    read(t);
    while(t--)
    {
        solve();
    }
    return 0;
}
    

[USACO20DEC] Cowmistry P

首先求出 \(B=2^d> K\) 的最小的 \(B\)

那么按照 \(B\) 对值域分块,不同块内显然不能同时选。

每个区间可以拆成若干整块和最多两个散块,整块的答案都是一样的,散块总共只有 \(O(n)\) 块,我们对每个散块分别处理,一个散块内我们不妨看成 \([0,B)\)

\(D=2^{d-1}\),那么就有两个子区间 \([0,D),[D,B)\) ,每个子区间内的数任意异或一定都不超过 \(K\),这一部分方案数我们简单算即可,然后来看跨过区间的贡献,一定是一边选了一个数,另一边选了两个。我们对于一边的每个数,计算出另一边有多少数与他异或不超过 \(K\),然后把 \(\binom{c_i}{2}\) 求和即可。

我们考虑递归计算 \(solve(A,B,d,C)\),表示对于 \(A\) 内的每一个区间内的数,求出 \(B\) 里区间内有多少个数与它异或不超过 \(K\),记为 \(c_i\),然后把 \(\binom{c_i+C}{2}\) 求和,并且这些区间的高位全部相同,且当前值域区间都可以看成是 \([0,2^d)\)

  • \(A\) 为空,直接返回。
  • \(B\) 为空,则就把 \(\binom {C}{2}\) 乘上 \(A\) 里的数的个数。
  • \(B\) 是一个满的,也就是 \([0,2^d)\) 内的数都有,那么此时可以发现 \(A\) 内的每一个数的 \(c_i\) 都相同,简单算算就能返回。
  • 否则,把 \(A,B\) 按照 \([0,2^{d-1}),[2^{d-1},2^d)\) 分裂成四个区间,然后可以类似数位 \(dp\) 一样的讨论继续递归下去,可以发现每个区间只会被递归一次。

算一下复杂度,对于 \(B\) ,类似于动态开点的值域线段树,所以复杂度 \(O(n\log V)\),对于 \(A\),因为它的返回条件没有 \(A\) 为满的情况,所以我们要再分析下:

  • 不妨把 \(A\) 割成若干个线段树上的节点区间,那么这些区间上方的总复杂度一定不超过 \(O(n\log V)\) ,对于下方,因为区间不交,且只有 \(B\) 不为空时才有用,所以也不超过 \(O(n\log V)\)

综上,总复杂度 \(O(n\log V)\)

另外,本体可以看成 CF1616H 的加强版,只需要把上述过程改成 \(dp\) 就行了,并且直接从值域角度理解感觉要比 trie 树更直观。

code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
const int N = 6e6+7;
const int mod = 1e9+7;
int Pow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)res=1ll*res*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return res;
}
const int i6=Pow(6,mod-2),i2=Pow(2,mod-2);
int n,m,R;
int lp[N],rp[N];
inline int C3(int x){return 1ll*x*(x-1)%mod*(x-2)%mod*i6%mod;}
inline int C2(int x){return 1ll*x*(x-1)/2%mod;}
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define segment vector<PII >
map<int,segment > vec;
int B=1,ans=0;
int suan(segment L)
{
    int res=0;
    for(auto u:L)
    res=(res+(u.second-u.first+1)%mod)%mod;
    return res;
}
void calc(segment A,segment B,int P,int c)
{
    if(A.empty())return;
    if(B.empty()){ans=(ans+1ll*suan(A)*C2(c)%mod)%mod;return;}
    segment F={mk(0,P-1)};
    if(B==F)
    {
        c=(c+((P-1)&m))%mod;
        ans=(ans+1ll*suan(A)*C2(c)%mod)%mod;
		return;
	}
	segment a[2],b[2];
	P/=2;
	auto apply=[&](segment &S,PII E)
	{
		E.first=max(E.first,0);
		E.second=min(E.second,P-1);
		if(E.first<=E.second)S.push_back(E);
	};
	//split
	for(auto S:A) apply(a[0],S),apply(a[1],mk(S.first-P,S.second-P));
	for(auto S:B) apply(b[0],S),apply(b[1],mk(S.first-P,S.second-P));
	if(m&P)
    {
        calc(a[0],b[1],P,(c+suan(b[0]))%mod);
        calc(a[1],b[0],P,(c+suan(b[1]))%mod);
	}
	else
    {
        calc(a[0],b[0],P,c);
        calc(a[1],b[1],P,c);
	}
}
void solve(segment E)
{
    int D=B/2;
    int c0=0,c1=0;
    segment S[2];
    for(auto u:E)
    {
        int l=u.first,r=u.second;
        if(l/D==r/D)S[l/D].push_back(mk(l%D,r%D));
        else
        {
            S[0].push_back(mk(l%D,D-1));
            S[1].push_back(mk(0,r%D));
        }
    }
    for(int c=0;c<=1;c++)
    {
        ans=(ans+C3(suan(S[c])))%mod;
        calc(S[c],S[c^1],D,0);
    }
}
int main()
{
    //freopen("a.in","r",stdin);
    read(n);read(m);++m;
    for(int i=1;i<=n;i++)read(lp[i]),read(rp[i]);
    B=1;
    while(B<=m)B<<=1;m-=(B/2);
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        int l=lp[i],r=rp[i];
        if(l/B==r/B)vec[l/B].push_back(mk(l%B,r%B));
        else
        {
            int pl=((l/B)+1)*B;
            int pr=(r/B)*B;
            vec[l/B].push_back(mk(l%B,(pl-1)%B));
            vec[r/B].push_back(mk(pr%B,r%B));
            cnt+=(r/B)-(l/B)-1;
            cnt%=mod;
        }
    }
    for(auto u:vec)solve(u.second);
    int c=(1ll*B*C2(m)%mod+2ll*C3(B/2)%mod)%mod;
    ans=(ans+1ll*c*cnt%mod)%mod;
    cout<<ans;
	return 0;
}
posted @ 2024-03-15 20:33  Larunatrecy  阅读(67)  评论(0编辑  收藏  举报