NOIP模拟74

前言

我就想说一句,T3 给了一个什么牛马大样例!!!!!!!!,气\(^{TM}\)死我!!!!!!!

我的 \(\mathcal{O}(n)\) 算法始终过不掉大样例我 TM ,要不然我就直接上矩阵乘优化了。。

最后没法了把我的所谓的不正确的正解交上去,考完发现有 56pts ,我以为是数据水,果断去看正解。。

苦思冥想了好久直到战神大样例没过切掉了此题,我才开始矩阵乘优化,然后就过了(然而和题解柿子完全不一样。。)

牛马大样例!!!!

T1 自然数

解题思路

考场上一眼 CDQ 分治,最后一看果然不是。。(逃

可惜的是我打的小暴力还被卡常卡掉了 34pts 。。。

对于每一个左端点线段树维护出每一个右端点的和,因此我们先求出左端点为 1 的所有答案作为初始值加入到线段树中。

然后向右移动左端点,每次相当于删去一个值 \(x\) 那么在当前扫到的左端点到 \(x\) 的上一次出现位置的所有大于 \(x\) 的答案都会变为 \(x\)

由于答案的值是单调不降的,因此我们可以二分答案在线段树上面查值,也可以线段树上二分,复杂度带一个或者两个 log

code

#include <bits/stdc++.h>
#define int long long 
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
#define ls x<<1
#define rs x<<1|1
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e5+10;
int n,ans,nxt[N],s[N],cnt[N],num[N];
unordered_map<int,int> mp;
struct Segment_Tree
{
	struct Node{int mn,dat,laz;}tre[N<<2];
	void push_up(int x)
	{
		tre[x].mn=min(tre[ls].mn,tre[rs].mn);
		tre[x].dat=tre[ls].dat+tre[rs].dat;
	}
	void push_down(int x,int l,int r)
	{
		if(tre[x].laz==-1) return ;
		int mid=(l+r)>>1;
		tre[ls].laz=tre[x].laz; tre[rs].laz=tre[x].laz;
		tre[ls].dat=(mid-l+1)*tre[x].laz; tre[rs].dat=(r-mid)*tre[x].laz;
		tre[ls].mn=tre[rs].mn=tre[x].laz;
		tre[x].laz=-1;
	}
	void build(int x,int l,int r)
	{
		tre[x].laz=-1;
		if(l==r) return tre[x].mn=tre[x].dat=num[l],void();
		int mid=(l+r)>>1;
		build(ls,l,mid); build(rs,mid+1,r);
		push_up(x);
	}
	void insert(int x,int l,int r,int L,int R,int val)
	{
		if(L<=l&&r<=R) return tre[x].dat=val*(r-l+1),tre[x].laz=tre[x].mn=val,void();
		int mid=(l+r)>>1; push_down(x,l,r);
		if(L<=mid) insert(ls,l,mid,L,R,val);
		if(R>mid) insert(rs,mid+1,r,L,R,val);
		push_up(x);
	}
	int query(int x,int l,int r,int L,int R)
	{
		if(L<=l&&r<=R) return tre[x].dat;
		int mid=(l+r)>>1,sum=0; push_down(x,l,r);
		if(L<=mid) sum+=query(ls,l,mid,L,R);
		if(R>mid) sum+=query(rs,mid+1,r,L,R);
		return sum;
	}
	int query(int x,int l,int r,int pos)
	{
		if(l==r) return tre[x].mn;
		int mid=(l+r)>>1; push_down(x,l,r);
		if(pos<=mid) return query(ls,l,mid,pos);
		return query(rs,mid+1,r,pos);
	}
}T;
signed main()
{
	freopen("mex.in","r",stdin); freopen("mex.out","w",stdout);
	n=read(); for(int i=1;i<=n;i++) s[i]=read();
	for(int i=1,pos=0;i<=n;i++)
	{
		if(s[i]<=n) cnt[s[i]]++;
		while(cnt[pos]) pos++;
		num[i]=pos;
	}
	T.build(1,1,n);
	for(int i=n;i;i--)
	{
		nxt[i]=n+1;
		if(mp.find(s[i])==mp.end()){mp.insert(make_pair(s[i],i));continue;}
		nxt[i]=mp.find(s[i])->second; mp[s[i]]=i;
	}
	for(int i=1;i<=n;i++)
	{
		ans+=T.query(1,1,n,i,n);
		int l=i,r=nxt[i]-1,temp=-1;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(T.query(1,1,n,mid)>s[i]) temp=mid,r=mid-1;
			else l=mid+1; 
		}
		if(~temp) T.insert(1,1,n,temp,nxt[i]-1,s[i]);
	}
	printf("%lld",ans);
	return 0;
}

T2 钱仓

解题思路

显然的一个结论一定存在一个点,使得按照顺时针顺序原来在它之前的点上的钱一定不会运到在它之后的点上。

于是我们就可以得到枚举断点求答案的 \(\mathcal{O}(n^2)\) 做法。

然后发现所有合法的序列其实答案都是相同的,我是我们可以枚举 \([1,2n]\) 查看是否有长度为 \(n\) 的一段。

满足一段内距离左端点的前缀和与长度的差值始终大于等于 0。

直接从该断点计算即可。

code

#include <bits/stdc++.h>
#define int long long 
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=2e5+10,INF=1e18;
int n,ans=INF,beg,s[N],p[N];
void solve(int fro)
{
	for(int i=fro,temp=0;i<=fro+n-1;i++)
	{
		temp+=s[i]-1; p[i-fro+1]=s[i];
		if(temp<0) return ;
	}
	int sum=0,pos=1; p[pos]--;
	for(int i=2;i<=n;i++)
	{
		while(!p[pos]) pos++;
		p[pos]--; sum+=(i-pos)*(i-pos);
	}
	ans=min(ans,sum);
}
signed main()
{
	freopen("barn.in","r",stdin); freopen("barn.out","w",stdout);
	n=read(); for(int i=1;i<=n;i++) s[i+n]=s[i]=read();
	for(int i=1,sum=0;i<=2*n;i++)
	{
		sum+=s[i];
		if(sum-i+beg<0) beg=i,sum=0;
		if(i-beg==n) break;
	}
	solve(beg+1); printf("%lld",ans);
	return 0;
}

T3 游戏

解题思路

如果没有那个大样例的话这绝对称得上是个期望好题。

易得,在只剩下一个棋子的时候都希望自己可以拿走,那么在剩下两个石子的时候一定都希望自己下一局先手,从而不希望本局可以取到石子。

以此类推,可以发现剩下奇数个石子的时候两人都想到得到,偶数时则相反。

假设这一局 \(A\) 先手的概率是 \(lasa\) , \(B\) 先手的概率是 \(lasb\) ,下一局的概率是 \(A,B\)

如果是剩下奇数个石子时,转移就是:

\[A=\dfrac{(1-a)b\times lasa}{a+b-ab}+\dfrac{b\times lasb}{a+b-ab} \]

\[B=\dfrac{a\times lasa}{a+b-ab}+\dfrac{(1-b)a\times lasb}{a+b-ab} \]

对于偶数的情况大概类似,只不过变成了两者都不想要。。

优化的话直接矩阵乘法就好了,由于矩阵乘法 不满足交换率 因此我们应该把一奇一偶绑在一起成为一个新的矩阵进行快速幂,如果是奇数最后再乘一次奇数矩阵就好了。。。

当然,总有些神仙和常人的思维不同,有意者可以去 zxb 的 blog 学习倍增算法。。

code

#include <bits/stdc++.h>
#define int long long 
#define ull unsigned long long
#define f() cout<<"Failed"<<endl
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int mod=1e9+7,Inv=571428574;
int T,n,a,b;
struct Square
{
	int a[2][2];
	void clear(){memset(a,0,sizeof(a));}
	Square friend operator * (Square x,Square y)
	{
		Square z; z.clear();
		for(int i=0;i<=1;i++)
			for(int j=0;j<=1;j++)
				for(int k=0;k<=1;k++)
					z.a[i][j]+=x.a[i][k]*y.a[k][j];
		for(int i=0;i<=1;i++)
			for(int j=0;j<=1;j++)
				z.a[i][j]%=mod;
		return z;
	}
}A,B,C,ans;
int power(int x,int y,int p=mod){int temp=1;while(y){if(y&1) temp=temp*x%p;x=x*x%p; y>>=1;}return temp;}
void pw(Square &x,Square y,int po){while(po){if(po&1) x=x*y;y=y*y; po>>=1;}}
void solve()
{
	n=read(); a=read()*Inv%mod; b=read()*Inv%mod;
	int sum=(a+(1-a+mod)*b)%mod,lasa=1,lasb=0;
	int inv=power((a+b-a*b%mod+mod)%mod,mod-2);
	A.clear(); B.clear(); C.clear(); ans.clear();
	ans.a[0][1]=1;
	A.a[0][0]=(1-a+mod)*b%mod*inv%mod;
	A.a[0][1]=b*inv%mod;
	A.a[1][0]=a*inv%mod;
	A.a[1][1]=(1-b+mod)*a%mod*inv%mod;
	inv=power((1-a*b%mod+mod)%mod,mod-2);
	B.a[0][0]=a*(1-b+mod)%mod*inv%mod;
	B.a[0][1]=(1-b+mod)*inv%mod;
	B.a[1][0]=(1-a+mod)*inv%mod;
	B.a[1][1]=b*(1-a+mod)%mod*inv%mod;
	C=A*B; pw(ans,C,n/2);
	if(n&1) ans=ans*A;
	printf("%lld\n",ans.a[0][0]%mod);
}
signed main()
{
	freopen("game.in","r",stdin); freopen("game.out","w",stdout);
	T=read(); while(T--) solve();
	return 0;
}

T4 Sanrd

大坑未补

posted @ 2021-10-11 21:42  Varuxn  阅读(205)  评论(0编辑  收藏  举报