NOIP模拟69

T1 石子游戏

解题思路

首先一个结论是对于最大取石子数量的限制 \(x\) 将所有石子个数分别 \(\bmod\;x+1\)\(xor\) 最后的结果如果是 0 的话,面临这个局面的人必败,否则必胜。

有点像 巴什博弈 (尽管我也是第一次接触。。),一堆与多堆的情况类似。。

\(n=m+1\) 时,由于一次最多只能取 \(m\) 个,所以无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜,所以当一方面对的局势是 \(n\bmod (m+1)=0\) 时,其面临的是必败的局势。所以当 \(n=(m+1)\times r+s\)\((r为任意自然数,s\le m)\) 时,如果先取者要拿走 \(s\) 个物品,如果后取者拿走 \(x(\le m)\) 个,那么先取者再拿走 \(m+1-k\) 个,结果剩下 \((m+1)\times(r-1)\) 个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下 \(m+1\) 的倍数,就能最后获胜。

然后就是优化了,对于每一位考虑贡献,预处理 \(cnt_x\) 表示为 \(x\) 的数字的个数 \(f_{i,j}\) 表示满足 \(x-i\)\(j\) 这个二进制位的数的个数。

就有 \(f_{i,j}=f_{i+2^{j+1},j}+\sum\limits_{k=i+2^j}^{i+2^{j+1}-1}cnt_k\)

然后对于每一个不同的区间 \([k\times(x+1),(k+1)\times(x+1)\;)\) 计算答案。

但是这并不是简单的前缀加减,柿子应该是这样的

\[sum=f_{k\times(i+1),j}-f_{k*(i+1)+\lfloor\frac{(k+1)\times(i+1)-1}{2^{j+1}}\rfloor\times 2^{j+1},j} \]

原因吗,显然再 \(j+1\) 这个二进制位的差是对于 \(j\) 没有影响的。

同样的代码中的 \(+2^j\) 这个区间中变动的都是 \(j\) 这个二进制位之前的也没有什么影响。

在处理一下边界问题就好了。

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=1e6+10,Lg=20;
int n,lg,s[N],cnt[N],pre[N],f[N][Lg];
signed main()
{
	freopen("stone.in","r",stdin); freopen("stone.out","w",stdout);
	n=read(); lg=log2(n)+1;
	for(int i=1;i<=n;i++) s[i]=read(),cnt[s[i]]++;
	for(int i=1;i<=n;i++) pre[i]=pre[i-1]+cnt[i];
	for(int i=n;i>=0;i--)
		for(int j=0;i+(1ll<<j)<=n;j++)
			f[i][j]=f[min(n+1,i+(1ll<<j+1))][j]+pre[min(n,i+(1ll<<j+1)-1)]-pre[i+(1ll<<j)-1];
	for(int i=1;i<=n;i++)
	{
		int lim=n/(i+1);
		for(int j=0;j<=lg;j++)
		{
			int sum=0;
			for(int k=0;k<=lim;k++)
			{
				int l=k*(i+1),r=min(n,(k+1)*(i+1)-1),temp=(r-l+1)>>j+1;
				int tot=f[l][j]-f[l+temp*(1ll<<j+1)][j];
				if(l+temp*(1ll<<j+1)+(1ll<<j)-1<r) tot+=pre[r]-pre[l+temp*(1ll<<j+1)+(1ll<<j)-1];
				sum+=tot;
			}
			if(sum&1){printf("Alice ");goto V;}
		}
		printf("Bob "); V:;
	}
	return 0;
}

T2 大鱼吃小鱼

解题思路

set+桶可以得到 60pts (code)

线段树上二分每一次优先递归右区间从右区间贪心选择,并且记录下更改过的值,在处理完答案之后再复原回去。

处理的时候类似于区间覆盖 laz 标记。

同时用 set 维护一个有序数组用于查看当前值的下一个应该选哪个

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=8e6+10,INF=1e18;
int n,m,ans,top,now,cnt,sta[N],s[N],lsh[N];
struct Ques{int opt,x,y;}q[N];
struct Node{int dat,bas,laz;}ys[N];
multiset<int> res;
bool can=true,jud=true;
struct Segment_Tree
{
	bool vis[N<<2]; Node tre[N<<2];
	int ceil(int x,int y){return x/y+(x%y!=0);}
	void push_up(int x)
	{
		if(can&&!vis[x]) sta[++top]=x,ys[top]=tre[x],vis[x]=true;
		tre[x].dat=tre[ls].dat+tre[rs].dat;
		tre[x].bas=tre[ls].bas+tre[rs].bas;
	}
	void push_down(int x)	
	{
		if(!tre[x].laz) return ;
		if(!vis[ls]) sta[++top]=ls,ys[top]=tre[ls],vis[ls]=true;
		if(!vis[rs]) sta[++top]=rs,ys[top]=tre[rs],vis[rs]=true;
		tre[ls].dat=tre[rs].dat=tre[ls].bas=tre[rs].bas=0; tre[ls].laz=tre[rs].laz=1; tre[x].laz=0;
	}
	void insert(int x,int l,int r,int pos,int val)
	{
		if(l==r) return tre[x].bas+=val,tre[x].dat+=val*lsh[l],void();
		int mid=(l+r)>>1;
		if(pos<=mid) insert(ls,l,mid,pos,val);
		else insert(rs,mid+1,r,pos,val);
		push_up(x);
	}
	int query(int x,int l,int r,int L,int R)
	{
		if(L>R||!tre[x].dat) return 0;
		if(L<=l&&r<=R) return tre[x].dat;
		int mid=(l+r)>>1,sum=0; push_down(x);
		if(L<=mid) sum+=query(ls,l,mid,L,R);
		if(R>mid) sum+=query(rs,mid+1,r,L,R);
		return sum;
	}
	void solve(int x,int l,int r,int L,int R,int &need)
	{
		if(l==r)
		{
			if(tre[x].dat<need) goto V;
			if(!vis[x]) sta[++top]=x,ys[top]=tre[x],vis[x]=true;
			int temp=ceil(need,lsh[l]); 
			tre[x].bas-=temp; ans+=temp;
			now+=temp*lsh[l]; need-=temp*lsh[l];
			jud=false; tre[x].dat=tre[x].bas*lsh[l];
			return ;
		} V:;
		if(r<=R&&tre[x].dat<=need)
		{
			need-=tre[x].dat; now+=tre[x].dat;
			if(need<=0) jud=true; ans+=tre[x].bas;
			if(!vis[x]) sta[++top]=x,ys[top]=tre[x],vis[x]=true;
			tre[x].dat=tre[x].bas=0; tre[x].laz=1; return ;
		}
		int mid=(l+r)>>1; push_down(x);
		if(R<=mid) return solve(ls,l,mid,L,R,need),push_up(x),void();
		if(jud) solve(rs,mid+1,r,L,R,need);
		if(jud) solve(ls,l,mid,L,R,need);
		push_up(x);
	}
}T;
void Restore()
{
	for(int i=1;i<=top;i++)
		T.tre[sta[i]]=ys[i],
		T.vis[sta[i]]=false,T.tre[sta[i]].laz=0;
}
void solve(int bas,int goal)
{
	if(bas>=goal) return printf("0\n"),void();
	int temp=goal-bas,rec,need; now=bas; ans=top=0;
	while(temp>0&&now<goal)
	{
		auto it=res.lower_bound(now);
		if(it!=res.end()) rec=*it;
		else  rec=INF;
		need=min(temp,rec-now+1); jud=true;
		int las=need,pos=lower_bound(lsh+1,lsh+cnt+1,now)-lsh-1;
		if(!pos) break;
		T.solve(1,1,cnt,1,pos,need);
		if(need>0||it==res.end()) break;
		temp-=las-need;
	}
	Restore();
	if(need<=0) printf("%lld\n",ans); else printf("-1\n"); 
}
signed main()
{
	freopen("fish.in","r",stdin); freopen("fish.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++) s[i]=read(),lsh[++cnt]=s[i],res.insert(s[i]);
	m=read();
	for(int i=1;i<=m;i++)
	{
		q[i].opt=read(); q[i].x=read();
		if(q[i].opt==1) q[i].y=read();
		lsh[++cnt]=q[i].x;
	}
	lsh[++cnt]=INF; sort(lsh+1,lsh+cnt+1); cnt=unique(lsh+1,lsh+cnt+1)-lsh-1; can=false;
	for(int i=1;i<=n;i++) T.insert(1,1,cnt,lower_bound(lsh+1,lsh+cnt+1,s[i])-lsh,1);
	can=true;
	for(int i=1;i<=m;i++)
	{
		int opt=q[i].opt,x=q[i].x,y=q[i].y;
		if(opt==1){solve(x,y);continue;}
		if(opt==2)
		{
			can=false; T.insert(1,1,cnt,lower_bound(lsh+1,lsh+cnt+1,x)-lsh,1);
			res.insert(x); can=true; continue;
		}
		can=false; T.insert(1,1,cnt,lower_bound(lsh+1,lsh+cnt+1,x)-lsh,-1);
		res.erase(res.find(x)); can=true;
	}
	return 0;
}

T3 黑客

解题思路

大水题。。

对于 \([1,999]\) 区间内任意两个互质的数,分别求出在 \([a,b],[c,d]\) 两个区间中的上下界。

取一个交集乘上对应的贡献就是答案了。

code

#include <bits/stdc++.h>
#define int long long 
#define ull unsigned long long
#define f() cout<<"Failed"
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=1e3+10,mod=1e9+7;
int a,b,c,d,ans;
vector<pair<int,int> > v;
inline void add(int &x,int y){x+=y;if(x>mod)x-=mod;}
signed main()
{
	freopen("hacker.in","r",stdin); freopen("hacker.out","w",stdout);
	a=read(); b=read(); c=read(); d=read();
	for(int i=1;i<=999;i++)
		for(int j=1;j<=999-i;j++)
			if(__gcd(i,j)==1)
				v.push_back(make_pair(i,j));
	for(int i=0;i<v.size();i++)
	{
		int x=v[i].first,y=v[i].second;
		int lima=ceil((1.0*a)/(1.0*x)),limb=floor((1.0*b)/(1.0*x));
		int limc=ceil((1.0*c)/(1.0*y)),limd=floor((1.0*d)/(1.0*y));
		int l=max(lima,limc),r=min(limb,limd);
		if(l<=r) add(ans,(r-l+1)%mod*(x+y)%mod);
	}
	printf("%lld",ans);
	return 0;
}

T4 黑客(续)

解题思路

丧心病狂考高精。。(改题的时候是我第一次打高精,竟然一遍过??)

数位 DP ,DP 方式有两种:传当前数字的状态或者对于后面数字的限制。

显然第二种 DP 复杂度是优于第一种的,但是第一种直接 __int128 强行压 17 位竟然可以过掉。。

经过本人实测倒数第三个测试点 dfs 函数 方法一调用了大约 25000 次,而方法二竟然只调用了 199次。

可谓天壤之别,剩下的就是板子了。。

code

#include <bits/stdc++.h>
#define ull unsigned long long
#define f() cout<<"Failed"
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=5e2+10,M=1000;
int n,m,k,can[10];
bool vis[10][10],pas[N][1<<9];
struct Node
{
	int a[M+10];
	void clear(){memset(a,0,sizeof(a));}
	inline Node friend operator << (Node x,int val)
	{
		if(!val) return x;
		int lim=M; while(lim&&!x.a[lim]) lim--;
		for(int i=lim;i>=1;i--) x.a[i+val]=x.a[i];
		for(int i=1;i<=val;i++) x.a[i]=0; return x;
	}
	inline Node friend operator * (Node x,int y)
	{
		int lim=M; while(lim&&!x.a[lim]) lim--;
		Node z; z.clear(); lim+=10;
		for(int i=1;i<=lim;i++) z.a[i]=x.a[i]*y;
		for(int i=1;i<=lim;i++) z.a[i+1]+=z.a[i]/10,z.a[i]%=10;
		return z;
	}
	inline Node friend operator + (Node x,Node y)
	{
		int lim=M; while(lim&&!x.a[lim]&&!y.a[lim]) lim--;
		Node z; z.clear(); lim++;
		for(int i=1;i<=lim;i++) z.a[i]=x.a[i]+y.a[i];
		for(int i=1;i<=lim;i++) z.a[i+1]+=z.a[i]/10,z.a[i]%=10;
		return z;
	}
	void print()
	{
		int lim=M; while(lim>1&&!a[lim]) lim--;
		for(int i=lim;i>=1;i--) printf("%d",a[i]);
		printf("\n");
	}
}f[N][1<<9],g[N][1<<9];
void dfs(int x,int lim)
{
	if(pas[x][lim]) return ; pas[x][lim]=true;
	if(x==n+1) return f[x][lim].a[1]=1,void();
	for(int i=1;i<=k;i++)
	{
		if((lim>>i-1)&1) continue;
		int p1=x+1,p2=lim|can[i]; dfs(p1,p2);
		f[x][lim]=f[x][lim]+f[p1][p2];
		g[x][lim]=g[x][lim]+(f[p1][p2]<<(n-x))*i+g[p1][p2];
	}
	return ;
}
signed main()
{
	freopen("hacker2.in","r",stdin); freopen("hacker2.out","w",stdout);
	n=read(); m=read(); k=read();
	for(int i=1,a,b;i<=m;i++) a=read(),b=read(),can[a]|=1<<(b-1);
	dfs(1,0); f[1][0].print(); g[1][0].print();
	return 0;
}

PS

尽管这一天是我生日,要是没有 星痕Force_A 的提醒还真就忘了。。

posted @ 2021-10-08 09:15  Varuxn  阅读(174)  评论(1编辑  收藏  举报