2024.7.19模拟赛2

模拟赛

T1 立大功。

T1 yyy loves Maths VI (mode)

摩尔投票法。

既然有一个人出现次数 \(\gt \frac{n}{2}\),那么我们可以用两两抵消的思路。最坏的情况就是每一个不是答案的都消掉了一个答案,但这样也会剩下正确答案。

for(int i=1;i<=n;++i)
{
	int x; scanf("%d",&x);
	if(cnt==0)  a=x;
	if(a==x) cnt++;
	else cnt--;
}

最炸裂的是用 CRT!!!取几个质数求模,每个都找出模数出现次数最多的那个,然后解。

复习CRT。(如果不证明还挺简单的?)

code
#include<bits/stdc++.h>
using namespace std;
int n,prime[3][1030],a[3]={1009,1019,1021},mx[3],ans[3];

int exgcd(int a,int b,int &x,int &y)
{
	if(b==0) {x=1; y=0; return a;} 
	int gcd=exgcd(b,a%b,x,y),t=x;
	x=y; y=t-y*(a/b);
	return gcd;
}
int u,v;
int crt()
{
	int x,y,res=0;long long ck=1,m;
	for(int i=0;i<3;i++) ck*=a[i];
	for(int i=0;i<3;i++)
	{
		m=ck/a[i];
		exgcd(a[i],m,x,y);
		res=(res+y*m*ans[i])%ck;
	}
	return res>0?res:res+ck;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		int x; scanf("%d",&x);
		for(int j=0;j<3;j++) prime[j][x%a[j]]++;
	}
	for(int j=0;j<3;j++)
		for(int i=0;i<a[j];i++)
			if(mx[j]<prime[j][i]) mx[j]=prime[j][i],ans[j]=i;
	printf("%d\n",crt());
	return 0;
}

T2 Non-boring sequences

赛时半小时打完,最后十分钟发现假了。。。

正解两种:

一、 分治

思路出奇的简单,我们考虑如果一个数在目前的区间中只出现一次,那么所有跨过它的子区间一定都满足,所以我们只需要对它左右的两个子区间递归求解就好了。

如何判断一个数在某个区间中是否只出现一次呢?可以预处理每个数上一次出现的位置和下一次出现的位置,判断是否在区间内就好了。

大概长这样:

for(int i=1;i<=n;i++)
{
	nxt[i]=1e9;
	int x; scanf("%d",&x);
	pre[i]=mp[x]; nxt[lst[i]]=i;
	mp[x]=i;//map离散化
}

这样我们还需要遍历每个区间,极限情况可以卡。

这里用到一个 trick:启发式分裂!!!

启发式分裂(中途相遇法):从两边同时遍历,最坏情况在中间 \(\frac{len}{2}\)

名字依旧很高级,其实就是每次分成两半,同时进行,防止被卡。用处挺大的。

复杂度证明用主定理,oi-wiki

code
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int t;
int n,nxt[N],pre[N];
unordered_map<int,int> mp;

bool dfs(int l,int r)
{
	if(l>=r) return 1;
	int ll=l,rr=r;
	while(ll<=rr)
	{
		if(pre[ll]<l&&nxt[ll]>r)
			return dfs(l,ll-1)&&dfs(ll+1,r);
		if(pre[rr]<l&&nxt[rr]>r)
			return dfs(l,rr-1)&&dfs(rr+1,r);
		ll++,rr--;		
	}
	return 0;
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		mp.clear();
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			nxt[i]=1e9;
			int x; scanf("%d",&x);
			pre[i]=mp[x]; nxt[lst[i]]=i;
			mp[x]=i;
		}
		if(dfs(1,n)) printf("non-boring\n");
		else printf("boring\n");
	}
	return 0;
}

二、 扫描线

考虑每一个点的贡献,依旧是记录上一次和下一次出现的位置,那么所有跨过这个点的区间仍是合法的。

我们设点的位置为 \(i\),上一次和下一次出现位置分别为 \(pre,nxt\)。那么这个点管辖的区间:

  • 左端点 \([pre+1,i]\),右端点 \([i,nxt-1]\)

合法的就是每一个子序列都能被覆盖,可是我们得到的是一个区间左右端点的范围,无法确定准确的区间。

如果把左右端点分开看,分别看成坐标系上的一维,那么这个管辖区间可以用一个矩形的面积表示。

所有子区间也就是每一个 \(l\) 对应一个 \(r\)(注意 \(l \le r\))。在坐标系上也就是一个面积为 \(\frac{(n+1) \times n}{2}\) 的三角形。

因此我们要求的就是这些矩形面积的并是否等于这个三角形的面积。

复习扫描线,细节在注释里了。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int t;
int n,nxt[N],pre[N],tot;
long long ans;
unordered_map<int,int> mp;
struct L
{
	int x,l,r,tg;
	bool operator < (const L &y) const
	{
		return x<y.x;
	}
} line[N<<1];
struct T
{
	long long sum,tag;
} tr[N<<3];
void clear()
{
	mp.clear(); tot=0; ans=0;
	memset(line,0,sizeof(line));
	memset(pre,0,sizeof(pre));
	memset(nxt,0,sizeof(nxt));
	memset(tr,0,sizeof(tr));
}
void pushup(int k,int l,int r)
{
	if(tr[k].tag) tr[k].sum=1ll*r-l+1;//有标记直接整段 
	else tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;//没有标记区间加和 
}
void add(int k,int l,int r,int v,int L,int R)													
{
	if(L<=l&&R>=r)
	{
		tr[k].tag+=v;
		pushup(k,l,r);
		return;
	}
	int mid=l+r>>1;
	if(L<=mid) add(k<<1,l,mid,v,L,R);
	if(R>mid) add(k<<1|1,mid+1,r,v,L,R);
	pushup(k,l,r);
}
int ask() {return tr[1].sum;}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		clear();
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			nxt[i]=n+1;
			int x; scanf("%d",&x);
			pre[i]=mp[x]; nxt[pre[i]]=i;
			mp[x]=i;
		}
		for(int i=1;i<=n;i++)
		{
			line[++tot]={pre[i]+1,i,nxt[i]-1,1};
			line[++tot]={i+1,i,nxt[i]-1,-1};//i+1在后一位减一,第i位也要有值 
		}
		sort(line+1,line+1+tot);
		for(int i=1,j=1;i<=n;i++) 
		{
			while(j<=tot&&line[j].x==i)//每一个横坐标上的线段 
				add(1,1,n,line[j].tg,line[j].l,line[j].r),j++;
			ans+=ask(); //由于每一条厚度都是一,我们遍历 [1,n] 的过程就在统计厚度。 
		}
		puts(ans==(1ll+n)*n/2?"non-boring":"boring");//最终统计的应是一个三角形,l 一定在 r 左边。 
	}
	return 0;
}

T3 Legacy

线段树优化建图板子,没什么好说的。

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e6+5;
int n,q,s,rt[2];
int tot,head[N];
struct E {int u,v,w;} e[N];
void add(int u,int v,int w) {e[++tot]={head[u],v,w}; head[u]=tot;}

struct T
{
	int l,r,ls,rs;
} tr[N];

void bui(int &u,int l,int r,bool k)
{
	tr[u].l=l; tr[u].r=r;
	if(l==r) return u=l,void(0);
	u=++tot;
	int mid=l+r>>1;
	bui(tr[u].ls,l,mid,k);
	bui(tr[u].rs,mid+1,r,k);
	if(k) add(u,tr[u].ls,0),add(u,tr[u].rs,0);
	else add(tr[u].ls,u,0),add(tr[u].rs,u,0);
}

void mdf(int u,int l,int r,int st,int ql,int qr,int w,bool k)
{
	if(ql<=l&&qr>=r) 
	{
		if(k) add(st,u,w);
		else add(u,st,w);
		return;
	}
	int mid=l+r>>1;
	if(ql<=mid) mdf(tr[u].ls,l,mid,st,ql,qr,w,k) ;
	if(qr>mid) mdf(tr[u].rs,mid+1,r,st,ql,qr,w,k);
}
int d[N];
bool vs[N];
void dj(int s)
{
	memset(d,0x3f,sizeof(d));
	priority_queue<pair<int,int> > q;
	d[s]=0; q.push(make_pair(0,s));
	while(!q.empty())
	{
		int u=q.top().second; q.pop();
		if(vs[u]) continue;
		vs[u]=1;
		for(int i=head[u];i;i=e[i].u)
		{
			int v=e[i].v;
			if(!vs[v]&&d[v]>d[u]+e[i].w)
			{
				d[v]=d[u]+e[i].w;
				q.push(make_pair(-d[v],v));
			}
		}
	}
}
main()
{
	scanf("%lld%lld%lld",&n,&q,&s);
	tot=n;
	bui(rt[0],1,n,0);
	bui(rt[1],1,n,1);
	while(q--)
	{
		int c; scanf("%lld",&c);
		if(c==1)
		{
			int x,y,z;  scanf("%lld%lld%lld",&x,&y,&z);
			add(x,y,z);
		}
		else if(c==2)
		{
			int x,y,z,w; scanf("%lld%lld%lld%lld",&x,&y,&z,&w);
			mdf(rt[1],1,n,x,y,z,w,1);
		}
		else
		{
			int x,y,z,w; scanf("%lld%lld%lld%lld",&x,&y,&z,&w);
			mdf(rt[0],1,n,x,y,z,w,0);			
		}
	}
	dj(s);
	for(int i=1;i<=n;i++) printf("%lld ",d[i]<1e15?d[i]:-1);
	putchar('\n');
	return 0;
}

T4 DP搬运工1

Description

预设性 dp,枚举填数,然后考虑当前状态的分段情况。

\(f_{i,j,k}\) 表示填前 \(i\) 个数,存在 \(j\)不相连的段的空缺,最大值的和为 \(k\)

我们转移时就是在这些空缺中填数,由于是从小数填到大数,如果一个数填的时候左右都没有相邻的数,那么它不会产生贡献,因为贡献会被后面的数覆盖。

根据只有左右已经有数才会产生贡献,我们可以分不同情况:

  1. 单独放进去,左右都没有相邻的数,段数 \(+1\),无贡献。

  2. 放其中一端,只有一边有相邻的数,段数不变,一倍贡献。

  3. 恰好放两端中间夹住,两边都有相邻的数,段数 \(-1\),二倍贡献。

code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define scan __builtin_scanf
#define print __builtin_printf

const int N = 55,mod = 998244353;
int n,k;
LL f[N][N][N*N],ans;

int main()
{
	// freopen("in.in","r",stdin);
	// freopen("out.out","w",stdout);
	scan("%d%d",&n,&k);
	f[1][0][0]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=n-i+1;j++)
		{
			for(int h=0;h<=k;h++)
			{
				if(!f[i-1][j][h]) continue;
				if(j)//至少两个连续的块,中间夹的空缺可以放
				{
					f[i][j][h+i]+=2*f[i-1][j][h]*j%mod;
					f[i][j+1][h]+=f[i-1][j][h]*j%mod;
					f[i][j-1][h+2*i]+=f[i-1][j][h]*j%mod;
				}
				f[i][j][h+i]+=2*f[i-1][j][h]%mod;//放两边
				f[i][j+1][h]+=2*f[i-1][j][h]%mod;//自立门户,谁都不靠。
			}
		}
	}
	for(int i=0;i<=k;i++) ans=(ans+f[n][0][i])%mod;
	print("%lld\n",ans);
	return 0;
}
posted @ 2024-07-20 11:15  ppllxx_9G  阅读(34)  评论(2编辑  收藏  举报