Atcoder ABC388F Dangerous Sugoroku 题解 [ 蓝 ] [ 矩阵加速 ] [ 状压矩乘 ] [ 模拟 ]

Dangerous Sugoroku:赛时写了矩乘 T 飞了,受到 sunkuangzheng 大佬的启发才知道要状压矩乘。

暴力矩乘思路

直接像过河那样写模拟细节非常多,于是考虑像美食家一样的思路,利用矩阵分段加速。

定义 dpi 表示 i 能否到达,则有如下转移:

dpi=j=iBiAdpj

因为 A,B20,所以可用状态非常少,就可以用矩阵优化了。

那么很显然就把段拆一下,然后直接跑矩乘就好了。这个做法甚至连 m=0,li1=ri1 的细节都不用判。

代码如下,如果你真这么写了,会收获 TLE 和 WA 的好成绩!

下面这份暴力中 dpi 表示 dpi 的方案数,所以转移有所不同,本质还是和上面一样的。只要 dpi>0 就能到达。

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
struct mat{
	ull a[25][25];
	mat(){memset(a,0,sizeof(a));}
	mat operator*(const mat &x)const{
		mat res;
		for(int i=0;i<=20;i++)
		{
			for(int k=0;k<=20;k++)
			{
				ull l=a[i][k];
				for(int j=0;j<=20;j++)
				{
					res.a[i][j]=(res.a[i][j]+l*x.a[k][j]);
				}
			}
		}
		return res;
	}
}s,dp1,dp2;
mat qpow(mat x,ll k)
{
	mat res;
	for(int i=0;i<=20;i++)res.a[i][i]=1;
	while(k>0)
	{
		if(k&1)res=res*x;
		x=x*x;
		k>>=1;
	}
	return res;
}
ll n,m,a,b,l[200005],r[200005];
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>a>>b;
    s.a[1][20]=1;
    for(int i=1;i<=19;i++)dp1.a[i+1][i]=dp2.a[i+1][i]=1;
    for(int i=20-a+1;i>=20-b+1;i--)dp1.a[i][20]=1;
    r[0]=1;
    for(int i=1;i<=m;i++)
    {
        cin>>l[i]>>r[i];
        if(r[i]-l[i]+1>=21||l[i]==1)
        {
            cout<<"No";
            return 0;
        }
        if(l[i]-r[i-1]-1>0)s=s*qpow(dp1,l[i]-r[i-1]-1);
        if(r[i]-l[i]+1>0)s=s*qpow(dp2,r[i]-l[i]+1);
    }
    if(n+1-r[m]-1>0)s=s*qpow(dp1,n+1-r[m]-1);
    if(s.a[1][20])cout<<"Yes";
    else cout<<"No";
    return 0;
}

原因是复杂度为 O(m×203logn),常数又大,无法通过。

优化矩乘

因为状态中只有 01,每行每列的数又只有 20 个,考虑用状压与位运算去掉一个 20

我们先看暴力的矩乘代码,再来考虑如何优化它。

struct mat{
	ull a[25][25];
	mat(){memset(a,0,sizeof(a));}
	mat operator*(const mat &x)const{
		mat res;
		for(int i=0;i<=20;i++)
		{
			for(int k=0;k<=20;k++)
			{
				for(int j=0;j<=20;j++)
				{
					res.a[i][j]=(res.a[i][j]|(a[i][k]&x.a[k][j]));
				}
			}
		}
		return res;
	}
}

可以发现,当 ai,k=1 时,resi,j 取决于 xi,j 是否为 1

因此,就可以省去循环 j,当 ai,k=1 时,直接让 resiresixk 即可。

时间复杂度 O(m×202logn),可以通过。

代码如下:

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
struct mat{
	ull a[25];
	mat(){memset(a,0,sizeof(a));}
	mat operator*(const mat &x)const{
		mat res;
		for(int i=0;i<=20;i++)
		{
			for(int k=0;k<=20;k++)
			{
				if((a[i]>>k)&1)res.a[i]=(res.a[i]|x.a[k]);
			}
		}
		return res;
	}
}s,dp1,dp2;
mat qpow(mat x,ll k)
{
	mat res;
	for(int i=0;i<=20;i++)res.a[i]=(1<<i);
	while(k>0)
	{
		if(k&1)res=res*x;
		x=x*x;
		k>>=1;
	}
	return res;
}
ll n,m,a,b,l[200005],r[200005];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>a>>b;
    s.a[1]=(1<<20);
    for(int i=1;i<=19;i++)dp1.a[i+1]=dp2.a[i+1]=(1<<i);
    for(int i=20-a+1;i>=20-b+1;i--)dp1.a[i]|=(1<<20);
    r[0]=1;
    for(int i=1;i<=m;i++)
    {
        cin>>l[i]>>r[i];
        if(r[i]-l[i]+1>=21||l[i]==1)
        {
            cout<<"No";
            return 0;
        }
        if(l[i]-r[i-1]-1>0)s=s*qpow(dp1,l[i]-r[i-1]-1);
        if(r[i]-l[i]+1>0)s=s*qpow(dp2,r[i]-l[i]+1);
    }
    if(n+1-r[m]-1>0)s=s*qpow(dp1,n+1-r[m]-1);
    if((s.a[1]>>20)&1)cout<<"Yes";
    else cout<<"No";
    return 0;
}
posted @   KS_Fszha  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示