2023/2/23模拟赛
本来还是会打一百多分的。。。。但是又是因为空间问题挂了五十几分,之后写题时要重视空间这个问题啊。
\(T1\)
关于这个题,从中学到的最重要的东西就是分层图的应用。感觉这个可以参考一下这个。
个人认为,分层图就是根据点/边的不同性质将其分类,建上好几个一样的图,再在不同的图之间连上相应的边,使得图之间不相互干扰,达到一种分类讨论的效果。
那么放到这个题里,可以根据计时器的三个状态来将点分类。假设走到点 \(i\) 时计时器为 \(k\),那么再走一条边,计时器就会变为 \(k + 1 \mod 3\),建好分层图后,我们只需判断点 \(i,0\) 能否到达 \(i,1\) 即可。用 \(tarjan\) 来找强连通分量即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e6;
int head[MAXN + 5],belong[MAXN + 5],n,m,tot,low[MAXN + 5],dfn[MAXN + 5],tim,s[MAXN + 5],cnt;
bool vis[MAXN + 5];
struct EDGE{
int u,v,next;
}edge[MAXN + 5];
void add(int u,int v){
++tot;
edge[tot].u = u;
edge[tot].v = v;
edge[tot].next = head[u];
head[u] = tot;
}
void tarjan(int u){
s[++s[0]] = u;
vis[u] = 1;
low[u] = dfn[u] = ++tim;
for(int i = head[u]; i; i = edge[i].next){
int v = edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(vis[v])low[u] = min(low[u],dfn[v]);
}
if(dfn[u] == low[u]){
++cnt;
while(s[s[0] + 1] != u){
int v = s[s[0]];
belong[v] = cnt;
s[0]--;
vis[v] = 0;
}
}
}
int main(){
freopen("circle.in","r",stdin);
freopen("circle.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i = 1; i <= m; i++){
int u,v;
scanf("%d%d",&u,&v);
add(3 * u,3 * v - 1),add(3 * u - 1,3 * v - 2),add(3 * u - 2,3 * v);//走到u时计时器为0与走到v时计时器为1相连,走到u时计时器为1与走到v时计时器为2相连,以此类推
}
for(int i = 1; i <= 3 * n; i++){
if(!dfn[i])tarjan(i);
}
int ans = 0;
for(int i = 1; i <= n; i++){
if(belong[i * 3] == belong[i * 3 - 1])++ans;
}
cout << ans;
}
\(T2\)
期望 \(DP\)……不会……留待以后再思。
点击查看代码
#include<algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long LL;
const int N=55,M=9,Mod=258280327;
int g[N][N][M][1<<M],f[N][M],s[N][N][M],C[N][N],n,m,pw2[N*N];
int Pow(int a,int b)
{
int t=1;
while(b)
{
if(b&1)
t=t*(LL)a%Mod;
a=a*(LL)a%Mod;b>>=1;
}
return t;
}
int dfs(int a,int b,int m,int d)
{
if(a>b)
swap(a,b);
if(a==0||d<=0)
return pw2[(a+b)*m];
if(d>=(1<<m))
return 0;
int &sum=g[a][b][m][d];
if(sum!=-1)
return sum;
sum=0;
for(int i=0;i<=a;i++)
for(int j=0;j<=b;j++)
if((i==0&&j==b)||(i==a&&j==0))
sum=(sum+dfs(a,b,m-1,d-(1<<m-1)))%Mod;
else
sum=(sum+(LL)dfs(i,j,m-1,d)*dfs(a-i,b-j,m-1,d)%Mod*C[a][i]%Mod*C[b][j])%Mod;
return sum;
}
int work(int a,int b,int m)
{
if(m<=0)
return 0;
if(a>b)
swap(a,b);
int &sum=s[a][b][m];
if(sum!=-1)
return sum;
sum=0;
for(int i=1;i<(1<<m);i++)
sum=(sum+dfs(a,b,m,i))%Mod;
return sum;
}
int solve(int n,int m)
{
if(m<=0||n<=1)
return 0;
int &sum=f[n][m];
if(sum!=-1)
return sum;
sum=2*solve(n,m-1)%Mod;
for(int i=1;i<n;i++)
sum=(sum+(solve(i,m-1)*(LL)pw2[(n-i)*(m-1)]+solve(n-i,m-1)*(LL)pw2[i*(m-1)]+work(i,n-i,m-1)+(1<<m-1)*(LL)pw2[n*(m-1)])%Mod*C[n][i])%Mod;
return sum;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("young.in","r",stdin);
freopen("young.out","w",stdout);
#endif
memset(g,-1,sizeof g);memset(f,-1,sizeof f);memset(s,-1,sizeof s);
cin>>n>>m;
pw2[0]=1;
for(int i=1;i<=n*m;i++)
pw2[i]=pw2[i-1]*2%Mod;
for(int i=0;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%Mod;
}
cout<<solve(n,m)*(LL)Pow(Pow(1<<m,n),Mod-2)%Mod<<endl;
return 0;
}
\(T3\)
正解是莫比乌斯反演\(+\)杜教筛,杜教筛也不太会,今天试试能不能看懂前 \(60%\) 做法。
点击查看代码
#include<algorithm>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vector>
#include <queue>
#include <map>
#include <set>
using namespace std;
typedef long long LL;
const int N=11000000,Mod=258280327;
LL n;
int p[N/10],tot,m,f[N],Num;
char mu[N],P[N];
void sieve(int n)
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!P[i])
p[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*p[j]<=n;j++)
{
int k=i*p[j];
P[k]=1;
if(i%p[j]==0)
{
mu[k]=0;break;
}
mu[k]=-mu[i];
}
}
for(int i=1;i<=n;i++)
f[i]=(f[i-1]+mu[i]*i)%Mod;
}
map<LL,int> F;
inline int sum2(LL n)
{
n%=Mod;
return (n*(n+1)>>1)%Mod;
}
inline int solve(LL x)
{
if(x<=m)
return f[x];
else
if(F.count(x))
return F[x];
int S=1,las=1,now;
for(LL l=2,r;l<=x;)
{
LL k=x/l;
r=x/k,now=sum2(r),S=(S-(now-las)*(LL)(k<=m?f[k]:F[k]))%Mod,l=r+1,las=now;
}
if(S<0)
S+=Mod;
return F[x]=S;
}
inline int Pow(LL x,LL y)
{
x%=Mod;int s=1;
while(y)
{
if(y&1)
s=s*x%Mod;
x=x*x%Mod;y>>=1;
}
return s;
}
inline int sum(LL n)
{
static LL Ny=Pow(9,Mod-2);
return (n%Mod*Pow(10,n+1)%Mod-(Pow(10,n+1)-10)*Ny%Mod)*Ny%Mod;
}
void solve()
{
m=min(10000000ll,n);
sieve(m);
LL Ans=0,las=0,now=0;
for(LL l=1,r;l<=n;)
r=n/(n/l),now=solve(r),Ans=(Ans+(now-las)*sum(n/l))%Mod,l=r+1,las=now;
cout<<(Ans+Mod)%Mod<<endl;
}
int main()
{
freopen("simple.in","r",stdin);
freopen("simple.out","w",stdout);
scanf("%lld",&n);
solve();
return 0;
}
\(T4\)
这个题可以类比于经典的高楼摔鸡蛋问题,只不过数据范围加强了。
\(30\%\) 做法:
设 \(dp[i][j]\) 为当有 \(i\) 个鸡蛋,楼层为 \(j\) 时最少多少次才能判断出哪一个楼层掉下来会摔碎。方程为:
\[dp[k][n] = \min(\max{dp[k - 1][n - 1],dp[k][n - j]} + 1)
\]
$50$ 做法:
对 \(k = 1\) 和 \(k = 2\) 进行特判。
\(80\%\) 做法:
换一个思路 \(dp\),设 \(dp[i][j]\) 为 \(j\) 个鸡蛋实验 \(i\) 次最高能探测到的楼层,转移方程为:
\[dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] + 1
\]
加上神奇的优化:\(dp[i][j] = \sum_{x = 1}^j {j \choose x}\),这个由数学归纳法证
时间复杂度 \(O(Tk\log n)\)
\(100\%\) 做法:
发现当 \(k = 1\) 时和 \(k = 2\) 时答案会很大,对这两种情况单独处理,其他情况 \(dp\) 即可,时间复杂度 \(O(T\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5;
const int INF = 1000000000000000000;
int T,n,k,dp[MAXN + 5][100];
bool check(int x){
if(x <= MAXN)return dp[x][k] >= n;
int now = x,s = x;
for(int i = 2; i <= k; i++){
if(now * (long double)(x - i + 1) / i >= 1e18)return 1;
now = now * (x - i + 1) / i;
s = s + now;
if(s >= n)return 1;
}
return s >= n;
}
signed main(){
freopen("naive.in","r",stdin);
freopen("naive.out","w",stdout);
for(int i = 1; i <= MAXN; i++){
for(int j = 1; j <= 64; j++){
dp[i][j] = min(INF,1 + dp[i - 1][j] + dp[i - 1][j - 1]);
}
}
scanf("%lld",&T);
while(T--){
scanf("%lld%lld",&n,&k);
int l = 0,r = n - 1;
r = INF - 1;
while(l + 1 < r){
int mid = (l + r) / 2;
if(check(mid))r = mid;
else l = mid;
}
cout << r << "\n";
}
}