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;
}

浙公网安备 33010602011771号