Potyczki Algorytmiczne 2021
Round 1
Oranżada [B]
题意
给定一个长度为 \(n\) 的序列 \(a\),每次操作可以交换相邻两个元素,问至少需要多少次操作才能使得前 \(k\) 个元素两两不同。
\(n\leq 5\times 10^5\)。
题解
容易发现,一个数字如果它前面已经出现过了,那么这个数字就没有用了。不妨把有用的数字记为 \(1\),没用的记为 \(0\),那么题意等价于每次交换相邻两个数字,使得 \(01\) 串前 \(k\) 个位置都是 \(1\)。贪心把前 \(k\) 个 \(1\) 放到前面即可。
复杂度 \(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]
题意
定义一次操作可以删除序列的一个区间,满足区间长度 \(\geq 2\) 且两端数字相等。定义一个序列是好的,当且仅当可以通过若干次操作把这个序列删空。
问所有长度为 \(n\),所有元素 \(\in[1,m]\) 的序列中有多少个序列是好的。对 \(10^9+7\) 取模。
\(n\leq 3000,m\leq 10^9\)。
题解
一直往容斥方面想,然后发现状态没法维护,直接寄了。
考虑换一个方向,考虑怎么判断一个序列是否合法。这个可以从前往后 dp,然后对于每个位置,如果它前面存在一个和它相同的位置 \(i\) 并且 \(dp_{i-1}=1\),那么这个前缀也是合法的。
反过来说,当前位置是否合法只和 \(j\in[1\sim i)\) 中满足 \(dp_{j-1}=1\) 的位置的不同颜色数有关。由于其中不包括 \(dp_i\),所以再额外记一维 \(dp_{i-1}\) 即可。
即用 \(f_{i,j,0/1}\) 表示前 \(i\) 个位置,满足 \(dp_{j-1}=1\) 的 \(a_j\) 本质不同数量有 \(j\) 种,\(dp_{i}=0/1\) 的方案数。转移枚举当前位置与上一个位置的 \(dp\) 值即可。
复杂度 \(O(n^2)\)。
代码
#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\) 个球排成一排,一开始有一些球是黑的。现在进行若干轮操作,每次你可以将一个没有被染黑的球染白,然后如果对于所有没有染色的球,如果它在一个黑色球旁边,那么它会被染黑。在所有球都被染色后,最小化黑色球个数。
\(n\leq 10^5\)。
题解
可以发现最小化黑色球数目等价于最大化无色球数目。把黑色球之间的区间拿出来,最优方案一定是舍弃一些区间,然后尽可能增加剩下区间中的无色球数目。
枚举两侧的区间是否要舍弃,中间的区间一定是优先舍弃小区间,直接贪心即可。复杂度 \(O(n)\)。
代码咕咕咕了。
Poborcy podatkowi [A]
题意
给定一棵树,边有边权(可能为负)。要求在树上选出若干条长度为 \(4\)(\(5\) 个点)的边不交路径,使得边权和最大。
\(n\leq 2\times 10^5\)。
题解
看到过好几次的题?考虑 dp,设 \(f_{i,j}\) 表示当前以 \(i\) 为根子树内,根所在链长度为 \(j\) 的边权和。考虑子树中 dp 需要让 \(1,3\) 匹配,\(2\) 两两匹配。然而 \(1,3\) 数量可能会很大,这可能会导致 \(O(n)\) 的额外复杂度。考虑将子树 shuffle 一下,根据霍夫丁不等式,此时最优解出现前缀 \(1\) 与 \(3\) 个数差超过 \(\sqrt n\) 的概率是 \(10^{-6}\) 左右,可以近似认为不会发生。
直接 dp 即可。复杂度 \(O(n\sqrt n)\)。
代码
#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=10^9+7\),给定一个长度为 \(n\) 的序列 \(a\),问把 \(a\) 分成若干区间的方案数,使得每个区间的和对 \(mod\) 取模后结果是偶数。\(n\leq 3\times 10^5\)。
题解
考虑 dp。显然有一个 \(O(n^2)\) 的区间 Dp。容易发现,\(s_i-s_j\) 对 \(mod\) 取模后的奇偶性,等于 \(s_i\) 取模后奇偶性异或 \(s_j\) 取模后奇偶性异或 \([s_i<s_j]\)。
直接对奇偶性分讨然后用树状数组优化。复杂度 \(O(n\log n)\)。
代码
#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\),要求构造一个序列 \(c\),\(c_i\) 等于 \(a_i\) 或者 \(b_i\),并且恰好有 \(k\) 个位置等于 \(a_i\),其余位置等于 \(b_i\)。最小化 \(c\) 的最大子段和。
\(n\leq 10^5\)。
题解
容易得到一个 \(O(n^2\log x)\) 的做法:首先二分答案 \(X\),然后设 \(f_{i,j}\) 表示前 \(i\) 个位置,有 \(j\) 个位置是 \(A\),前缀最长子段和 \(\leq X\) 情况下的最大后缀和最小值。容易写出转移式子:\(f_{i,j}\rightarrow f_{i+1,j}+B_i,f_{i,j}\rightarrow f_{i+1,j+1}+A_i\)。同时所有数字对 \(0\) 取 \(\max\)。
考虑将 \((j,f_{i,j})\) 画出,容易发现第一个转移式子相当于把图像向上平移了 \(B_i\),第二个转移式子等价于找到第一个斜率 \(>A_i-B_i\) 的位置,然后将后面的图像移动 \((1,A_i-B_i)\)。可以证明这个图像是下凸的。
考虑求出差分数组,显然差分数组是单调的,那么第二个转移式子等价于插入到不影响单调性的位置上。接下来对 \(0\) 取 \(\max\) 等价于将一段前缀的差分数组变成 \(0\)(需要特殊处理第一个位置),\(\leq X\) 等价于将一段后缀的函数删去。
最后如果图像中存在 \(k\) 位置就是有解,否则无解。
考虑如何构造。一种方式是直接用可持久化线段树记录前驱,但非常难写。实际上有一种非常巧妙的方式:注意到差分数组的每一个数字都是由一个位置的 \(A_i-B_i\) 贡献的。那么不妨记录这些位置,这样只需要将留到最后的差分数组上的这些位置取出就是构造。
复杂度 \(O(n\log n\log x)\)。
代码
#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)\times (m+1)\) 个街区,被 \(n\) 条横向道路和 \(m\) 条纵向道路分开。每个十字路口有一个红绿灯。红绿灯以不大于 \(8\) 的周期运作,如果为 \(0\) 表示这个时刻其左边的两个街区分别与右边的两个街区相连,否则表示其上方的两个街区分别与下方的两个街区相连。你可以在一个时刻内通过任意相连的街区。
\(q\) 次询问,每次问你在 \(t_i\) 时刻从 \((a_i,b_i)\) 到 \((c_i,d_i)\) 最早什么时刻可以到达。
题解
手模一下会发现大部分情况似乎所有街区都是连通的,除了某一排的红绿灯全部横向连接,或者某一列的红绿灯全部纵向连接。
任意时刻不会同时出现两种情况,所以可以把行列分开算。由于 \(q\) 很大,考虑离线分治,每次枚举中间行的通过时间对 \(k=3\times 5\times 7\times 8=840\) 取模的结果,左右可以分别贪心计算。
复杂度 \(O(nm+k(n+m)\log n+q\log n)\)。非常卡常,过不太去。
考虑换个思路。不妨假设当前处理的是行,那么可以构造一个 \(n\times 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\in[1,n]\),问有多少对点 \((u,v)\) 满足 \(u\neq v\) 且从 \(u\) 开始走一定可以在有限步数走到 \(v\)。
\(n\leq 2\times 10^5,m\leq 5\times 10^5\)。
题解
为了简化,记 \(u\leadsto v\) 表示从 \(u\) 出发一定能到 \(v\)。
首先考虑 \(k=n\) 的情况。显然有一个 \(O(n^2)\) 做法是:枚举终点 \(v\),显然 \(v\leadsto v\),如果一个点 \(u\) 所有出边对应点 \(v'\) 都有 \(v'\leadsto v\),那么 \(u\leadsto v\)。显然这样类似拓扑的过程是充要的。
考虑一个性质:如果 \(u\leadsto v,u\leadsto v'\),那么 \(v\leadsto v'\) 和 \(v'\leadsto v\) 至少有一个满足。并且显然 \(u\leadsto v,v\leadsto w\) 可以推出 \(u\leadsto w\)。所以可以将整张图划成若干子图,满足每个子图 \(G'\) 内都存在一个点 \(rt\in G'\),使得 \(\forall v\in G',v\leadsto rt\)。
分割子图的过程可以用启发式合并加拓扑在 \(O(m\log n)\) 内解决。
容易发现,如果 \((u,v)\) 在不同子图中,那么一定不存在 \(u\leadsto v\),所以可以对于每个子图分别处理。
这样的一个好处就是:对于每张子图 \(G'\),取 \(rt\) 为根,所有点都存在到根的路径,且去掉 \(rt\) 连出的边后,整个子图是一张 DAG。这样可以把一个点 \(u\) 开始走的路径分成从 \(u\) 开始到 \(rt\) 和从 \(rt\) 出发回到 \(rt\)。只要建出 \(G'\) 的除去 \(rt\) 出边外的支配树,上述过程都可以直接在支配树上解决。复杂度 \(O(m\log n)\)。
接下来考虑解决 \(k=1\cdots n\) 的部分。首先可以计算出一个点 \(u\) 到其支配点过程中可能到达的点编号最大值 \(w\),如果 \(w>k\),可以证明 \(u\) 就不存在支配点了(即不存在 \(v\) 使得 \(u\leadsto v\))。但是对于 \(rt\),这样做可能是不正确的:可能存在一个点编号非常大,但实际上在这个点之前存在一个基环上的点是必经点。
考虑直接找出这个环,然后以环上最大编号点作为根。这样就不会存在上述的问题了。
题意简化为:有一个基环内向树森林,对于每个 \(k\),求出有多少 \((x,y)\) 满足 \(x\) 到 \(y\) 最短路径上边权均 \(\leq k\)。考虑找到环上最大的边,那么在这条边断掉之前可以直接套用树的做法,这条边加上后可以看做一棵新的树。用 set
启发式合并即可做到 \(O(n\log^2 n)\),用其他类似的东西可以做到 \(O(n\log n)\)。
感觉非常难写。咕咕咕了。
Round 5
Autostrada [B]
题意
有 \(3\) 条道路,上面有若干辆 \(1\times 1\) 的车,通过一个 \(3\times L\) 的矩阵描述。第 \(i\) 条道路上的车子车速为 \(v_i\)。
你现在在最后一格,需要超过所有车。你的车子最快速度是 \(v_0\),并且可以在相邻道路之间任意切换,切换不需要时间但是需要对应位置没有车。你不一定需要在整数时间内切换。现在你希望知道你超过所有车的最小时间。
\(L\leq 2\times 10^5,v_3<v_2<v_1<v_0\leq 140\)。
题解
考虑以中间车道的车为基准,假设中间车道的车是不动的,相当于左侧的车在往前,右侧的车在往后。
容易发现,在任何一条车道,当前的车要么全速往前,要么与当前车道保持相对禁止。
考虑可能的策略。首先在中间车道如果前面是空着的一定全速往前,否则可以转到左右侧车道,或者等待左右侧车道的空位。
可以发现,无论在左侧车道还是右侧车道,只要中间车道是空着的,转移到中间车道一定不劣。同时对于左侧车道,还有一个附加的策略是一直跟在最前面的车后面,直到到达中间车道的下一个位置。
代码有点难写,先咕咕咕了。
Desant 2 [B]
题意
给定整数序列 \(a\),有 \(q\) 次询问,每次询问一个区间 \([l,r]\),问从中选出若干不交的长度为 \(k\) 的区间,最大化区间内部 \(a\) 之和。
题解
考虑将 \(a\) 列成一个 \(k\times \frac nk\) 的网格,那么 \(i\) 向 \(i+1\) 和 \(i+k\) 连边相当于网格中向上和向右连边(最顶上的点向下一列最底下的点连边)。
考虑根据 \(k\) 与 \(\sqrt n\) 的关系分讨。如果 \(k<\sqrt n\),那么直接按行分治。否则特别处理第一行的连边,然后按列分治。
复杂度 \(O(n\sqrt n\log n)\)。如果每次都取较大的一边分治,可以做到 \(O(n\sqrt n)\)。
代码
#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\in[0,k]\),输出有多少个区间 \([l,r]\),使得从 \([1,k]\) 开始到 \([l,r]\) 内的点结束的不相交路径条数恰好为 \(x\)。
\(n\leq 10^5,k\leq 50\)。
题解
其实和 这题 很像。
考虑构造一个 \(k\) 维向量,对于前 \(k\) 个点,令第 \(i\) 个点点权 \(f_i\) 是除了第 \(i\) 个位置为 \(1\),其余位置全部为 \(0\) 的向量。对于 \(k+1\sim n\) 的点,令第 \(i\) 个点点权 \(f_i\) 是满足 \(v_j=i\) 的边 \((u_j,v_j)\) 的 \(f_{u,j}x_j\) 之和。
容易证明,最后区间 \([l,r]\) 中极大线性无关组数量就是答案。证明考虑用类似 LGV 引理的思路,要判断是否存在满流只需要判断 \(|\{f_i|i\in[l,r]\}|\) 是否满秩即可,同样要判断流量大小等于矩阵秩的大小,也即极大线性无关组数量。
直接取 \(x_i\) 复杂度是无法接受的。根据经典套路,我们令 \(x_i\) 等于一个 \([0,P)\) 的随机值,然后求出乘积在模 \(P\) 意义下的结果。根据 Schwartza-Zippela 定理,这个随机值导致基变小的概率是 \(\frac 1P\)。
这样维护一个广义线性基。注意到这里我们需要时刻维护所有基中编号最大的元素,由于线性基满足拟阵性质,每次插入的向量 \(x\),当 \(x\) 需要减去线性基中某个元素时,如果那个元素对应编号小于 \(x\) 的编号,就交换它们。可以证明,这样总是在线性基中留下编号最大的元素。
这样直接从前往后扫,然后将线性基中元素排序后左端点在 \([a_i,a_{i+1})\) 的部分就是大小为 \(i\) 的线性基。
复杂度 \(O(mk+nk^2)\)。
代码
#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 题,爬了。

浙公网安备 33010602011771号