Vjudge 20220429 练习14

written on 2022-05-02

A题数学推导题,但是因为数学太菜所以只会打表找规律。答案的递推公式是 \(a[n]=6a[n-1]-a[n-2]+2\) 。正解是化简以后用佩尔方程找递推式。佩尔方程以后再补,先放一个链接。找到递推式以后,就能用高精模拟求解了

B题是博弈论的题,题目若与博弈有关,那么就要牢牢抓住博弈的两个重要结论

  • 能转移到必败态的都是必胜态

  • 只能转移到必胜态的就是必败态

观察到n的范围只有7000,于是大胆暴力模拟,用dfs模拟状态然后根据两个结论转移。因为同一个已经搜过的状态再搜一次肯定没必要,所以加上记忆化就能过了

code

#include<bits/stdc++.h>
#define N 7005
using namespace std;
int n,p,q,a[N],b[N];
bool vis[N][2];
int ans[N][2],cnt[N][2];
void dfs(int x,bool sta)
{
	if(vis[x][sta]) return ;
	vis[x][sta]=1;
	if(sta==1)
	{
		for(int i=1;i<=p;i++)
		{
			int y=x-a[i];
			if(y<1) y+=n;
			if(y==1||vis[y][sta^1]) continue;
			if(ans[x][sta]==0) ans[y][sta^1]=1,dfs(y,sta^1);
			else if(++cnt[y][sta^1]==p) ans[y][sta^1]=0,dfs(y,sta^1);
		}
	}
	else
	{
		for(int i=1;i<=q;i++)
		{
			int y=x-b[i];
			if(y<1) y+=n;
			if(y==1||vis[y][sta^1]) continue;
			if(ans[x][sta]==0) ans[y][sta^1]=1,dfs(y,sta^1);
			else if(++cnt[y][sta^1]==q) ans[y][sta^1]=0,dfs(y,sta^1);
		}
	}
}
int main()
{
	scanf("%d",&n);
	scanf("%d",&p);for(int i=1;i<=p;i++) scanf("%d",&a[i]);
	scanf("%d",&q);for(int i=1;i<=q;i++) scanf("%d",&b[i]);
	memset(ans,-1,sizeof(ans));
	ans[1][1]=0,dfs(1,1),ans[1][0]=0,dfs(1,0);
	for(int i=2;i<=n;i++)
	{
		if(ans[i][0]==1) printf("Win ");
		else if(ans[i][0]==0) printf("Lose ");
		else printf("Loop ");
	}
	puts("");
	for(int i=2;i<=n;i++)
	{
		if(ans[i][1]==1) printf("Win ");
		else if(ans[i][1]==0) printf("Lose ");
		else printf("Loop ");
	}
}

先看D题,cpp教诲我们,做这种区间可能发生转移的题目,可以考虑固定左端点,然后再想删除左端点后对后续答案的影响。后续答案显然可以用一个普通线段树来维护,那么现在我们要做的事就有以下几点:

  1. 预处理出以1为左端点的所有mex,加入线段树,维护区间和

  2. 找到第一个与该数( \(a[x]\) )相同的数的位置 \(y\)

  3. \(x+1\) ~ \(y-1\) 这些位置mex大于 \(a[x]\) 的值全部用线段树改为 \(a[x]\)

  4. 统计答案

对于1操作,我的做法是,新开一个线段树,然后二分找到第一个不为0的位置,该位置即为从1开始的mex值

PS:1操作中的线段树值域要开到n+1

code

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 200005
using namespace std;
typedef long long ll;
int n,a[N];
int M[N],to[N];
struct Seg
{
	int siz[N<<2];
	ll val[N<<2],add[N<<2];
	void build(int p,int l,int r)
	{
		val[p]=siz[p]=0;
		add[p]=-1;
		if(l==r) return ;
		int mid=l+r>>1;
		build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	}
	void update(int p,int l,int r,int x)
	{
		if(l==r)
		{
			siz[p]=1;
			return ;
		}
		int mid=l+r>>1;
		if(x<=mid) update(p<<1,l,mid,x);
		else update(p<<1|1,mid+1,r,x);
		siz[p]=siz[p<<1]+siz[p<<1|1];
	}
	int find(int p,int l,int r)
	{
		if(l==r) return l;
		int mid=l+r>>1,num=siz[p<<1];
		if(num!=mid-l+1) return find(p<<1,l,mid);
		else return find(p<<1|1,mid+1,r);
	}
	void spread(int p,int l,int r)
	{
		if(add[p]==-1) return ;
		int mid=l+r>>1;
		val[p<<1]=add[p]*(mid-l+1),val[p<<1|1]=add[p]*(r-mid);
		add[p<<1]=add[p<<1|1]=add[p];
		add[p]=-1;
	}
	void update(int p,int l,int r,int x,ll v)
	{
		if(l==r)
		{
			val[p]+=v;
			return ;
		}
		spread(p,l,r);
		int mid=l+r>>1;
		if(x<=mid) update(p<<1,l,mid,x,v);
		else update(p<<1|1,mid+1,r,x,v);
		val[p]=val[p<<1]+val[p<<1|1];
	}
	void update(int p,int l,int r,int L,int R,ll v)
	{
		if(L>R) return ;
		if(L<=l&&R>=r)
		{
			val[p]=1ll*(r-l+1)*v;
			add[p]=v;
			return ;
		}
		spread(p,l,r);
		int mid=l+r>>1;
		if(L<=mid) update(p<<1,l,mid,L,R,v);
		if(R>mid) update(p<<1|1,mid+1,r,L,R,v);
		val[p]=val[p<<1]+val[p<<1|1];
	}
	ll ask(int p,int l,int r,int L,int R)
	{
		if(L<=l&&R>=r) return val[p];
		spread(p,l,r);
		int mid=l+r>>1;
		ll res=0;
		if(L<=mid) res+=ask(p<<1,l,mid,L,R);
		if(R>mid) res+=ask(p<<1|1,mid+1,r,L,R);
		return res;
	}
}t1,t2;
void prework1()
{
	for(int i=1;i<=n;i++)
	{
		if(a[i]<=n) t1.update(1,1,n+1,a[i]+1);
		M[i]=t1.find(1,1,n+1)-1;
		t2.update(1,1,n,i,M[i]);
	}
//	for(int i=1;i<=n;i++) printf("M=%d ",M[i]);printf("\n");
}
int nxt[N];
void prework2()
{
	memset(nxt,0,sizeof(nxt));
	for(int i=n;i;i--)
	{
		if(a[i]>n) continue;
		to[i]=n+1;
		if(nxt[a[i]]) to[i]=nxt[a[i]];
		nxt[a[i]]=i;
	}
//	for(int i=1;i<=n;i++) printf("to=%d\n",to[i]);printf("\n");
}
int get(int l,int r,ll x)
{
	int res=-1;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(t2.ask(1,1,n,mid,mid)>x) res=mid,r=mid-1;
		else l=mid+1;
	}
	return res;
}
void work()
{
	ll ans=0;
//	printf("val=%d\n",t2.ask(1,1,n,1));
	for(int i=1;i<=n;i++)
	{
		ans+=t2.ask(1,1,n,i,n);
		if(a[i]>n) continue;
		int pos=get(i+1,to[i]-1,1ll*a[i]);
		if(pos!=-1) t2.update(1,1,n,pos,to[i]-1,1ll*a[i]);
//		printf("i=%d ans=%d pos=%d\n",i,ans,pos);
	}
	printf("%lld\n",ans);
}
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		if(!n) return 0;
		t1.build(1,1,n+1),t2.build(1,1,n);
		for(int i=1;i<=n;i++) scanf("%d",&a[i]);
		prework1();//预处理出以1为左端点的这些区间的mex值
		prework2();//预处理出于a[i]相同的下一个位置
		//对于mex<=a[i]的点来说,是没有影响的。但是对于大于a[i]的点来说,是有影响的
		work();
	}
}//一样要注意细节

C题有两种做法。

1. 用主席数暴力预处理出一段区间内不同的数的个数,对于每一次 \(k\) 的查询,二分最远的点来跳,这样的时间复杂度
\(O(nlog²n)\)

  1. 利用根号分治的思想,因为 \(∑n/k\) , \(k\)值域 \(1\) ~ \(n\) 的值可证明小于nlogn,然后答案又具有二分性,因此暴力向右二分相等的答案,暴力输出即可。题解

第一份代码懒得打了,就贴第二份代码了

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,a[N];
bool mark[N];
int ans[N];
int work(int x)
{
	if(ans[x]) return ans[x];
	int res=0;
	for(int i=1;i<=n;i++)
	{
		int t=i,now=0;
		while(t<=n)
		{
			if(!mark[a[t]])
			{
				if(now==x)
				{
					res++,now=0;
					break;
				}
				mark[a[t]]=1;
				now++;
			}
			t++;
		}
		if(now) res++;
		for(int j=t;j>=i;j--) mark[a[j]]=0;
		i=t-1;
	}
	return ans[x]=res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int B=sqrt(n);
	for(int i=1;i<=B;i++) printf("%d ",work(i));
	for(int i=B+1;i<=n;i++)
	{
		int now=work(i);
		int l=i+1,r=n,res=i;
		while(l<=r)
		{
			int mid=l+r>>1;
			if(work(mid)==now) l=mid+1,res=mid;
			else r=mid-1;
		}
		for(int j=i;j<=res;j++) printf("%d ",now);
		i=res;
	}
}

update on 2022-7-31

注意其中 \(C\) 题的第二种根号分治的做法,这种思想在一些根号分治的题目中还是比较常见的。

posted @ 2022-07-31 18:00  Freshair_qprt  阅读(28)  评论(0编辑  收藏  举报