ZR CSP七连 D2

上次挂得太惨,这次稍微强了点,但也是一般, 100 + 50 + 30 + 0 = 180 p t s 100+50+30+0=180pts 100+50+30+0=180pts,排名在 100 100 100~ 150 150 150。不过值得一提的是这次数据弱爆了,T1、T2、T3 都可以暴力+剪枝水过,T4 的暴力分没拿到(爆 double 精度了)是我认为这次最大的遗憾。

T1

题目

给你 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n \leq 10^5) n(1n105) 个开关,和一个初始值为 0 0 0 的变量 s u m sum sum。同时第 i i i 个开关对应一个数 a i ( 0 ≤ a i ≤ 5 × 1 0 5 ) a_i(0\leq a_i\leq 5\times 10^5) ai(0ai5×105),并有 +- 两种状态,如果状态是 +,那么就将 s u m sum sum 加上 a i a_i ai;反之就将 s u m sum sum 减去 a i a_i ai。现在需要判断,有没有两种不同的设置开关状态的方式,使得得到的 s u m sum sum 值相同。

题解

首先可以自然地将原问题转化一下:对于选择-的第 i i i 个开关,在全部开关的状态是+时的 s u m sum sum 值(全部元素之和,即 ∑ j = 1 n a j \sum_{j=1}^na_j j=1naj)的基础上减去 2 × a i 2\times a_i 2×ai 即可。也就是说,只要从 a a a 中挑选两组不完全重合的数 a 1 a_1 a1 a 2 a_2 a2 满足元素之和相等即可。这个问题的解决方式本人因为太菜在赛时没有想出来,于是先特判有没有相同数或 0 0 0,再直接用暴力 O ( 2 n ) O(2^n) O(2n) 枚举 a i a_i ai 状态,为了防止超时,本人加入了乱搞方法——卡时,最终效果还不错,拿到了满分。下面说一下 std :

首先我们假设对于数组 b b b 中,不存在如上所述的和相等的两组数,那么我们考虑记录时使用一个 v i s vis vis 数组记录能够作为 b b b 中某些元素之和的数。很显然,如果真的不存在合法两组数,那么对于任何一个数,在 v i s vis vis 数组中不可能被记录一次以上。然而,当 v i s vis vis 数组记录了 b 1 , b 2 , ⋯   , b k − 1 , b k ( k < n ) b_1,b_2,\cdots,b_{k-1},b_k(k<n) b1,b2,,bk1,bk(k<n) 所构成的情况后,再考虑 b k + 1 b_{k+1} bk+1,此时对于原先的 ∀ v i s x = 1 \forall vis_x=1 visx=1,有 v i s x + b k + 1 = 1 vis_{x+b_{k+1}}=1 visx+bk+1=1,另外也有 v i s b k + 1 = 1 vis_{b_{k+1}}=1 visbk+1=1。因此如果每个数在 v i s vis vis 中都只被标记了 0 0 0 1 1 1 次,那么每多考虑 b b b 中的一个数, v i s vis vis 中被标记的数的个数就会变为原来的 2 2 2 倍并加 1 1 1。那么根据这样一个递推关系,我们就知道考虑 b b b k k k 个数时,想要满足合法的两组数不存在, v i s vis vis 中被标记的数的个数为 2 k − 1 2^k-1 2k1,但是此时 v i s vis vis 中能够被标记的最大数为 ∑ j = 1 k b j ≤ k × 5 × 1 0 5 \sum_{j=1}^kb_j\leq k\times 5\times 10^5 j=1kbjk×5×105。因此当 2 k − 1 > k × 5 × 1 0 5 ≥ ∑ j = 1 k b j 2^k-1>k\times 5\times 10^5\ge \sum_{j=1}^kb_j 2k1>k×5×105j=1kbj 时,就一定存在合法的两组数,而该不等式转化得 2 k − 1 k > 5 × 1 0 5 \frac{2^k-1}{k}>5\times 10^5 k2k1>5×105,解得 k > 24 k>24 k>24。因此我们在 n > 24 n>24 n>24 时可以直接判断存在,反之则暴力枚举,此时时间复杂度为 O ( 2 n ) ≤ O ( 2 24 ) ≈ O ( 1.7 × 1 0 7 ) O(2^n)\leq O(2^{24})≈O(1.7\times 10^7) O(2n)O(224)O(1.7×107) ,可以直接接受。另外,可以直接判断的 n n n 的下限其实并非 24 24 24,而是 20 20 20,答案为不存在的 n n n 最大的 a a a { 1 , 2 , 4 , 8 , ⋯   , 2 17 , 2 18 } \{1,2,4,8,\cdots,2^{17},2^{18}\} {1,2,4,8,,217,218}(想一想,为什么满足题意,为什么是最大),此时 n = 19 n=19 n=19

代码

我的代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[100010],cnt;
bool fg[15];
long long s[100010];
unordered_set<int>S;
unordered_set<long long>S2;
void dfs(int x)
{
	if(x==n+1||(double)clock()/CLOCKS_PER_SEC>=0.94)
	{
		int sum=0;
		for(int i=1;i<=n;i++)
		{
			if(fg[i])
				sum+=a[i];
			else
				sum-=a[i];
		}
		if(S.count(sum))		
		{
			printf("Yes");
			exit(0);
		}
		S.insert(sum);
		return;
	}
	dfs(x+1);
	fg[x]=1;
	dfs(x+1);
	fg[x]=0;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++)
		if(a[i]==a[i-1])
		{
			printf("Yes");
			return 0;
		}
	dfs(1);
	printf("No");
	return 0;
}

std:

#include<bits/stdc++.h>
using namespace std;
int n,a[100010];
bool o;
unordered_set<int>S; 
void dfs(int now,int sum)
{
	if(o)
		return;
	if(now==n+1)
	{
		if(S.count(sum)) 
			o=1;
		S.insert(sum);
		return;
	}
	dfs(now+1,sum+a[now]);
	dfs(now+1,sum);
}
int main()
{
	scanf("%d",&n);
	if(n>=20)
	{
		cout<<"Yes";
		return 0;
	}
	for(int i=1;i<=n;i++) 
		scanf("%d",&a[i]),a[i]<<=1;
	dfs(1,0);//暴搜 
	printf(o?"Yes":"No");
	return 0;
}

T2

题目

n ( 1 ≤ n ≤ 1000 ) n(1\leq n \leq 1000) n(1n1000) 堆石子,每堆石子个数都为 2 2 2,进行改编版的 Nim 游戏:规则与普通 Nim 游戏相同,但是每进行三轮后就再增加一堆个数为 2 2 2 的堆(无论这一轮有没有取完)。判断先手(Ayano)还是后手(Bob)必胜。

题解

比赛时我就直接输出的后手必胜,得了 50 p t s 50pts 50pts

这还是一个典型的有向图游戏。每堆石子要么没取,也就是个数为 2 2 2 ;或者被取了 1 1 1 个,个数为 1 1 1;否则被取完,可以直接认为没有这一堆。那么状态就可以设计为 ( x 1 , x 2 , s ) (x_1,x_2,s) (x1,x2,s) ,表示状态有 x 1 x_1 x1 1 1 1 x 2 x_2 x2 2 2 2 ,进行取的轮数模 3 3 3 的余数为 s s s(设计 s s s 是为了特判添加一堆的时间)。显然当 s = 0 s=0 s=0 1 1 1 时,状态 ( x 1 , x 2 , s ) (x_1,x_2,s) (x1,x2,s) 可以转化为 ( x 1 + 1 , x 2 − 1 , s + 1 ) (x_1+1,x_2-1,s+1) (x1+1,x21,s+1)(在一堆个数为 2 2 2 的石子中取出一个,前提是 x 2 > 0 x_2>0 x2>0)、 ( x 1 , x 2 − 1 , s + 1 ) (x_1,x_2-1,s+1) (x1,x21,s+1)(取完一堆个数为 2 2 2 的石子,前提也是 x 2 > 0 x_2>0 x2>0)、 ( x 1 − 1 , x 2 , s + 1 ) (x_1-1,x_2,s+1) (x11,x2,s+1)(取完一堆个数为 1 1 1 的石子, x 1 > 0 x_1>0 x1>0); s = 2 s=2 s=2 的情况也差不多,只不过结果中 x 2 x_2 x2 一项要加 1 1 1,同时 s s s 变为 0 0 0。最后就可以从状态 ( 0 , n , 0 ) (0,n,0) (0,n,0) 开始(初始状态是第 0 0 0 次取),不断拓展,然后计算 S G SG SG 函数(当然要记忆化,否则会 T 飞)并判断 S G ( 0 , n , 0 ) SG(0,n,0) SG(0,n,0) 是否等于 0 0 0 即可(等于 0 0 0 后手必胜,反之先手必胜)。下面给出代码:

代码

#include<bits/stdc++.h>
using namespace std;
const int dx1[3]={0,1,-1},dx2[3]={-1,-1,0};
int n,f[1210][1210][3];//记忆化记录答案
int dfs(int x1,int x2,int s)
{
	if(f[x1][x2][s]!=-1)
		return f[x1][x2][s];
	if(x1==0&&x2==0)
		return 0;
	set<int>S;//懒得写哈希表了,直接用 set 筛
	S.clear();
	for(int i=0;i<3;i++)
	{
		int nx1=x1+dx1[i],nx2=x2+dx2[i];
		if(nx1>=0&&nx2>=0)
			S.insert(dfs(nx1,nx2+(s==2),(s+1)%3));
	}
	for(int i=0;;i++)//mex
		if(!S.count(i))
			return f[x1][x2][s]=i;//SG
}
int main()
{
	memset(f,-1,sizeof(f));
	scanf("%d",&n);
	printf(dfs(0,n,0)?"Ayano":"Bob");
	return 0;
}

事实上,当且仅当 n ≡ 2 ( m o d    3 ) n≡2(\mod 3) n2(mod3) 时后手胜,反之先手胜,这是出题人大佬的结论,没给证明,本蒟蒻也不会证,大家有兴趣可以自行研究。

T3

题目

改版的背包问题, n ( 1 ≤ n ≤ 36 ) n(1\leq n\leq 36) n(1n36) 个物品, m ( 1 ≤ m ≤ 1 0 9 ) m(1\leq m\leq10^9) m(1m109) 大小的背包,第 i i i 个物品的大小为 v i ( 1 ≤ v i ≤ 1 0 9 ) v_i(1\leq v_i\leq 10^9) vi(1vi109),价值为 w i ( ∣ w i ∣ ≤ 1 0 9 ) w_i(|w_i|\leq10^9) wi(wi109)。总价值的计算方式仍是相加的总和,但总大小的计算方式是全部异或起来的结果(甚至可能是 0 0 0!),求背包能装下的前提下可以获得的最大价值。

题解

比赛时我只是打了个没带剪枝的暴力搜索, 30 p t s 30pts 30pts 滚粗。

大部分能过的暴力都是加了一个最优性剪枝。具体是这样的:计算物品价值的后缀和,然后仍是从前往后搜索,但是如果当前的价值 w c u r w_{cur} wcur 与剩下没搜到的物品价值之和小于等于当前最优答案就直接回溯。

std的思路巧妙~~(不过跑起来比暴搜慢多了,应该是良心出题人故意放水)~~,是先折半搜索,搜出来的结果存下大小异或和、权值和。然后放在 01trie 上, 再进行一遍 dfs,找到答案。

代码

暴力:

#include<bits/stdc++.h>
using namespace std;
int n,m;
long long mx,a[40],b[40],mxb[40];
void dfs(int x,long long sum,long long sm)
{
	if(x==n+1)
	{
		if(sum<=m)
			mx=max(mx,sm);
		return;
	}
	if(sm+mxb[x]<=mx)
		return;
	dfs(x+1,sum,sm);
	dfs(x+1,sum^a[x],sm+b[x]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",a+i);
	for(int i=1;i<=n;i++)
		scanf("%lld",b+i);
	for(int i=n;i>=1;i--)
		mxb[i]=mxb[i+1]+max(0ll,b[i]);//后缀和
	dfs(1,0,0);
	printf("%lld",mx);
	return 0;
}

std:

#include<bits/stdc++.h>
using namespace std;
struct node
{
	int to[2];
	long long val;
	node()
	{
		val=-1e17;
		to[0]=to[1]=-1;
	}
};
int n,m;
vector<node>trie;
pair<long long,long long>goods[40];
vector<int>calc_dig(int p)
{
	vector<int>res;
	for(int i=0;i<31;i++)
		res.push_back(p&1),p>>=1;
	reverse(res.begin(),res.end());
	return res;
}
void add(int p,long long val,int head)
{
	auto v=calc_dig(p);
	trie[head].val=max(trie[head].val,val);
	for(int i=0;i<31;i++)
	{
		int b=v[i];
		trie[head].val=max(trie[head].val,val);
		if(trie[head].to[b]<0)
		{
			trie[head].to[b]=trie.size();
			trie.push_back(node());
		}
		head=trie[head].to[b];
		trie[head].val=max(trie[head].val,val);
	}
}
long long dfs(int now,int lid,int rid,vector<int>&dig)
{
	if(now==31)
		return trie[lid].val+trie[rid].val;
	long long res=0;
	if(dig[now])
	{
		int nxtl=trie[lid].to[1],nxtr=trie[rid].to[0];
		if(nxtl>=0&&nxtr>=0)
			res=max(res,dfs(now+1,nxtl,nxtr,dig));
		nxtl=trie[lid].to[0],nxtr=trie[rid].to[1];
		if(nxtl>=0&&nxtr>=0)
			res=max(res,dfs(now+1,nxtl,nxtr,dig));
		nxtl=trie[lid].to[0],nxtr=trie[rid].to[0];
		if(nxtl>=0&&nxtr>=0)
			res=max(res,trie[nxtl].val+trie[nxtr].val);
		nxtl=trie[lid].to[1],nxtr=trie[rid].to[1];
		if(nxtl>=0&&nxtr>=0)
			res=max(res,trie[nxtl].val+trie[nxtr].val);
	}
	else
	{
		int nxtl=trie[lid].to[0],nxtr=trie[rid].to[0];
		if(nxtl>=0&&nxtr>=0)
			res=max(res,dfs(now+1,nxtl,nxtr,dig));
		nxtl=trie[lid].to[1],nxtr=trie[rid].to[1];
		if(nxtl>=0&&nxtr>=0)
			res=max(res,dfs(now+1,nxtl,nxtr,dig));
	}
	return res;
}
long long solve()
{
	int lid=0,rid=0;
	auto make_trie=[](int l,int r)
	{
		int head=trie.size(),len=r-l;
		trie.push_back(node());
		for(int i=0;i<(1<<len);i++)
		{
			long long sum=0,p=0;
			for(int j=0;j<len;j++)
				if(i>>j&1)
					sum+=goods[l+j].second,p^=goods[l+j].first;
			add(p,sum,head);
		}
		return head;
	};
	lid=make_trie(0,n/2);
	rid=make_trie(n/2,n);
	vector<int>dig=calc_dig(m);
	return dfs(0,lid,rid,dig);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;++i)
		scanf("%lld",&goods[i].first);
	for(int i=0;i<n;++i)
		scanf("%lld",&goods[i].second);
	printf("%lld",solve());
	return 0;
}

T4

题目

给出 n n n 条直线 y i = k i x + b y_i=k_ix+b yi=kix+b,对于给出的每个 x x x,求出 k i ( k j x + b j ) + b i ( i ≠ j ) k_i(k_jx+b_j)+b_i(i≠j) ki(kjx+bj)+bi(i=j) 的最大值。

题解

赛时本人的暴力居然爆了 double 精度,否则应该能拿到 60 p t s 60pts 60pts

显然当 k i < 0 k_i<0 ki<0 y j y_j yj 应取 y m i n y_{min} ymin k i > 0 k_i>0 ki>0 y j y_j yj 应取 y m a x y_{max} ymax k i = 0 k_i=0 ki=0 y j y_j yj 取任何值都没有影响。

那么也就是说,对于 x x x,只有 y m i n y_{min} ymin y m a x y_{max} ymax 有成为答案 k i y j + b i k_iy_j+b_i kiyj+bi 的可能,那么我们很自然地想到使用李超线段树维护。但是还忽略了一个条件: i ≠ j i≠j i=j。不过也比较容易解决,只需要讨论一下让外层的 i i i 等于内层时的情况,维护次小值即可。

代码

#include<bits/stdc++.h>
using namespace std;
long long Lim=2000000000000;
struct Line
{
	long long k,b,id;
	long long calc(long long x)
	{
		return k*x+b;
	}
};
struct tr
{
	long long lson,rson,flg;
	Line l;
};
struct Res
{
	long long val,id;
};
Res max(Res a,Res b)
{
	return a.val>b.val?a:b;
}
struct Li_Chao_Tree
{
	long long tot=0,root[100010],time=0;
	tr t[4500010];
	long long trs(long long l,long long r,Line a,Line b)
	{
		if((a.calc(l)>=b.calc(l))&&(a.calc(r)>=b.calc(r))) 
			return 1;
		if((a.calc(l)<b.calc(l))&&(a.calc(r)<b.calc(r))) 
			return -1;
		return 0;
	}
	#define mid (l+r>>1)
	void change(long long &p,long long q,long long l,long long r,Line L)
	{
		p=++tot;
		t[p]=t[q];
		if(!t[p].flg) 
			return t[p].flg=1,t[p].l=L,void();
		long long v=trs(l,r,t[p].l,L);
		if(v==1) 
			return;
		if(v==-1) 
		{
			t[p].l=L;
			return;
		}
		v=trs(l,mid,t[p].l,L);
		if(abs(v))
		{
			if(v==1) 
				change(t[p].rson,t[q].rson,mid+1,r,L);
			else
			{
				Line ll=t[p].l;
				t[p].l=L;
				change(t[p].rson,t[q].rson,mid+1,r,ll);
			}
		}
		else
		{
			v=trs(mid+1,r,t[p].l,L);
			if(v==1) 
				change(t[p].lson,t[q].lson,l,mid,L);
			else
			{
				Line ll=t[p].l;
				t[p].l=L;
				change(t[p].lson,t[q].lson,l,mid,ll);
			}
		}
		return;
	}
	void Change(Line l)
	{
		time++;
		change(root[time],root[time-1],-Lim,Lim,l);
		return ;
	}
	Res query(long long p,long long l,long long r,long long x)
	{
		if(!p||!t[p].flg||x<l||x>r) 
			return {LLONG_MIN,0};
		if(l==r) 
			return {t[p].l.calc(x),t[p].l.id};
		Res tmp={t[p].l.calc(x),t[p].l.id};
		if(t[p].l.id==0) 
			tmp={LLONG_MIN,0};
		if(x<=mid) 
			tmp=max(tmp,query(t[p].lson,l,mid,x));
		else 
			tmp=max(tmp,query(t[p].rson,mid+1,r,x));
		return tmp;
	}
}t1,t2,t3,t4;
int n,q;
long long k[100010],b[100010],x;
Res ccalc(long long id,long long val)
{
	Res ans={LLONG_MIN,0};
	if(id>1) 
		ans=max(ans,t1.query(t1.root[id-1],-Lim,Lim,val));
	if(id<n) 
		ans=max(ans,t2.query(t2.root[n-id],-Lim,Lim,val));
	return ans;
}
long long calc(long long x)
{
	Res res1=t1.query(t1.root[n],-Lim,Lim,x),res2=t3.query(t3.root[n],-Lim,Lim,x);
	Res ans={LLONG_MIN,0};
	ans=max(ans,ccalc(res1.id,res1.val));
	ans=max(ans,ccalc(res2.id,-res2.val));
	if(res1.id>1)
	{
		Res Res1=t1.query(t1.root[res1.id-1],-Lim,Lim,x);
		ans=max(ans,ccalc(Res1.id,Res1.val));
	}
	if(res1.id<n)
	{
		Res Res1=t2.query(t2.root[n-res1.id],-Lim,Lim,x);
		ans=max(ans,ccalc(Res1.id,Res1.val));
	}
	if(res2.id>1)
	{
		Res Res2=t3.query(t3.root[res2.id-1],-Lim,Lim,x);
		ans=max(ans,ccalc(Res2.id,-Res2.val));
	}
	if(res2.id<n)
	{
		Res Res2=t4.query(t4.root[n-res2.id],-Lim,Lim,x);
		ans=max(ans,ccalc(Res2.id,-Res2.val));
	}
	return ans.val;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
		scanf("%lld%lld",&k[i],&b[i]);
	for(int i=1;i<=n;i++) 
		t1.Change({k[i],b[i],i});
	for(int i=n;i;i--)
		t2.Change({k[i],b[i],i});
	for(int i=1;i<=n;i++) 
		t3.Change({-k[i],-b[i],i});
	for(int i=n;i;i--)
		t4.Change({-k[i],-b[i],i});
	scanf("%d",&q);
	while(q--) 
	{
		scanf("%lld",&x);
		printf("%lld\n",calc(x));
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值