Potyczki Algorytmiczne 2021

Round 1

Oranżada [B]

题意

给定一个长度为 n 的序列 a,每次操作可以交换相邻两个元素,问至少需要多少次操作才能使得前 k 个元素两两不同。

n5×105

题解

容易发现,一个数字如果它前面已经出现过了,那么这个数字就没有用了。不妨把有用的数字记为 1,没用的记为 0,那么题意等价于每次交换相邻两个数字,使得 01 串前 k 个位置都是 1。贪心把前 k1 放到前面即可。

复杂度 O(n)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=500010;
int a[N];bool vis[N];
int main()
{
    int n,k;scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    long long ans=0;
    for(int i=1,p=1;i<=k;i++)
    {
        while(p<=n && vis[a[p]]) p++;
        if(p>n){puts("-1");return 0;}
        vis[a[p]]=true;
        ans+=p-i;
    }
    printf("%lld\n",ans);
    return 0;
}

Od deski do deski [A]

题意

定义一次操作可以删除序列的一个区间,满足区间长度 2 且两端数字相等。定义一个序列是好的,当且仅当可以通过若干次操作把这个序列删空。

问所有长度为 n,所有元素 [1,m] 的序列中有多少个序列是好的。对 109+7 取模。

n3000,m109

题解

一直往容斥方面想,然后发现状态没法维护,直接寄了。

考虑换一个方向,考虑怎么判断一个序列是否合法。这个可以从前往后 dp,然后对于每个位置,如果它前面存在一个和它相同的位置 i 并且 dpi1=1,那么这个前缀也是合法的。

反过来说,当前位置是否合法只和 j[1i) 中满足 dpj1=1 的位置的不同颜色数有关。由于其中不包括 dpi,所以再额外记一维 dpi1 即可。

即用 fi,j,0/1 表示前 i 个位置,满足 dpj1=1aj 本质不同数量有 j 种,dpi=0/1 的方案数。转移枚举当前位置与上一个位置的 dp 值即可。

复杂度 O(n2)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=3010,mod=1000000007;
int f[N][N][2];
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    f[1][1][0]=m;
    for(int i=2;i<=n;i++)
        for(int j=1;j<=i;j++)
        {
            add(f[i][j][1],1ll*j*(f[i-1][j][0]+f[i-1][j][1])%mod);
            add(f[i][j][0],(1ll*(m-j+1)*f[i-1][j-1][1]+1ll*(m-j)*f[i-1][j][0])%mod);
        }
    int ans=0;
    for(int i=1;i<=n;i++) add(ans,f[n][i][1]);
    printf("%d\n",ans);
    return 0;
}

Round 2

Pandemia [B]

题意

n 个球排成一排,一开始有一些球是黑的。现在进行若干轮操作,每次你可以将一个没有被染黑的球染白,然后如果对于所有没有染色的球,如果它在一个黑色球旁边,那么它会被染黑。在所有球都被染色后,最小化黑色球个数。

n105

题解

可以发现最小化黑色球数目等价于最大化无色球数目。把黑色球之间的区间拿出来,最优方案一定是舍弃一些区间,然后尽可能增加剩下区间中的无色球数目。

枚举两侧的区间是否要舍弃,中间的区间一定是优先舍弃小区间,直接贪心即可。复杂度 O(n)

代码咕咕咕了。

Poborcy podatkowi [A]

题意

给定一棵树,边有边权(可能为负)。要求在树上选出若干条长度为 45 个点)的边不交路径,使得边权和最大。

n2×105

题解

看到过好几次的题?考虑 dp,设 fi,j 表示当前以 i 为根子树内,根所在链长度为 j 的边权和。考虑子树中 dp 需要让 1,3 匹配,2 两两匹配。然而 1,3 数量可能会很大,这可能会导致 O(n) 的额外复杂度。考虑将子树 shuffle 一下,根据霍夫丁不等式,此时最优解出现前缀 13 个数差超过 n 的概率是 106 左右,可以近似认为不会发生。

直接 dp 即可。复杂度 O(nn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<random>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200010;const ll inf=0x3f3f3f3f3f3f3f3f;
mt19937 Rnd(123);
int nxt[N<<1],to[N<<1],w[N<<1],head[N],cnt;
void chkmax(ll &x,ll y){x=max(x,y);}
void add(int u,int v,int w0){nxt[++cnt]=head[u];to[cnt]=v,w[cnt]=w0;head[u]=cnt;}
ll f[N][4];
void dfs(int u,int p)
{
    int c=0;
    vector<pair<int,int>>son;
    for(int i=head[u];i;i=nxt[i]) if(to[i]!=p) dfs(to[i],u),++c,son.emplace_back(to[i],w[i]);
    shuffle(son.begin(),son.end(),Rnd);
    int s=max(2*(int)sqrt(c)+1,8),sz=0;static ll t1[N][2][4],t2[N][2][4];auto g=t1+s,h=t2+s;
    for(int i=-s;i<=s;i++)
        for(int x=0;x<=3;x++) g[i][0][x]=g[i][1][x]=-inf;
    g[0][0][0]=0;
    for(auto [v,w]:son)
    {
        sz=min(sz+1,s-1);
        for(int j=-sz;j<=sz;j++)
            for(int x=0;x<=1;x++)
                for(int y=0;y<=3;y++)
                    h[j][x][y]=g[j][x][y],g[j][x][y]=-inf;
        for(int j=-sz;j<=sz;j++)
        {
            for(int x=0;x<=3;x++) if(f[v][x]>-inf)
                for(int k=0;k<=1;k++)
                    for(int y=0;y<=3;y++)
                    {
                        ll res=h[j][k][y]+f[v][x]+w;
                        if(x!=3 && !y) chkmax(g[j][k][x+1],res);
                        if(x==3) chkmax(g[j][k][y],res);
                        if(x==2) chkmax(g[j+1][k][y],res);
                        if(x==1) chkmax(g[j][!k][y],res);
                        if(x==0) chkmax(g[j-1][k][y],res),chkmax(g[j][k][y],h[j][k][y]+f[v][x]);
                    }
        }
    }
    for(int i=0;i<=3;i++) f[u][i]=g[0][0][i];
}
int main()
{
    memset(f,0xcf,sizeof(f));
    int n;scanf("%d",&n);
    for(int i=1,x,y,w;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&w);
        add(x,y,w),add(y,x,w);
    }
    dfs(1,0);
    printf("%lld\n",f[1][0]);
    return 0;
}

Round 3

Mopadulo [B]

题意

mod=109+7,给定一个长度为 n 的序列 a,问把 a 分成若干区间的方案数,使得每个区间的和对 mod 取模后结果是偶数。n3×105

题解

考虑 dp。显然有一个 O(n2) 的区间 Dp。容易发现,sisjmod 取模后的奇偶性,等于 si 取模后奇偶性异或 sj 取模后奇偶性异或 [si<sj]

直接对奇偶性分讨然后用树状数组优化。复杂度 O(nlogn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300010,mod=1000000007;
int a[N],b[N],s[N],p[N],n,m;
struct fenwick_pre{
    int a[N];
    void add(int x,int v){for(;x<=m;x+=x&-x) a[x]=(a[x]+v)%mod;}
    int qry(int x){int v=0;for(;x;x-=x&-x) v=(v+a[x])%mod;return v;}
}pre[2];
struct fenwick_suf{
    int a[N];
    void add(int x,int v){for(;x;x-=x&-x) a[x]=(a[x]+v)%mod;}
    int qry(int x){int v=0;for(;x<=m;x+=x&-x) v=(v+a[x])%mod;return v;}
}suf[2];
int f[N];
int main()
{
    int n;scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=(s[i-1]+a[i])%mod;
    for(int i=0;i<=n;i++) b[++m]=s[i];
    sort(b+1,b+m+1),m=unique(b+1,b+m+1)-b-1;
    for(int i=0;i<=n;i++) p[i]=lower_bound(b+1,b+m+1,s[i])-b;
    pre[0].add(p[0],1),suf[0].add(p[0],1);
    for(int i=1;i<=n;i++)
    {
        f[i]=(pre[s[i]&1].qry(p[i])+suf[!(s[i]&1)].qry(p[i]+1))%mod;
        pre[s[i]&1].add(p[i],f[i]),suf[s[i]&1].add(p[i],f[i]);
    }
    printf("%d\n",f[n]);
    return 0;
}

Wystawa [A]

题意

给定两个长度为 n 的序列 a,b,要求构造一个序列 cci 等于 ai 或者 bi,并且恰好有 k 个位置等于 ai,其余位置等于 bi。最小化 c 的最大子段和。

n105

题解

容易得到一个 O(n2logx) 的做法:首先二分答案 X,然后设 fi,j 表示前 i 个位置,有 j 个位置是 A,前缀最长子段和 X 情况下的最大后缀和最小值。容易写出转移式子:fi,jfi+1,j+Bi,fi,jfi+1,j+1+Ai。同时所有数字对 0max

考虑将 (j,fi,j) 画出,容易发现第一个转移式子相当于把图像向上平移了 Bi,第二个转移式子等价于找到第一个斜率 >AiBi 的位置,然后将后面的图像移动 (1,AiBi)。可以证明这个图像是下凸的。

考虑求出差分数组,显然差分数组是单调的,那么第二个转移式子等价于插入到不影响单调性的位置上。接下来对 0max 等价于将一段前缀的差分数组变成 0(需要特殊处理第一个位置),X 等价于将一段后缀的函数删去。

最后如果图像中存在 k 位置就是有解,否则无解。

考虑如何构造。一种方式是直接用可持久化线段树记录前驱,但非常难写。实际上有一种非常巧妙的方式:注意到差分数组的每一个数字都是由一个位置的 AiBi 贡献的。那么不妨记录这些位置,这样只需要将留到最后的差分数组上的这些位置取出就是构造。

复杂度 O(nlognlogx)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<vector>
#define fi first
#define se second
using namespace std;
const int N=100010;
typedef long long ll;
int a[N],b[N],n,k;bool ban[N];ll sm,al;
vector<int>ans;set<pair<ll,int>>s;
void out(int x){if(ans.size()<k) ans.push_back(x);};
void add(pair<ll,int> x){s.insert(x),al+=x.fi;};
void del(pair<ll,int> x){s.erase(x),al-=x.fi;};
bool check(ll X)
{
    sm=0,al=0,ans.clear(),s.clear();
    for(int i=1;i<=n;i++)
    {
        sm+=a[i];
        if(!ban[i]) add({b[i]-a[i],i});
        while(sm+al>X)
        {
            if(s.empty()) return false;
            del(*s.rbegin());
        }
        while(sm<0)
        {
            if(s.empty()){sm=0;break;}
            auto p=*s.begin();del(p);
            if(sm+p.fi<0) out(p.se),sm+=p.fi;
            else p.fi+=sm,sm=0,add(p);
        }
    }
    for(auto v:s) out(v.se);
    return ans.size()>=k;
}
int w[N];
int main()
{
    scanf("%d%d",&n,&k),k=n-k;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    int k0=0,flg=0;for(int i=1;i<=n;i++) if(a[i]>b[i]) ++k0;
    if(k0>k){flg=1;for(int i=1;i<=n;i++) swap(a[i],b[i]);k=n-k;}
    for(int i=1;i<=n;i++) if(a[i]>b[i]) --k,swap(a[i],b[i]),ban[i]=true;
    ll l=0,r=1e15,res=0;
    while(l<=r)
    {
        ll mid=(l+r)>>1;
        if(check(mid)) r=mid-1,res=mid;
        else l=mid+1;
    }
    check(res);
    printf("%lld\n",res);
    for(int i=1;i<=n;i++) w[i]=ban[i];
    for(int v:ans) w[v]=1;
    for(int i=1;i<=n;i++) putchar((w[i]^flg)?'B':'A');
    return 0;
}

Round 4

Skrzyżowania [B]

题意

平面上有 (n+1)×(m+1) 个街区,被 n 条横向道路和 m 条纵向道路分开。每个十字路口有一个红绿灯。红绿灯以不大于 8 的周期运作,如果为 0 表示这个时刻其左边的两个街区分别与右边的两个街区相连,否则表示其上方的两个街区分别与下方的两个街区相连。你可以在一个时刻内通过任意相连的街区。

q 次询问,每次问你在 ti 时刻从 (ai,bi)(ci,di) 最早什么时刻可以到达。

题解

手模一下会发现大部分情况似乎所有街区都是连通的,除了某一排的红绿灯全部横向连接,或者某一列的红绿灯全部纵向连接。

任意时刻不会同时出现两种情况,所以可以把行列分开算。由于 q 很大,考虑离线分治,每次枚举中间行的通过时间对 k=3×5×7×8=840 取模的结果,左右可以分别贪心计算。

复杂度 O(nm+k(n+m)logn+qlogn)。非常卡常,过不太去。

考虑换个思路。不妨假设当前处理的是行,那么可以构造一个 n×k 的网格,其中相邻列之间的某些边界不能通过,其余都可以通过。每次可以往上或者往右走,最顶上可以继续往上走走到最底下。容易发现,不妨假设一个点往右是 0,往上是 1,找到从这个点开始字典序最小的到达最右端的路径,那么对于一次从这个点开始到 r 结束的询问,只需要找到路径中第一次到达第 r 列的时间即可。

而在这道题中这等价于找到反串字典序最大的。所以直接从右端开始 bfs 就可以确定这个路径树,然后再路径树上 dfs 即可。

复杂度 O(nm+k(n+m)+q)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<ctime>
#include<queue>
#define fi first
#define se second
using namespace std;
const int N=15010,M=842,m=840,K=1000010;
vector<vector<vector<int>>>s;
int ans[K],q;
struct sub{
    bool f[N][M],vis[N*M];int ql[K],qr[K],qt[K],n;
    vector<int>Q[N*M];int son[N*M][2];
    int id(int x,int y){return x*m+y;}
    int dep[N];
    void dfs(int u)
    {
        int d=u/m;
        for(int v:Q[u])
            ans[v]=max(ans[v],dep[d]-dep[qr[v]]);
        for(int i=0;i<=1;i++)
        {
            int v=son[u][i];
            if(v==-1) break;
            if(v/m==d) dep[d]++,dfs(v),dep[d]--;
            else dep[v/m]=dep[d],dfs(v);
        }
    }
    void work()
    {
        memset(son,-1,sizeof(son));
        for(int i=1;i<=q;i++) if(ql[i]<qr[i]) Q[id(ql[i],qt[i]%m)].push_back(i);
        queue<int>qu;
        auto push=[&](int u,int p){
            if(vis[u]) return;
            if(p!=-1)
            {
                if(son[p][0]==-1) son[p][0]=u;
                else if(son[p][1]==-1) son[p][1]=u;
                else throw;
            }
            vis[u]=true,qu.push(u);
        };
        for(int i=0;i<m;i++) push(id(n,i),-1);
        while(!qu.empty())
        {
            int u=qu.front(),x=u/m,y=u%m;qu.pop();
            push(id(x,(y-1+m)%m),u);
            if(x && f[x-1][y]) push(id(x-1,y),u);
        }
        for(int i=0;i<m;i++) dep[n]=0,dfs(id(n,i));
        memset(son,-1,sizeof(son));
        for(int i=0;i<=(n+1)*m;i++) Q[i].clear(),vis[i]=0;
        for(int i=1;i<=q;i++) if(ql[i]>qr[i])
            Q[id(ql[i],qt[i]%m)].push_back(i);
        for(int i=0;i<m;i++) push(id(0,i),-1);
        while(!qu.empty())
        {
            int u=qu.front(),x=u/m,y=u%m;qu.pop();
            push(id(x,(y-1+m)%m),u);
            if(x<n && f[x][y]) push(id(x+1,y),u);
        }
        for(int i=0;i<m;i++) dep[0]=0,dfs(id(0,i));
        for(int i=0;i<=(n+1)*m;i++) Q[i].clear(),vis[i]=0;
    }
}F;
int q0[K],q1[K],q2[K],q3[K],q4[K];
int main()
{
    int r,c;scanf("%d%d%d",&r,&c,&q);
    s.resize(r+1);
    for(int i=0;i<=r;i++) s[i].resize(c+1);
    for(int i=0;i<r;i++)
        for(int j=0;j<c;j++)
        {
            char str[10];scanf("%s",str);
            int l=strlen(str);s[i][j].resize(l);
            for(int k=0;k<l;k++) s[i][j][k]=str[k]-'0';
        }
    for(int i=1;i<=q;i++) scanf("%d%d%d%d%d",&q0[i],&q1[i],&q2[i],&q3[i],&q4[i]);
    F.n=r;
    for(int i=0;i<r;i++)
    {
        static bool g[9][9];memset(g,0,sizeof(g));
        for(int j=0;j<c;j++)
        {
            int l=s[i][j].size();
            for(int k=0;k<l;k++) if(!s[i][j][k]) g[l][k]=true;
        }
        for(int j=0;j<m;j++)
        {
            F.f[i][j]=false;
            for(int l=2;l<=8;l++) if(g[l][j%l]){F.f[i][j]=true;break;}
        }
    }
    for(int i=1;i<=q;i++) F.qt[i]=q0[i],F.ql[i]=q1[i],F.qr[i]=q3[i];
    F.work();
    F.n=c;
    for(int i=0;i<c;i++)
    {
        static bool g[9][9];memset(g,0,sizeof(g));
        for(int j=0;j<r;j++)
        {
            int l=s[j][i].size();
            for(int k=0;k<l;k++) if(s[j][i][k]) g[l][k]=true;
        }
        for(int j=0;j<m;j++)
        {
            F.f[i][j]=false;
            for(int l=2;l<=8;l++) if(g[l][j%l]){F.f[i][j]=true;break;}
        }
    }
    for(int i=1;i<=q;i++) F.qt[i]=q0[i],F.ql[i]=q2[i],F.qr[i]=q4[i];
    F.work();
    for(int i=1;i<=q;i++) printf("%d\n",q0[i]+ans[i]);
    return 0;
}

Areny [A]

题意

给定一张有向图,保证每个点至少有 1 条出边。有一个参数 k,从一个点 u 开始走,每次随机一条出边走出去,如果走到 >k 的点就结束操作。对于 k[1,n],问有多少对点 (u,v) 满足 uv 且从 u 开始走一定可以在有限步数走到 v

n2×105,m5×105

题解

为了简化,记 uv 表示从 u 出发一定能到 v

首先考虑 k=n 的情况。显然有一个 O(n2) 做法是:枚举终点 v,显然 vv,如果一个点 u 所有出边对应点 v 都有 vv,那么 uv。显然这样类似拓扑的过程是充要的。

考虑一个性质:如果 uv,uv,那么 vvvv 至少有一个满足。并且显然 uv,vw 可以推出 uw。所以可以将整张图划成若干子图,满足每个子图 G 内都存在一个点 rtG,使得 vG,vrt

分割子图的过程可以用启发式合并加拓扑在 O(mlogn) 内解决。

容易发现,如果 (u,v) 在不同子图中,那么一定不存在 uv,所以可以对于每个子图分别处理。

这样的一个好处就是:对于每张子图 G,取 rt 为根,所有点都存在到根的路径,且去掉 rt 连出的边后,整个子图是一张 DAG。这样可以把一个点 u 开始走的路径分成从 u 开始到 rt 和从 rt 出发回到 rt。只要建出 G 的除去 rt 出边外的支配树,上述过程都可以直接在支配树上解决。复杂度 O(mlogn)

接下来考虑解决 k=1n 的部分。首先可以计算出一个点 u 到其支配点过程中可能到达的点编号最大值 w,如果 w>k,可以证明 u 就不存在支配点了(即不存在 v 使得 uv)。但是对于 rt,这样做可能是不正确的:可能存在一个点编号非常大,但实际上在这个点之前存在一个基环上的点是必经点。

考虑直接找出这个环,然后以环上最大编号点作为根。这样就不会存在上述的问题了。

题意简化为:有一个基环内向树森林,对于每个 k,求出有多少 (x,y) 满足 xy 最短路径上边权均 k。考虑找到环上最大的边,那么在这条边断掉之前可以直接套用树的做法,这条边加上后可以看做一棵新的树。用 set
启发式合并即可做到 O(nlog2n),用其他类似的东西可以做到 O(nlogn)

感觉非常难写。咕咕咕了。

Round 5

Autostrada [B]

题意

3 条道路,上面有若干辆 1×1 的车,通过一个 3×L 的矩阵描述。第 i 条道路上的车子车速为 vi

你现在在最后一格,需要超过所有车。你的车子最快速度是 v0,并且可以在相邻道路之间任意切换,切换不需要时间但是需要对应位置没有车。你不一定需要在整数时间内切换。现在你希望知道你超过所有车的最小时间。

L2×105,v3<v2<v1<v0140

题解

考虑以中间车道的车为基准,假设中间车道的车是不动的,相当于左侧的车在往前,右侧的车在往后。

容易发现,在任何一条车道,当前的车要么全速往前,要么与当前车道保持相对禁止。

考虑可能的策略。首先在中间车道如果前面是空着的一定全速往前,否则可以转到左右侧车道,或者等待左右侧车道的空位。

可以发现,无论在左侧车道还是右侧车道,只要中间车道是空着的,转移到中间车道一定不劣。同时对于左侧车道,还有一个附加的策略是一直跟在最前面的车后面,直到到达中间车道的下一个位置。

代码有点难写,先咕咕咕了。

Desant 2 [B]

题意

给定整数序列 a,有 q 次询问,每次询问一个区间 [l,r],问从中选出若干不交的长度为 k 的区间,最大化区间内部 a 之和。

题解

考虑将 a 列成一个 k×nk 的网格,那么 ii+1i+k 连边相当于网格中向上和向右连边(最顶上的点向下一列最底下的点连边)。

考虑根据 kn 的关系分讨。如果 k<n,那么直接按行分治。否则特别处理第一行的连边,然后按列分治。

复杂度 O(nnlogn)。如果每次都取较大的一边分治,可以做到 O(nn)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N=600010;
typedef long long ll;const ll inf=1e18;
int n,m,q,a[N];ll s[N];
void chkmax(ll &x,ll y){x=max(x,y);}
struct node{
    int x,y;
    node(){x=y=0;}node(int s):x(s%m),y(s/m){}
    node(int x,int y):x(x),y(y){}
};
int id(int x,int y){return x+y*m;}
bool in(node l,node r,node u){return l.x<=u.x && u.x<=r.x && l.y<=u.y && u.y<=r.y;}
ll f[N],g[N];
void upd(node l,node r,node p)
{
    for(int i=l.x;i<=r.x;i++)
        for(int j=l.y;j<=r.y;j++) f[id(i,j)]=g[id(i,j)]=-inf;
    f[id(p.x,p.y)]=g[id(p.x,p.y)]=0;
    for(int j=l.y;j<=r.y;j++)
        for(int i=l.x;i<=r.x;i++)
        {
            int u=id(i,j);
            if(in(l,r,u+1)) chkmax(g[u+1],g[u]);
            if(in(l,r,u+m)) chkmax(g[u+m],g[u]+s[u+m]-s[u]);
        }
    for(int j=r.y;j>=l.y;j--)
        for(int i=r.x;i>=l.x;i--)
        {
            int u=id(i,j);
            if(in(l,r,u-1)) chkmax(f[u-1],f[u]);
            if(in(l,r,u-m)) chkmax(f[u-m],f[u]+s[u]-s[u-m]);
        }
}
int ql[N],qr[N];ll ans[N];
void solve1(node l,node r,vector<int>&Q)
{
    if(l.x>r.x || l.y>r.y || Q.empty()) return;
    // cerr<<l.x<<" "<<l.y<<" "<<r.x<<" "<<r.y<<":";
    // for(int v:Q) cerr<<v<<" ";cerr<<endl;
    vector<int>L,R;
    int p=(l.x+r.x)>>1;node pl={p-1,r.y},pr={p+1,l.y};
    for(int v:Q) if(in(l,pl,ql[v]) && in(l,pl,qr[v])) L.push_back(v);
    else if(in(pr,r,ql[v]) && in(pr,r,qr[v])) R.push_back(v);
    solve1(l,pl,L),solve1(pr,r,R);
    for(int i=l.y;i<=r.y;i++)
    {
        upd(l,r,{p,i});
        for(int v:Q) chkmax(ans[v],f[ql[v]]+g[qr[v]]);
    }
    if(l.x==0 && r.x==m-1)
        for(int i=l.y;i<=r.y;i++)
        {
            upd(l,r,{r.x,i});
            for(int v:Q) chkmax(ans[v],f[ql[v]]+g[qr[v]]);
        }
}
void solve2(node l,node r,vector<int>&Q)
{
    if(l.x>r.x || l.y>r.y || Q.empty()) return;
    vector<int>L,R;
    int p=(l.y+r.y)>>1;node pl={r.x,p-1},pr={l.x,p+1};
    for(int v:Q) if(in(l,pl,ql[v]) && in(l,pl,qr[v])) L.push_back(v);
    else if(in(pr,r,ql[v]) && in(pr,r,qr[v])) R.push_back(v);
    solve2(l,pl,L),solve2(pr,r,R);
    for(int i=l.x;i<=r.x;i++)
    {
        upd(l,r,{i,p});
        for(int v:Q) chkmax(ans[v],f[ql[v]]+g[qr[v]]);
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(++n;n%m;++n);
    for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
    vector<int>id;
    for(int i=1;i<=q;i++) scanf("%d%d",&ql[i],&qr[i]),id.push_back(i),--ql[i];
    if(m<=633) solve2({0,0},{m-1,n/m-1},id);
    else solve1({0,0},{m-1,n/m-1},id);
    for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);
    return 0;
}

Fiolki 2 [A]

题意

给定一张 n 个点的 DAG,保证前 k 个点没有入度。对于 x[0,k],输出有多少个区间 [l,r],使得从 [1,k] 开始到 [l,r] 内的点结束的不相交路径条数恰好为 x

n105,k50

题解

其实和 这题 很像。

考虑构造一个 k 维向量,对于前 k 个点,令第 i 个点点权 fi 是除了第 i 个位置为 1,其余位置全部为 0 的向量。对于 k+1n 的点,令第 i 个点点权 fi 是满足 vj=i 的边 (uj,vj)fu,jxj 之和。

容易证明,最后区间 [l,r] 中极大线性无关组数量就是答案。证明考虑用类似 LGV 引理的思路,要判断是否存在满流只需要判断 |{fi|i[l,r]}| 是否满秩即可,同样要判断流量大小等于矩阵秩的大小,也即极大线性无关组数量。

直接取 xi 复杂度是无法接受的。根据经典套路,我们令 xi 等于一个 [0,P) 的随机值,然后求出乘积在模 P 意义下的结果。根据 Schwartza-Zippela 定理,这个随机值导致基变小的概率是 1P

这样维护一个广义线性基。注意到这里我们需要时刻维护所有基中编号最大的元素,由于线性基满足拟阵性质,每次插入的向量 x,当 x 需要减去线性基中某个元素时,如果那个元素对应编号小于 x 的编号,就交换它们。可以证明,这样总是在线性基中留下编号最大的元素。

这样直接从前往后扫,然后将线性基中元素排序后左端点在 [ai,ai+1) 的部分就是大小为 i 的线性基。

复杂度 O(mk+nk2)

代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<random>
#include<queue>
#include<vector>
#include<algorithm>
#define fi first
#define se second
using namespace std;
typedef vector<int> vec;
const int N=100010,K=52,mod=1019260817;
int ksm(int a,int b=mod-2)
{
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
    return r;
}
mt19937 Rand(1234);
vec f[N];int n,m,k;vector<int>g[N];int deg[N];
vec operator +(const vec &a,const vec &b){vec c(k);for(int i=0;i<k;i++) c[i]=(a[i]+b[i])%mod;return c;}
vec operator -(const vec &a,const vec &b){vec c(k);for(int i=0;i<k;i++) c[i]=(a[i]-b[i]+mod)%mod;return c;}
vec operator *(const vec &a,int v){vec c(k);for(int i=0;i<k;i++) c[i]=1ll*a[i]*v%mod;return c;}
queue<int>q;
pair<int,vec> b[K];
void insert(pair<int,vec> x)
{
    for(int i=k-1;i>=0;i--) if(x.se[i])
    {
        if(!b[i].fi){b[i]=x;break;}
        else
        {
            if(b[i].fi<x.fi) swap(b[i],x);
            int iv=1ll*x.se[i]*ksm(b[i].se[i])%mod;
            x.se=x.se-b[i].se*iv;
        }
    }
}
long long ans[K];
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0,x,y;i<m;i++) scanf("%d%d",&x,&y),--x,--y,g[x].push_back(y),deg[y]++;
    for(int i=0;i<n;i++) f[i].resize(k);
    for(int i=0;i<n;i++) if(!deg[i]) q.push(i);
    for(int i=0;i<k;i++) f[i][i]=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int v:g[u])
        {
            f[v]=f[v]+f[u]*(Rand()%(mod-1)+1);
            if(!--deg[v]) q.push(v);
        }
    }
    for(int i=k;i<n;i++)
    {
        insert({i,f[i]});
        static int tmp[K];int tt=0;
        for(int j=0;j<k;j++) if(b[j].fi) tmp[++tt]=b[j].fi;
        tmp[++tt]=k-1,tmp[0]=i;
        sort(tmp+1,tmp+tt+1,greater<int>());
        for(int j=0;j<tt;j++) ans[j]+=tmp[j]-tmp[j+1];
    }
    for(int i=0;i<=k;i++) printf("%lld\n",ans[i]);
    return 0;
}

Zbiory niezależne [A]

Poly 题,爬了。

posted @   Flying2018  阅读(366)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示