Loading [MathJax]/jax/output/CommonHTML/autoload/mtable.js

模拟赛总结

2024.2.6 【寒假集训】20240206测试

T1 珠子

看来是关于双指针的神秘东西。

T2 数组

这个题,我没考虑到的是要保留全部的 x,yx,y 操作信息,以及上一次 AA 操作的时间等等。

代码(参考 lcy):

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int t1[500001],t2[500001];
int lst[50000100];
int pos,dx;
signed main()
{
cin>>n>>m;
int su=(n+1)*n/2;
pos=0,t1[0]=1,t2[0]=0;
// for(int i=1;i<=n;i++)lst[i]=i;
for(register int i=1;i<=m;++i)
{
char c=getchar();
int x,y;cin>>x>>y;
t1[i]=x,t2[i]=y;
if(c=='A')
{
pos=i,dx=0;
printf("%lld\n",x*su+n*y);
}
else
{
if(lst[t1[i]]<=pos)
{
dx+=t2[i]-(t1[i]*t1[pos]+t2[pos]);
lst[t1[i]]=i;
}
else
{
int last=lst[t1[i]];
dx-=t2[last]-(t1[i]*t1[pos]+t2[pos]);
dx+=t2[i]-(t1[i]*t1[pos]+t2[pos]);
lst[t1[i]]=i;
}
printf("%lld\n",t1[pos]*su+t2[pos]*n+dx);
}
}
return 0;
}

T3 幸运区间

题意:在序列 aa 中,求出所有子序列 bb 中, gcd(b)=1gcd(b)=1 的个数。

nx=1ny=x[gcd(xy)==1]nx=1ny=x[gcd(xy)==1]

( 表示从 xxyy 的所有元素)

考虑画出一个表示所有子序列的 gcdgcd 的三角形矩阵:

1
1 1
1 1 1
1 1 1 1
1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
5 1 4 1 3 1 2 1 1
5 5 4 4 3 3 2 2 1 1

我们可以看到,从最下面开始,对于每个左端点确定的区间,只要在右端点为 rr 的时候 gcd=1gcd=1 ,那么从[l,r][l,r][l,n][l,n],所有区间的 gcd=1gcd=1

有一个定理:只要一个序列的子序列 gcd=1gcd=1,那么这个序列 gcd=1gcd=1

所以,我们需要实现两个东西:

  1. 查找区间 [l,r][l,r]gcdgcd

  2. 找到对于一个 l[1,n]l[1,n]rr 最小并且 gcd=1gcd=1 的子序列,统计答案 ans+=n-r+1

对于查找,由上面的定理可知, gcdgcd 具有传递性,因此我们可以构建一棵线段树来实现此操作。

tr[now].gcd=__gcd(tr[lid].gcd,tr[rid].gcd);

对于找到一个 ll、满足条件的最小的 rr,我们考虑直接 for 循环去找(区间伸缩)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[200100];
class emw{
public:
#define lid now<<1
#define rid now<<1|1
void build(int now,int l,int r)
{
if(l==r){
tr[now].g=a[l];return ;
}
int mid=(l+r)>>1;
build(lid,l,mid),build(rid,mid+1,r);
tr[now].g=__gcd(tr[lid].g,tr[rid].g);
}
int getgcd(int now,int l,int r,int x,int y)
{
if(x<=l&&r<=y)return tr[now].g;
int mid=(l+r)>>1;
int res=0;
if(x<=mid) res=__gcd(res,getgcd(lid,l,mid,x,y));
if(y>mid) res=__gcd(res,getgcd(rid,mid+1,r,x,y));
return res;
}
private:
struct segTree{
int g;
}tr[200100<<2];
}st;
int ans;
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
st.build(1,1,n);
int l=1,r=1;
for(l=1;l<=n;l++)
{
r=max(l,r);
while(l<=r&&r<=n)
{
if(st.getgcd(1,1,n,l,r)==1)
{
break;
}
r++;
}
ans+=(n-r+1);
}
cout<<ans;
return 0;
}

T4 找不同

官方题解的 RMQ 我没理解,但是理解了题解区某位大佬的线性 DP,深受震撼。

思路是这样的:

  1. 定义 dp[i]dp[i] 表示以 ii 为右端点、最长的、使得区间内没有重复出现单词左端点

形式化地,定义 dp[i]dp[i] 表示 dp[i]=j,x,y[j,i],a[x]a[y]dp[i]=j,x,y[j,i],a[x]a[y],并且对于所有 jj 符合,要求 j=dp[i]j=dp[i] 最小。

  1. 状态转移时,我们会用 map<string,int>last 记录当前字符串 a[i]a[i] 上一次出现的位置(下标),显然 dp[i]dp[i] 有可能从 last[a[i]]+1last[a[i]]+1 转移而来,并且 dp[i]dp[i] 的值不会小于等于 last[a[i]]last[a[i]]

  • 那么还能怎么转移?

  • 我们在转移 i1i1 之后,已经知道了 dp[i1]dp[i1] 的值,意思就是我们知道了以 i1i1 为右端点的合法区间的最大长度。如果在这个区间 [ji1,i1][ji1,i1] 中没有 a[i]a[i] ,那么 dp[i]dp[i] 一定等于 dp[i1]dp[i1]

  • 如果 last[a[i]]>dp[i1]last[a[i]]>dp[i1] ,就是在上一个合法区间内有一个 a[i]a[i],那么 dp[i]=last[a[i]]+1dp[i]=last[a[i]]+1

  • 综上所述,我们可以得到状态转移方程:

dp[i]=max(dp[i1],last[a[i]]+1)dp[i]=max(dp[i1],last[a[i]]+1)


  1. 对于每个询问 x,yx,y,我们只需要判断 dp[y]xdp[y]x 即可得到答案。

意思是如果 dp[y]xdp[y]x,那么 xxyy 对应的合法区间内,一定满足要求,输出 YES

否则输出 NO

#include<bits/stdc++.h>
using namespace std;
int n,m;
string a[100101];
int dp[100101];
map<string,int>last;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i]=max(dp[i-1],last[a[i]]+1);
last[a[i]]=i;
}
cin>>m;
while(m--)
{
int x,y;cin>>x>>y;
if(dp[y]<=x)
{
cout<<"YES\n";
}
else cout<<"NO\n";
}
}

2024.2.18 【寒假集训】20240218测试

T1 家庭作业

太可恶了!!!

没想到被离散化背刺了。

这个题思路很简单,只需要找出 aabb 的所有质因数是什么及其个数。

我考场上用的桶,想着省事开了个 map,结果炸了。

考完试改了十分钟,把一个桶改成两个数组,加上 cntcnt 就过了。

真令人忍俊不禁。

for(int i=1;i<=cnta;i++)
{
int kk=findb(ax[i]);
if(kk>0)
{
// cout<<min(aa[i],bb[i])<<" "<<i<<endl;
tag[ax[i]]=1;
ans*=(ppow(ax[i],min(aa[i],bb[kk])))%mod;
ans%=mod;
}
}
for(int i=1;i<=cntb;i++)
{
int kk=finda(bx[i]);
if(kk>0&&!tag[bx[i]])
{
tag[bx[i]]=1;
ans*=(ppow(bx[i],min(aa[kk],bb[i])))%mod;
ans%=mod;
}
}

T2 距离之和

更令人忍俊不禁。

考场上想到二分优化的做法,但是二分一直神奇地出问题。

二分:

考虑每个转移,只会影响 xx 轴或 yy 轴上的距离。

  • 如果是 SJ,那么 xx 轴方向上的距离都不会变
  • 如果是 IZ,那么 yy 轴方向上的距离都不会变

我们可以考虑分别以纵坐标和横坐标为关键字,对所有控制点进行排序。

然后就可以二分查找找到当前机器人坐标的相对位置,并记录其变化量就能得出答案。

还有两种说法,第一种用了权值线段树,60 分;

第二种 lhx 用了计数前缀和直接秒掉二分。

题解给的正解就是二分!!!!!

T3 country

T4 太空飞船

2024.2.19 【寒假集训】20240219测试

T1 素数

小黄题,无话可说。

搞前缀和就行了。

T2 晨练

dpdp,是我的能力范围之外,用 dfsdfs 骗了 20 分。

定义 dp[i][j]dp[i][j] 表示在第 ii 分钟,疲劳度为 jj 时的最长跑步距离。

对于每一个 ii,有四种状态:

  1. 疲劳度不为 00,且要继续跑

  2. 疲劳度不为 00,且要休息

  3. 疲劳度为 00,且要开始跑

  4. 疲劳度为 00,且要休息

由此,我们分类讨论,可以得到:

dp[i][j]={dp[i1][j1]+a[i],j[1,m]max(dp[i1][j],dp[ip][j+p]),j=0

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[10005];
int dp[10005][505];
int m;
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
dp[1][1]=a[1];
for(int i=1;i<=m;i++) dp[0][i]=-1145141919;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dp[i][j]=dp[i-1][j-1]+a[i];
// dp[i+j][0]=max(dp[i][j],dp[i+j][0]);
}dp[i][0]=dp[i-1][0];
for(int j=i-1,k=1;j&&k<=m;j--,k++)
{ //j-i-p,k=j+p
dp[i][0]=max(dp[i][0],dp[j][k]);
}
}
cout<<dp[n][0];
}

T3 奇怪的桌子

神秘组合 dp,用纯组合骗了 40 分。

那就是当 n=m 时,答案是 Ckn2

通过模拟,我们可以知道对于第 i 列和第 i 列的放法一定是相同的

考虑 dp,令 dp[i][j] 表示第 i 列放 j 个的方案数,

通过神秘的感性理解,可以得到状态转移方程:

dp[i][j]=dp[i][j]+dp[i1][jl]×Cmn+[im%n]n  ,jmin(i×n,k),lmin(n,j)

其中 [im%n] 表示当 im%n 成立时为 1,否则为 0 (一种神秘的 bool 表达式)

但是,这样写会 T 掉几个点,必须先预处理 Cmn+[im%n]n 这一堆。

预处理后时间复杂度为 O(n3) 左右(也有说法是 O(n3+n×max(n,k)))。

dp[0][0]=1;
for(int i=1;i<=n;i++)//预处理
{
for(int j=0;j<=max(n,k);j++)
{
qp[i][j]=ppow(getc(n,j),(m/n+(i<=m%n)))%mod;
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=min(i*n,k);j++)
{
for(int l=0;l<=min(n,j);l++)
{
dp[i][j]=(dp[i][j]+(dp[i-1][j-l]*qp[i][l])%mod)%mod;
}
}
}
cout<<dp[n][k];

T4 学校

神秘最短路+tarjan 求割边。

2024.2.21【寒假集训】20240221测试

寄的最惨的一次。。

image

T1 排序

这是个数学题。

给定 n4n 个元素,要求将其分为 n 组,使得对于每组四个数 a,b,c,d,所有组中 abcd 的和最大,求最大和。

一开始我看了一眼,没多想就去看 T2 T3 T4 了,结果 T4 写了两个小时挂了,回来再看 T1 已经神志不清了。。

现在用不等式证明一下:

设有 8 个数,分别为 a1,a2,a3,a4,并且 a1>a2>a3>a4

我们可以知道,所有的 abcd,有这几种可能:

a1a2  a3a4a1a3  a2a4a1a4  a2a3

我们要找 a1a2a3a4a1a3a2a4 的关系,可以这么写:

a1a2a3a4a1a3+a2a4=a2(a1+a4)a3(a1+a4)a2>a3a2(a1+a4)a3(a1+a4)>0a1a2a3a4>a1a3a2a4

其他证明同理。

最终我们可以得到这个不等式链:

a1a2+a3a4>a1a3+a2a4>a2a3+a1a4

所以,大的数应该相邻搭配,小的数应该一大一小搭配。

用样例去不完全归纳也可以得到这个结论。

答案序列:5,5,3,1  4,3,2,1

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[400005],ans;
signed main()
{
cin>>n;
for(int i=1;i<=4*n;i++)
{
cin>>a[i];
}
sort(a+1,a+4*n+1);
for(int i=1;i<=n;i++)
{
ans+=a[2*n+2*i-1]*a[2*n+2*i]-a[i]*a[2*n-i+1];
}
cout<<ans;
return 0;
}

T2 牛吃草

神秘 dp,是我的认知范围之外:所有我解释不了的题目不是神秘 dp 就是科技数据结构。

首先二分答案 size,对于每个 size,跑一遍神秘 dp 计算答案:

dp[i] 表示考虑完 [1,i] 后得到的最大覆盖长度。

则:

dp[i]=maxiwijisize(dp[i1],dp[j]+(ij))

神秘 dp 跑完之后,如果 dp[n]mm 表示s×n100,就记录答案。

这样能搞到 75。

bool ck(int siz)
{
for(int i=1;i<=n;i++) dp[i]=0;
for(int i=1;i<=n;i++)
{
dp[i]=dp[i-1];
for(int j=i-a[i];j<=i-siz;j++)
dp[i]=max(dp[i],dp[j]+(i-j));
}
if(dp[n]>=m) return 1;
return 0;
}

考虑优化:
观察:

wi1wi1

可以感性地得到:

(i1)wi1(i1)(wi1)=iwi

下限单调不降,相当于滑动窗口 [iwi,isize],可以单调队列维护。

#include<bits/stdc++.h>
using namespace std;
int n,a[1000101];
int ans;
double m;
int dp[1000010];
int q[1001001];
bool ck(int siz)
{
for(int i=1;i<=n;i++) dp[i]=0,q[i]=0;
int tt=1,hh=1;q[1]=1;
for(int i=siz;i<=n;i++)
{
if(a[i]<siz)
{
dp[i]=dp[i-1];continue;
}
while(hh<=tt&&dp[i-siz]-i+siz>=dp[q[tt]]-q[tt]) tt--;
q[++tt]=i-siz;
while(hh<=tt&&i-a[i]>q[hh])hh++;
dp[i]=max(dp[i-1],dp[q[hh]]+i-q[hh]);
}
if(dp[n]>=m) return 1;
return 0;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int s;cin>>s;
m=ceil(n*s/100.0);
int l=1,r=n;
while(l<=r)
{
int mid=(l+r)>>1;
if(ck(mid))
ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans;
}

2024.2.22【寒假集训】20240222测试

T1 打赌

大大大模拟。

空空空间思维。

判判判断。

这样我们可以找规律:

  • cmod4=1 时,列以 4 个一循环。
  • cmod4=2 时,列以 6 个一循环。
  • cmod4=3 时,列以 2 个一循环。
  • cmod4=0 时,列以 1 个一循环。

然后对于循环,预处理出每一列的和,最后一加就可以了。

搞了一个比较骚的 namespace

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,c;
int line[7];
namespace Rotation{
void rotateleft(int &top,int &bottom,int &left,int &right)
{
int tmp=top;
top=left,left=bottom,bottom=right,right=tmp;
return ;
}
void rotatefront(int &top,int &bottom,int &front,int &back)
{
int tmp=top;
top=back,back=bottom,bottom=front,front=tmp;
return ;
}
void rotateright(int &top,int &bottom,int &left,int &right)
{
int tmp=top;
top=right,right=bottom,bottom=left,left=tmp;
return ;
}
}
void init()
{
int top=1,bottom=6,front=2,back=5,left=3,right=4;
for(int k=1;k<=6;k++)
{
int cnt=0;
for(int i=1;i<c;i++)
{
cnt+=top;
if(k%2==1)
Rotation::rotateright(top,bottom,left,right);
else
Rotation::rotateleft(top,bottom,left,right);
}cnt+=top;
line[k]=cnt;
Rotation::rotatefront(top,bottom,front,back);
}
}
int cntline=0;
signed main()
{
cin>>n>>c;
init();int tp=c%4,kk=0;
if(tp==1) kk=4;
else if(tp==2) kk=6;
else if(tp==3) kk=2;
else kk=1;
for(int i=1;i<=kk;i++) cntline+=line[i];
cntline*=(n/kk);
for(int i=1;i<=n%kk;i++) cntline+=line[i];
cout<<cntline;
}

T2 舞会

对于这种不需要动脑的 ** 数据结构题,就不要考虑朴素算法。

我们考虑使用权值线段树去模拟平衡树操作。

但是不会。

那我们可以使用一个万能小 map 加上万能lower(upper)_bound 帮助我们找一个数的前驱后继

这样权值线段树就起到了辅助我们判断是否有合法的匹配,map 来找前驱后继。

然后分别在这两个数据结构里删点即可。

正数和负数要开两个。

要注意判断是否有合法时 now 要加一或减一。

码子有点长,有点丑,放到里头了:

#include<bits/stdc++.h>
using namespace std;
int n,a[101000],b[101000];
struct segmentTree{
public:
class node{
public:
int sum,l,r;
}tr[30000<<2];
#define lid now<<1
#define rid now<<1|1
void update(const int now,const int l,const int r,const int x,const int y,const int k)
{
if(x<=l&&r<=y)
{
tr[now].sum+=k;return ;
}
const int mid=(l+r)>>1;
if(x<=mid) update(lid,l,mid,x,y,k);
if(y>mid) update(rid,mid+1,r,x,y,k);
tr[now].sum=tr[lid].sum+tr[rid].sum;
}
int query(const int now,const int l,const int r,const int x,const int y)
{
if(x<=l&&r<=y) return tr[now].sum;
const int mid=(l+r)>>1;
int res=0;
if(x<=mid) res+=query(lid,l,mid,x,y);
if(y>mid) res+=query(rid,mid+1,r,x,y);
return res;
}
}st[2];
long long ans;
map<int,int>gir1,gir0;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
int t;cin>>t;
st[t>=0].update(1,1,2500,abs(t),abs(t),1);
if(t>0) gir1[t]++;
else gir0[-t]++;
}
for(int i=1;i<=n;i++)
{
int now;cin>>now;
if(now>0)
{
if(st[0].query(1,1,2500,now+1,2500)>=1)
{
int kk=(gir0.upper_bound(now))->first;
++ans;
st[0].update(1,1,2500,kk,kk,-1);
gir0[kk]--;
if(gir0[kk]==0) gir0.erase(kk);
}
}
else
{
if(st[1].query(1,1,2500,0,-now-1)>=1)
{
int kk=(--gir1.lower_bound(-now))->first;
++ans;
st[1].update(1,1,2500,kk,kk,-1);
gir1[kk]--;
if(gir1[kk]==0) gir1.erase(kk);
}
}
}
cout<<ans;
}

T3 最小生成树

这个题看似是一个图论题,实际上是数学题。

  • 对于要留下的边的个数,我们知道是要互质的。

答案就是每个点欧拉函数乘积。

可以使用 wme 大佬的线性筛,也可以使用简单质朴的神秘筛法:

inline int euler()
{
for(int i=1;i<=n;++i) phi[i]=i;
for( int i=2;i<=n;++i)
{
if(phi[i]==i)
{
for( int j=i;j<=n;j+=i)
phi[j]=phi[j]/i*(i-1);
}
}
}

T4 买汽水

这道题可以使用神秘 dp 骗到 100 分。

但是正解是搜素,对半搜索加上神秘剪枝。

参考:(不是我写的)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n;
ll ans=0,m,a[50];
void dfs(int x,ll k){
if(x==n+1){
if(k<=m) ans=max(ans,k);
return ;
}
k+=a[x];
if(k==m||ans==m){//再加点剪枝?
ans=m;
return ;
}
if(k<=m) dfs(x+1,k);
k-=a[x];
dfs(x+1,k);
}
int main(){
scanf("%d%lld",&n,&m);
ll sum=0;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum+=a[i];
}
if(sum<=m){
printf("%lld",sum);
return 0;
}
dfs(1,0);
printf("%lld",ans);
return 0;
}

20240405测试

我爱期望。。。

T1 [JLOI2014] 聪明的燕姿

这是数学。用线性筛去筛约数和然后循环判断可以拿 28 分。

T2 luogu4550收集邮票

纯期望。

考虑倒推,取 i 次剩下的期望 f[i]=f[i+1]×nni

i 次的期望得分 g[i]=ini+g[i+1]+f[i+1]×f[i]+nni

for(int i=n-1;~i;i--)
{
f[i]=f[i+1]+1.0*n/(1.0*(n-i));
g[i]=(1.0*i)/(1.0*n-i)*f[i]+g[i+1]+f[i+1]+n/(1.0*(n-i));
}

答案就是 g[0]

20240503测试

T1 [CF1279C]Stack of Present

就是小贪心,记录探过的最深的地方,每次更新。

T2 [luogu5522]棠梨煎雪

T3 [luogu1174]打砖块

神秘三维 dp,维护 dp[i][j][0/1] 表示到第 i 列,用过 j 颗子弹的最大得分,0/1 表示这一列有没有向前面借子弹

T4 「NOIP2015」斗地主

超级神秘大模拟,使用 dfs 加回溯。

image

这是搜索的顺序,至于为啥,咱也不知道,咱也不敢问

image

心肺骤停。。。就说为啥调不出来。

这直接无缝衔接,把小王大王读成 A 了。。。

END



set up on 24.2.6

upd on 24.2.15

posted @   ccjjxx  阅读(133)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示