模拟86 考试总结
状态有所回升
考试经过
开题不很友好,仔细看发现T1不难,半小时胡了个\(26n\)dp过大样例走人了,T2没看懂转做T3,想了一波发现就是搜,于是直接莽。后来多了组样例一看WA了,原来剪枝出毛病了,改了就没事了,然后大力手模T2样例,发现是扶墙,于是拆点大力开搜,写了快2h最后过了大样例,发现大样例0.8十分虚,T4根本不会于是想把T2的map
优化成unordered
,最后没写完
100+100+0+0=200,发现T3少特判爆零了,他每个包里都有\(1,1\)就离谱
T1.特殊字符串
处理出每两个字符的贡献,然后来dp
\(f_i\)表示以\(i\)结尾的字符最大贡献,枚举前面是啥转移,注意转移的字符必须已经出现过
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
int v[30][30],n,m;
int f[30];bool p[30];
char s[N],c[5];
signed main()
{
freopen("shiki.in","r",stdin);
freopen("shiki.out","w",stdout);
cin>>n;scanf("%s",s+1);cin>>m;
for(int i=1;i<=m;i++)
{
scanf("%s",c+1);int p1=c[1]-'a'+1;
scanf("%s",c+1);int p2=c[1]-'a'+1;
int x;scanf("%lld",&x);
v[p1][p2]+=x;
}
for(int i=1;i<=n;i++)
{
int x=s[i]-'a'+1;int an=0;
for(int j=1;j<=26;j++)
if(p[j])an=max(an,f[j]+v[j][x]);
f[x]=max(f[x],an);p[x]=1;
}
int ans=0;
for(int i=1;i<=26;i++)ans=max(f[i],ans);
cout<<ans<<endl;
return 0;
}
T2.宝可梦
发现是右手扶着墙走,那么他的路线是固定的,只要求出来他是怎么走的就行了
由于可能多次经过一个点,所以不能简单的记录经过时间,考虑拆点,把一个点加上他的方向作为状态,这个方向必须能走到,就是前边没有障碍
然后把所有状态记下来开始搜,然后判出来下一步往哪个方向走,标记一下经过顺序
他一定会最后走出若干个环,把所有状态不重不漏覆盖一遍
询问起点方向确定,枚举终点方向,要求合法且与起点状态在一个环里,然后更新答案
#include <bits/stdc++.h>
using namespace std;
const int N=100050;
char s[N],c[3];int n,m,q,tot;
vector <bool> p[N];
int p1[4]={-1,0,1,0},p2[4]={0,-1,0,1};
pair<pair<int,int>,int> rk[20*N];
bool v[20*N];int sum[20*N],pp,f[20*N];
int ed[N];
map <pair<pair<int,int>,int>,int> mp;
inline int gan(int x,int y,int op)
{
assert(mp.find(make_pair(make_pair(x,y),op))!=mp.end());
return mp[make_pair(make_pair(x,y),op)];
}
void dfs(int id,int ff,int t)
{
while(!v[id])
{
if(v[id]){ed[ff]=t-1;return;}
f[id]=ff;sum[id]=t;v[id]=1;
int x=rk[id].first.first,y=rk[id].first.second;
int op=rk[id].second,xx=x+p1[op],yy=y+p2[op];
int op1=(op-1+4)%4,op2=(op+1)%4;
if(p[xx+p1[op1]][yy+p2[op1]])id=gan(xx,yy,op1),t++;
else if(p[xx+p1[op]][yy+p2[op]])id=gan(xx,yy,op),t++;
else if(p[xx+p1[op2]][yy+p2[op2]])id=gan(xx,yy,op2),t++;
else id=gan(xx,yy,(op+2)%4),t++;
}
ed[ff]=t-1;
}
signed main()
{
freopen("pokemon.in","r",stdin);
freopen("pokemon.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=m+1;i++)p[0].push_back(0);
for(int i=1;i<=n;i++)
{
scanf("%s",s+1);
p[i].push_back(0);
for(int j=1;j<=m;j++)
{
if(s[j]=='X')p[i].push_back(0);
else p[i].push_back(1);
}
p[i].push_back(0);
}
for(int i=1;i<=m+1;i++)p[n+1].push_back(0);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
{
if(!p[i][j])continue;
for(int k=0;k<=3;k++)
{
int x=i+p1[k],y=j+p2[k];
if(p[x][y])mp[make_pair(make_pair(i,j),k)]=++tot,rk[tot]=make_pair(make_pair(i,j),k);
}
}
for(int i=1;i<=tot;i++)if(!v[i])dfs(i,++pp,1);
cin>>q;
for(int i=1;i<=q;i++)
{
int x,y,xx,yy;scanf("%d%d%d%d",&x,&y,&xx,&yy);
scanf("%s",c+1);int opt;
if(c[1]=='U')opt=0;if(c[1]=='L')opt=1;
if(c[1]=='D')opt=2;if(c[1]=='R')opt=3;
int id=gan(x,y,opt),ans=1e9;
int pid=f[id],st=sum[id];
for(int j=0;j<=3;j++)
{
if(mp.find(make_pair(make_pair(xx,yy),j))==mp.end())continue;
int idd=mp[make_pair(make_pair(xx,yy),j)];
if(f[idd]!=pid)continue;int ss=sum[idd];
if(st<=ss)ans=min(ans,ss-st);
else ans=min(ans,ed[pid]-(st-ss));
}
printf("%d\n",ans);
}
return 0;
}
常数委实较大,有点淦
T3.矩阵
从一个点开始bfs,每次最多搜\(log\)层,而状态数是\(log^2\)的,于是\(nlog^2\)完全可过
一定记得特判!小心爆零!
#include <bits/stdc++.h>
using namespace std;
#define mp make_pair
#define pr pair<int,int>
const int N=40050;
vector <int> a[N];
int n,m,ans=1;
inline void die(){puts("-1");exit(0);}
queue <pair<pr,int> >q;
int p1[4]={0,-1,1,0},p2[4]={-1,0,0,1};
bitset <N> v[N];
inline void bfs(int px,int py,int p)
{
q.push(mp(mp(px,py),1));v[px][py]=1;
while(q.size())
{
int xx=q.front().first.first,yy=q.front().first.second;
int w=q.front().second;ans=max(ans,w);q.pop();v[xx][yy]=0;
for(int i=0;i<=3;i++)
{
int x=xx+p1[i],y=yy+p2[i];
if(a[x][y]==a[xx][yy]*p&&v[x][y]==0)q.push(mp(mp(x,y),w+1)),v[x][y]=1;
}
}
}
signed main()
{
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
cin>>n>>m;int ma=0;
for(int j=0;j<=m+1;j++)a[0].push_back(0);
for(int i=1;i<=n;i++)
{
a[i].push_back(0);
for(int j=1;j<=m;j++)
{
int x;scanf("%d",&x);
a[i].push_back(x);ma=max(ma,x);
}
a[i].push_back(0);
}
for(int j=0;j<=m+1;j++)a[n+1].push_back(0);
if(n==1&&m==1){cout<<1<<endl;return 0;}
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
{
int x=a[i][j];
if(a[i-1][j]==x||a[i+1][j]==x||a[i][j+1]==x||a[i][j-1]==x)
die();
}
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
{
int x=a[i][j];
for(int p=0;p<=3;p++)
{
int y=a[i+p1[p]][j+p2[p]];
if(y!=0&&y%x==0)
{
int p=y/x;
bfs(i,j,y/x);
}
}
}
cout<<ans<<endl;
return 0;
}
T4.乘法
毒瘤,咕
首先要明白最后16位怎么取,如果不考虑后缀0的话,就是直接\(ull\)自然溢出,原理就是对\(2^{64}\)取模,算一下就知道
然而这个题恶心就恶心在你根本想不出一条连续的思路,需要多步问题转化,而每部分之间基本没啥关系
假如我们先不考虑复杂度,如果直接算就会遇到后缀0的问题,发现只有乘积有16的时候才会出现后缀0,所以可以奇偶分治
先算出来\(1-n\)内所有奇数的积,然后偶数全部除以2,接下来就是一个递归的子问题,统计一下除了多少个2,最后再将2的个数模4乘回去,取模消掉的那一部分就是没有用的后缀0
当然分16的倍数讨论也是有正确性的,但是显然奇数偶数性质更多,方便下一步转化
那么我们就需要计算\(\prod_{i=1}^{(n-1)/2}{2i+1}\),这里是第一步转化
然后思考这个东西怎么算,注意我们的性质是对\(2^{64}\)取模,这恰恰是突破口
利用小学数学把多项式展开,那么每一项都是若干个\(2i\)和1的积,当2超过63个时会溢出,一定没贡献
那么柿子就变成了\(\sum_{i=0}^{\min(63,n)}f_{n,i}\times 2^{i}\)(这里默认枚举上界是\(n\)),其中\(f_{n,i}\)表示从1到\(n\)选择\(i\)个数相乘,所有方案的和,这里是第二步转化
接下来就比较晦涩了,先说结论,这个\(f_{n,m}\)答案就是第一类斯特林\({n+1} \brack {n-m+1}\)
理解方式大体有两种,一种是根据原始定义,导出递推式,然后利用其与第一类斯特林数递推式的相似性进行变形,具体可见fengwu的博客
另一种是将原始贡献式拆开,从结果导向斯特林数的定义出发,利用其贡献过程与斯特林数在二维平面上的转移路径实际含义相似性进行类比解释,具体可见ICEY的博客
都比较神仙,感觉这步是思维瓶颈所在,但是大佬说的都比我清楚,感觉理解也更为到位
现在我们的问题就变成了把这个斯特林求出来,这是第三步转化
想怎么求,直接求肯定暴毙,发现一个事情就是这里的\(m\)很小,利用实际定义,在\(n+1\)个数中排成\(n-m+1\)个环,实际上这里绝大多数的环都是单个点,所以我们尝试只计算不是单个点的方案数,最后再把他拼起来
设\(dp_{i,j}\)表示在\(i\)个数中构成\(j\)个长度大于等于2的环的方案数,枚举最后一个环的大小进行转移,那么方程就是
由于要固定一个点所以-1,阶乘是排一个环的方案数
考虑拼答案,枚举有多少个大于1的环即可,就是\({n \brack m}=\sum_{i=0}^{\min(m,n-m)}\dbinom{n}{n-m+i}\times dp_{n-m+i,i}\)
到这里基本就完了,全部过程逆着做一遍即可,dp提前预处理好
一个小问题就是大数的组合数,\(C_n^m\)直接\(O(m)\)算即可,由于对\(2^{64}\)取模,先把下面的2提出来,根据欧拉定理除以一个奇数等价的逆元就是\(k^{2^{63}-1}\),快速幂就行,这一步根据实现为\(log\)或\(log^2\)不等,再看是否预处理总复杂度应该为3-4个\(log\),复杂度基本不是问题
考场不做是明智的
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N=80;
int c[2*N][2*N],jc[2*N],f[2*N][2*N],cnt;
inline int ksm(int x,int y)
{
int s=1;
for(;y;y>>=1)
{
if(y&1)s*=x;
x*=x;
}
return s;
}
inline int getny(int x)
{
assert(x&1);
return ksm(x,(((int)1)<<63)-1);
}
int inv[2*N],bit[2*N],p[2*N];
inline void pre()
{
for(int i=1;i<=150;i++)
{
int x=i;
while(!(x&1))x>>=1,bit[i]++;
inv[i]=getny(x);
}
}
inline int C(int n,int m)
{
if(n<m)return 0;
if(!m)return 1;
int s=1,ny=1,s1=0,s2=0;
for(int i=n;i>=n-m+1;i--)
{
int x=i;
while(!(x&1))s1++,x>>=1;
s*=x;
}
for(int i=1;i<=m;i++)ny*=inv[i],s2+=bit[i];
assert(s1>=s2);
int ans=s*ny;s1=(s1-s2);
return ans*(ksm(2,s1));
}
inline int ga(int n,int m)
{
int ans=0;
for(int i=0;i<=min(n-m,m);i++)
ans+=p[n-m+i]*f[n-m+i][i];
return ans;
}
inline int gan(int x)
{
x=(x-1)>>1;int ans=0;
for(int i=0;i<=min((int)150,x+1);i++)p[i]=C(x+1,i);
for(int i=0;i<=min((int)63,x+1);i++)
ans+=ga(x+1,x+1-i)*(((int)1)<<i);
return ans;
}
inline int solve(int x)
{
if(x==1)return(int)1;
return cnt+=(x>>1),solve(x>>1)*gan(x);
}
signed main()
{
freopen("multiplication.in","r",stdin);
freopen("multiplication.out","w",stdout);
c[0][0]=jc[0]=1;
for(int i=1;i<=150;i++)
{
c[i][0]=1;jc[i]=jc[i-1]*i;
for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
}
f[0][0]=1;pre();
for(int i=1;i<=150;i++)for(int j=0;j<=i;j++)
for(int k=2;k<=i;k++)f[i][j]+=c[i-1][k-1]*jc[k-1]*f[i-k][j-1];
int t;cin>>t;
while(t--)
{
int x;cin>>x;cnt=0;
int ans=solve(x);
ans*=(1<<(cnt%4));
printf("%llX\n",ans);
}
return 0;
}
考试总结
题目比较简单,但还是挂了分,细节处理还是不到位
如果每一道都做的话时间不一定太够,所以尽量让自己全神贯注吧