上次挂得太惨,这次稍微强了点,但也是一般, 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(1≤n≤105) 个开关,和一个初始值为
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(0≤ai≤5×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,⋯,bk−1,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 2k−1,但是此时 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=1kbj≤k×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 2k−1>k×5×105≥∑j=1kbj 时,就一定存在合法的两组数,而该不等式转化得 2 k − 1 k > 5 × 1 0 5 \frac{2^k-1}{k}>5\times 10^5 k2k−1>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(1≤n≤1000) 堆石子,每堆石子个数都为 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,x2−1,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,x2−1,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) (x1−1,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) n≡2(mod3) 时后手胜,反之先手胜,这是出题人大佬的结论,没给证明,本蒟蒻也不会证,大家有兴趣可以自行研究。
T3
题目
改版的背包问题, n ( 1 ≤ n ≤ 36 ) n(1\leq n\leq 36) n(1≤n≤36) 个物品, m ( 1 ≤ m ≤ 1 0 9 ) m(1\leq m\leq10^9) m(1≤m≤109) 大小的背包,第 i i i 个物品的大小为 v i ( 1 ≤ v i ≤ 1 0 9 ) v_i(1\leq v_i\leq 10^9) vi(1≤vi≤109),价值为 w i ( ∣ w i ∣ ≤ 1 0 9 ) w_i(|w_i|\leq10^9) wi(∣wi∣≤109)。总价值的计算方式仍是相加的总和,但总大小的计算方式是全部异或起来的结果(甚至可能是 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;
}