2022“杭电杯”中国大学生算法设计超级联赛(4)1005 (Problem 7178) Link with Level Editor II(矩阵乘法+双指针)
题目链接:Link with Level Editor II
题意:
给定
n
n
n个图,每个图有
m
m
m个点,
l
l
l条有向边,选出一段最长的连续的世界,使得满足:
初始在第一个图的点
1
1
1,每次操作可以在当前图选择一条和当前点相连的边走向另一个点或者呆在该点,每次操作后都会从该世界传送到下一个世界对应点,在最后一个世界时恰好到达点
m
m
m的方案数不超过
k
k
k种.
题解:
发现 m m m很小,我们可以使用矩阵来储存图,这样我们就可以通过矩阵乘法来求得一段区间内从点 1 1 1到 m m m的路径数(离散数学里的,不知道的可以去先学一下,这里不再缀叙),知道了一段区间内的方案数,显然我们可以利用双指针来求最长合法区间,但是发现在确定 r r r后收缩 l l l的过程中的除的部分很麻烦,,我们可以再开一个矩阵数组 b b b,和一个分界线 l i m lim lim, b [ i ] b[i] b[i]表示的即为 a [ i ] a[i] a[i]到 a [ l i m ] a[lim] a[lim]的累乘和,用 b a s e base base表示 a [ l i m + 1 ] a[lim+1] a[lim+1]到 a [ r ] a[r] a[r]的累乘和,这样每次 a [ l ] ∗ b a s e a[l]*base a[l]∗base即为 a [ l ] a[l] a[l]到 a [ r ] a[r] a[r]的累乘和,这时判断下方案数是否超过k来确定双指针走向即可。
具体实现和细节参考代码注释:
ll n,m,k;
struct square{
ll v[22][22];
square(int x=0){
memset(v,0,sizeof(v));
for(int i=1;i<=m;i++){
v[i][i]=x;
}
}
square operator *(square q){
square ans;
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
for(int k=1;k<=m;k++){
ans.v[i][j]=ans.v[i][j]+v[i][k]*q.v[k][j];
if(ans.v[i][j]>1e9){//多次累乘可能越界,过大就处理一下
ans.v[i][j]=1e9;
}
}
}
}
return ans;
}
void print(){
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
cout<<v[i][j]<<" ";
}
cout<<endl;
}
}
};
square a[5010];
square b[5010];
bool check(square x,square y){//判断当前方案数是否超出k
ll ans=0;
for(int i=1;i<=m;i++){
ans+=x.v[1][i]*y.v[i][m];
if(ans>k){
return 0;
}
}
return 1;
}
int main(){
/*cout<<setiosflags(ios::fixed)<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(不含整数部分)*/
/*cout<<setprecision(8)<<ans<<endl;//输出ans(float)格式控制为8位小数(含整数部分)*/
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);//同步流
int t;
cin>>t;
while(t--){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
int l;
cin>>l;
a[i]=1;
for(int j=1;j<=l;j++){
int u,v;
cin>>u>>v;
a[i].v[u][v]=1;
}
}
int ans=0;
b[0].v[1][m]=inf;//初始化
square base=1;
for(int l=0,lim=0,r=1;r<=n;r++){
base=base*a[r];//储存a[lim+1]-a[r]的累乘和
while(!check(b[l],base)){//方案数过多,开始缩小区间,查找当前人对应的l
//根据base,和b[i]的含义,b[l]*base即为a[l]-a[r]的累乘和
l++;
if(l>lim){//l超过lim了,此时lim后面的b还没有定义,需要更新lim和对应的b
base=1;
b[r]=a[r];//b[i]储存的是a[i]-a[lim]间的累乘和,
for(int i=r-1;i>lim;i--){
b[i]=a[i]*b[i+1];
}
lim=r;
}
}
ans=max(ans,r-l+1);//更新答案
}
cout<<ans<<endl;
}
return 0;
}