模拟赛总结

2024.2.6 【寒假集训】20240206测试

T1 珠子

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

T2 数组

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

代码(参考 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 幸运区间

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

\[\sum_{x=1}^{n}\sum_{y=x}^{n}[\gcd(x \sim y)==1] \]

(\(\sim\) 表示从 \(x\)\(y\) 的所有元素)

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

         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

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

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

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

  1. 查找区间 \([l,r]\)\(\gcd\)

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

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

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

对于找到一个 \(l\)、满足条件的最小的 \(r\),我们考虑直接 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]\) 表示以 \(i\) 为右端点、最长的、使得区间内没有重复出现单词左端点

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

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

  • 那么还能怎么转移?

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

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

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

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


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

意思是如果 \(dp[y]\le x\),那么 \(x\)\(y\) 对应的合法区间内,一定满足要求,输出 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 家庭作业

太可恶了!!!

没想到被离散化背刺了。

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

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

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

真令人忍俊不禁。

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 距离之和

更令人忍俊不禁。

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

二分:

考虑每个转移,只会影响 \(x\) 轴或 \(y\) 轴上的距离。

  • 如果是 SJ,那么 \(x\) 轴方向上的距离都不会变
  • 如果是 IZ,那么 \(y\) 轴方向上的距离都不会变

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

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

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

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

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

T3 country

T4 太空飞船

2024.2.19 【寒假集训】20240219测试

T1 素数

小黄题,无话可说。

搞前缀和就行了。

T2 晨练

\(dp\),是我的能力范围之外,用 \(dfs\) 骗了 20 分。

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

对于每一个 \(i\),有四种状态:

  1. 疲劳度不为 \(0\),且要继续跑

  2. 疲劳度不为 \(0\),且要休息

  3. 疲劳度为 \(0\),且要开始跑

  4. 疲劳度为 \(0\),且要休息

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

\[dp[i][j]= \begin{cases} dp[i-1][j-1]+a[i]&,j\in[1,m] \\ \\ \max(dp[i-1][j],dp[i-p][j+p])&,j=0 \end{cases} \]

#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\) 时,答案是 \(\large{C_{n^2}^k}\)

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

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

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

\[\large{dp[i][j]=dp[i][j]+dp[i-1][j-l] \times C_{n}^{ \frac{m}{n} +[i \le m \% n]}} \ \ ,j \le \min(i \times n,k),l \le \min(n,j) \]

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

但是,这样写会 T 掉几个点,必须先预处理 \(\large{C_{n}^{\frac{m}{n} +[i \le m \% n]}}\) 这一堆。

预处理后时间复杂度为 \(O(n^3)\) 左右(也有说法是 \(O(n^3 + n \times \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 排序

这是个数学题。

给定 \(n\)\(4n\) 个元素,要求将其分为 \(n\) 组,使得对于每组四个数 \(a,b,c,d\),所有组中 \(∣ab−cd∣\) 的和最大,求最大和。

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

现在用不等式证明一下:

设有 \(8\) 个数,分别为 \(a_1,a_2,a_3,a_4\),并且 \(a_1>a_2>a_3>a_4\)

我们可以知道,所有的 \(ab-cd\),有这几种可能:

\[\begin{aligned} a_1a_2 \ \ a_3a_4 \\ a_1a_3 \ \ a_2a_4 \\ a_1a_4 \ \ a_2a_3 \end{aligned} \]

我们要找 \(a_1a_2-a_3a_4\)\(a_1a_3-a_2a_4\) 的关系,可以这么写:

\[\large{ \begin{aligned} &a_1a_2-a_3a_4-a_1a_3+a_2a_4 \\ &=a_2(a_1+a_4)-a_3(a_1+a_4) \\ &\because a_2>a_3 \\ &\therefore a_2(a_1+a_4)-a_3(a_1+a_4)>0 \\ \\ &\therefore a_1a_2-a_3a_4>a_1a_3-a_2a_4 \end{aligned}} \]

其他证明同理。

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

\[\large{a_1a_2+a_3a_4>a_1a_3+a_2a_4>a_2a_3+a_1a_4} \]

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

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

答案序列:\(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]\) 后得到的最大覆盖长度。

则:

\[\large{ dp[i]=\max_{i-w_i \le j \le i-size}(dp[i-1],dp[j]+(i-j)) }\]

神秘 dp 跑完之后,如果 \(dp[n]\ge m\)\(m\) 表示\(\frac{s \times n}{100}\),就记录答案。

这样能搞到 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;
}

考虑优化:
观察:

\[w_{i-1}\ge w_{i}-1 \]

可以感性地得到:

\[(i-1)-w_{i-1} \ge (i-1)-(w_i-1)=i-w_i \]

下限单调不降,相当于滑动窗口 \([i-w_i,i-size]\),可以单调队列维护。

#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 打赌

大大大模拟。

空空空间思维。

判判判断。

这样我们可以找规律:

  • \(c \mod 4=1\) 时,列以 \(4\) 个一循环。
  • \(c \mod 4=2\) 时,列以 \(6\) 个一循环。
  • \(c \mod 4=3\) 时,列以 \(2\) 个一循环。
  • \(c \mod 4=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]\times \frac{n}{n-i}\)

\(i\) 次的期望得分 \(g[i]=\frac{i}{n-i}+g[i+1]+f[i+1]\times f[i]+\frac{n}{n-i}\)

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



\(\frak{set \ up \ on \ 24.2.6}\)

\(\frak{upd \ on \ 24.2.15}\)

posted @ 2024-02-15 11:02  ccjjxx  阅读(17)  评论(0编辑  收藏  举报