2022.9.27测试

一测 \(300pts\),原因 KMP 没学好……

T1:P4160 [SCOI2009]生日快乐(蓝)

T2:P4162 [SCOI2009]最长距离(蓝)

T3:P2657 [SCOI2009] windy 数(蓝)

T4:P3193 [HNOI2008]GT考试(紫)

T1:

由题意可知,每次切只会把一个蛋糕分成两半,所以不会存在弦图的情况,或是很混乱的情况,所有的情况切出来的蛋糕都是工整的。

那么,就可以利用分治思想,先预处理出来最后每个蛋糕面积,再每次将蛋糕横或竖切成两份,枚举每一份会包含多少个单位的最终蛋糕。

比如样例,我们可以先横着切,将蛋糕分为 \(2,3\) 份,那么相当于把两块切成了面积为 \(10,15\) 的蛋糕,而由于横着切,那么长不会改变,所以两块蛋糕变成 \(2\times 5\)\(3\times 5\)

一直分下去,直到一块蛋糕被分成一个单位的蛋糕,统计答案。

但由于这样不好统计答案,那么就二分答案,再进行验证,每次分到最后看是否满足比例小于验证值即可。

时间复杂度:\(O(\log(10^{11})\times n\times \log(n))\)

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,k;
double ans,s;
double eps=1e-7;
bool dfs(double x,double y,int num)
{
	if(num==1)
	{
		double t=x/y;
		if(t<1)t=1/t;
		if(t>ans)return false;
		return true;
	}
	for(int i=1;i<num;i++)
	{
		double yy=y-s/x*i;
		if(dfs(x,yy,num-i)&&dfs(x,y-yy,i))return true;
	}
	for(int i=1;i<num;i++)
	{
		double xx=x-s/y*i;
		if(dfs(xx,y,num-i)&&dfs(x-xx,y,i))return true;
	}
	return false;
}
int main()
{
	freopen("happy.in","r",stdin);
	freopen("happy.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	s=n*m*1.0/k;
	double l=1,r=10000;
	while(l<r-eps)
	{
		double mid=(l+r)/2;
		ans=mid;
		if(dfs(n,m,k))r=mid;
		else l=mid+eps;
	}
	ans=l;
	printf("%.6lf",ans);
	return 0;
}

T2:

向四周建图,赋上点权,对于每一个点跑一次最短路,最后判断两个点距离是否小于 \(T\),如果小于,更新欧几里得距离即可。

时间复杂度:\(O(n^4\times\log(n^4))\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
const int N=35;
int n,m,k,dis[N*N][N*N],vis[N*N][N*N];
int t[N][N];
struct node2
{
	int to,data;
};
vector<node2>a[N*N];
struct node
{
	int name,cnt;
};
priority_queue<node>q;
bool operator <(node fi,node se)
{
	return fi.cnt>se.cnt;
}
void dijstra(int beg)
{
	q.push((node){beg,dis[beg][beg]});
	while(!q.empty())
	{
		int x=q.top().name;
		q.pop();
		if(vis[beg][x])continue;
		vis[beg][x]=1;
		int len=a[x].size();
		for(int i=0;i<len;i++)
		{
			if(dis[beg][a[x][i].to]>dis[beg][x]+a[x][i].data)
			{
				dis[beg][a[x][i].to]=dis[beg][x]+a[x][i].data;
				q.push((node){a[x][i].to,dis[beg][a[x][i].to]});
			}
		}
	}
}
int main()
{
	freopen("dis.in","r",stdin);
	freopen("dis.out","w",stdout);
	memset(dis,0x3f,sizeof(dis));
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%1d",&t[i][j]),dis[(i-1)*m+j][(i-1)*m+j]=t[i][j];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(i>1)a[(i-1)*m+j].push_back((node2){(i-2)*m+j,t[i-1][j]});
			if(i<n)a[(i-1)*m+j].push_back((node2){i*m+j,t[i+1][j]});
			if(j>1)a[(i-1)*m+j].push_back((node2){(i-1)*m+j-1,t[i][j-1]});
			if(j<m)a[(i-1)*m+j].push_back((node2){(i-1)*m+j+1,t[i][j+1]});
		}
	}
	for(int i=1;i<=n*m;i++)dijstra(i);
	double ans=0;
	for(int i=1;i<=n*m;i++)
	{
		for(int j=1;j<=n*m;j++)
		{
			if(dis[i][j]<=k)
			{
				double y1=(i-1)%m+1,x1=(i-1)/m+1,y2=(j-1)%m+1,x2=(j-1)/m+1;
				ans=max(ans,sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));
			}
		}
	}
	printf("%.6lf",ans);
	return 0;
}

T3:

模板题。

\(dp_{i,j,k,l}\) 代表枚举到第 \(i\) 位数,上一位为 \(j\),前面的数是否抵达上界,是否为前导零。

如果有前导零或是枚举此位与上一位差的绝对值 \(\ge2\),即可继续递归,达到边界,即 \(i=0\) 时返回 \(1\)

时间复杂度:\(O(\log_{10}^2(n))\)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=15;
int n,dp[N][N][2][2],l,r,a[N];
inline int tabs(int x)
{
	return x>0?x:-x;
}
int dfs(int x,int num,bool flag,bool zero)
{
	if(x==0)return 1;
	if(dp[x][num][flag][zero]!=-1)return dp[x][num][flag][zero];
	int ed=9,sum=0;
	if(flag)ed=a[x];
	for(int i=0;i<=ed;i++)if(zero||tabs(num-i)>=2)sum+=dfs(x-1,i,flag&(i==a[x]),zero&(i==0));
	return dp[x][num][flag][zero]=sum;
}
int solve(int x)
{
	int cnt=0;
	memset(dp,-1,sizeof(dp));
	while(x)
	{
		a[++cnt]=x%10;
		x/=10;
	}
	int res=dfs(cnt,0,1,1);
	return res;
}
int main()
{
	freopen("windy.in","r",stdin);
	freopen("windy.out","w",stdout);
	scanf("%d%d",&l,&r);
	int ans=solve(r)-solve(l-1);
	printf("%d",ans);
	return 0;
}

T4:

首先容易想到数位 dp。

\(dp_{i,j}\) 代表枚举到第 \(i\) 位,可以匹配上冲突串的前 \(j\) 位的方案数,对于每一位与冲突串进行匹配,当匹配上后返回 \(0\),达到边界返回 \(1\)

匹配过程用 KMP 优化,若本位匹配不上,持续跳到上一个失配指针,如果匹配上或达到边界,则从这一位继续匹配。

复杂度:\(O(\log_{10}(n)\times m)\)

很明显过不了,考虑优化。

其实在每一位上做的事情比较重复,所以考虑是否能矩阵加速。

\(m_{i,j}\) 代表已经匹配到第 \(i\) 位,这位枚举数字为 \(j\),匹配后指针指向的位置。

利用 KMP 预处理出来数组 \(m\),可以发现,\(m\) 数组其实就是在处理 \(dp\) 数组的位置 \(j\),所以可以建一个 \(m\times 1\) 的初始矩阵,代表装载 \(dp_{0,0}\)\(dp_{0,m-1}\),那对于 \(m\) 数组的每一位,将其以 \((m_{i,j},i)\) 的形式压入转移矩阵,意思为 \(dp_{i,j}\) 枚举第 \(i\) 位时从 \(dp_{i-1,k}\) 转移过来的情况。

最后矩阵加速优化即可。

有点抽象,建议画图理解一下。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int M=25;
int n,m,mod,pre[M],mac[M][M];
char s[M];
struct matrix
{
	int x,y,a[M][M];
	void init()
	{
		for(int i=1;i<=21;i++)for(int j=1;j<=21;j++)a[i][j]=0;
	}
};
void init()
{
	for(int i=1;i<=m;i++)
	{
		for(int j='0';j<='9';j++)
		{
			int temp=i;
			while(temp>1&&s[temp]!=j)temp=pre[temp-1]+1;
			if(s[temp]==j)temp++;
			if(temp<=m)mac[temp][i]++;
		}
	}
}
matrix operator *(matrix fi,matrix se)
{
	matrix th;
	th.init();
	th.x=fi.x,th.y=se.y;
	for(int i=1;i<=fi.x;i++)
	{
		for(int j=1;j<=se.y;j++)
		{
			for(int k=1;k<=fi.y;k++)
			{
				th.a[i][j]+=fi.a[i][k]*se.a[k][j];
				th.a[i][j]%=mod;
			}
		}
	}
	return th;
}
matrix quick_pow(matrix x,int y)
{
	matrix num=x,sum;
	sum.init();
	sum.x=sum.y=m;
	for(int i=1;i<=m;i++)sum.a[i][i]=1;
	while(y)
	{
		if(y&1)sum=sum*num;
		num=num*num;
		y>>=1;
	}
	return sum;
}
int main()
{
	//freopen("GT.in","r",stdin);
	//freopen("GT.out","w",stdout);
	scanf("%d%d%d",&n,&m,&mod);
	scanf("%s",s+1);
	int j=1;
	for(int i=2;i<=m;i++)
	{
		while(j>1&&s[i]!=s[j])j=pre[j-1]+1;
		if(s[i]==s[j])j++;
		pre[i]=j-1;
	}
	matrix x,y;
	x.init(),y.init();
	x.x=x.y=m;
	y.x=m,y.y=1;
	y.a[1][1]=1;
	init();
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=m;j++)x.a[i][j]=mac[i][j];
	}
	x=quick_pow(x,n);
	y=x*y;
	int ans=0;
	for(int i=1;i<=m;i++)ans+=y.a[i][1],ans%=mod;
	printf("%d",ans);
	return 0;
}
posted @ 2023-02-24 13:53  Gmt丶Fu9ture  阅读(20)  评论(0)    收藏  举报