板刷 CF 记录
全部为 EDU 场次。
CF EDU 157
T1
首先明确,必须要到 \(y\),所以答案至少是 \(y\),发现还有箱子这一回事,考虑分类讨论,如果我们先碰到钥匙,就可以直接带着钥匙去找箱子。如果先遇到箱子,就能搬几步,剩下去到钥匙拿回来即可。
感觉没啥 trick 或者好总结的,就是需要注意到必须要到 \(y\),感觉挺显然的。评分 *\(800\) 分。
T2
考虑到这个式子,只是 \(x\) 减 \(x\) 或者 \(y\) 减 \(y\),所以直接全拍了,然后分别维护两个指针移动统计即可。
我们尝试证明一下排序是对的,绝对值的几何意义是两数的差距,那么我们希望每两个数的差距都尽可能的小,则排序是最优的。然后考虑为什么要整个排序,还是考虑交换,只会使得差距增大,然后就证出来了。
这个排序缩小查询是一个非常简单的 trick,然后没了。
T3
简单题。考虑对于每一个字符串,我们枚举每个前缀作为一半的情况,然后就是找一个数,能使得它的后本部分加上这整个串的和等于前半段,还有一个限制是长度需要相等。我们考虑 vector,把所有长度 \(=x\) 的值丢进 v[x],查询时直接二分即可。
重点还是 vector 分类这个 trick 吧。
T4
牛牛题。我的思路就是,考虑 \(0\) 是关键,假设 \(0\) 在 \(i\) 位置,我们枚举 \(i\),前面的就是 \(b\) 所有以 \(i\) 结尾的所有后缀异或和,后面类似,以 \(i+1\) 为前缀的所有前缀异或和。
然后这里有一个关键转化/trick:我们只需要后面的 \(b\) 与前面 \(b\) 均小于 \(n\),就能把所有 \(b\) 都控制在 \(0\)~\(n-1\) 内。注意到有所有 \(b\) 不同,因为题目保证有解,则只要最大的 \(b\) 小于 \(n\),所有又不同,就会刚好被映射进 \(0\)~\(n-1\)。
还需要有一个步骤,我们如何维护这个最大值呢?我们假设要求前缀,后缀直接仿照做就行。考虑我们把 \(i\) 前面所有的前缀异或和插进一个 0/1 Tie 里。然后假设一个区间对应 \([1,p]\),我们考虑给异或上一个 \(1\) 到 \(i\) 的异或和,把它设为 \(x\),现在就会变成 \([p+1,n]\) 这个区间的异或和。那么就只要在 01 Trie 里面查询与 \(x\) 异或的最大值即可。码量很大。
然后考虑另外一种更简单的做法,我们简单手玩发现 \(b_1\) 异或上 \(b_{i+1}\) 就等于 \(1\) 到 \(j\) 的前缀异或和。把这些前缀异或和插入 01 Trie,然后根据前面的结论,就是让这些里面的最大值最小,枚举 \(b_1\) 在 01 Trie 上判断即可
妙。
#include<bits/stdc++.h>
using namespace std;
int sum[200005],trie[6200005][2],a[200005],tot;
void insert(int x){
int p=0;
for (int i=30;i>=0;--i){
int a=(x>>i)&1;
if (!trie[p][a])trie[p][a]=++tot;
p=trie[p][a];
}
}
int query(int x){
int p=0,ans=0;
for (int i=30;i>=0;--i){
int a=(x>>i)&1;
if (trie[p][!a])ans+=(1<<i),p=trie[p][!a];
else p=trie[p][a];
}
return ans;
}
int main(){
int n;cin>>n;
for (int i=1;i<n;++i){
cin>>a[i];
sum[i]=sum[i-1]^a[i];
}
for (int i=0;i<n;++i)insert(sum[i]);
int b=0;
for (int i=0;i<n;++i){
if (query(i)<n){b=i;break;}
}
for (int i=0;i<n;++i)cout<<(b^sum[i])<<" ";
return 0;
}
T5
emm... 2300 感觉很困难。
我们称先手为 A,后手为 B。对于对方打出了一张牌,我们肯定选择一个攻击力能够管上,然后防御值尽可能大的牌,这样能尽可能压缩对手的出牌空间,所以对于每张牌队手管上的牌是一定的。然后考虑一个 DAG 博弈论模型,我们把每张牌视作一个节点,然后把牌和管它的牌连一条有向边边。如何连边呢?我们按照攻击力排序,枚举每张牌,然后二分找到对面牌池中第一个能够管上的牌,此时一定连向防御值的最大后缀值。
然后有一个结论是如果某一个节点出度是 \(0\),这个点就是能够令 A 必胜的。考虑经典博弈结论,必胜点一定连向必败点,必败点一定连向必胜点,那么先初始化必胜点状态,然后一边 DFS 即可。
代码很长。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int posa[N],posb[N],ta[N],tb[N];
struct KKK{
int x,y;
}a[N],b[N];
bool cmp(KKK p,KKK q){
return p.x<q.x;
}
struct edge{
int to,nxt;
}e[N*2];
int hd[N*2],tot;
void add(int u,int v){
e[++tot].to=v;
e[tot].nxt=hd[u];
hd[u]=tot;
}
int n,m;
int find1(int x){
int ans=0;
for (int i=30;i>=0;--i){
int y=(1ll<<i);
if (ans+y>n)continue;
if (a[ans+y].x<=x)ans+=y;
}
if (ans>=n)return -1;
else return ans+1;
}
int find2(int x){
int ans=0;
for (int i=30;i>=0;--i){
int y=(1ll<<i);
if (ans+y>m)continue;
if (b[ans+y].x<=x)ans+=y;
}
if (ans>=m)return -1;
else return ans+1;
}
int ans[N*2],vis[N*2];
void dfs(int u){
vis[u]=1;
for (int i=hd[u];i;i=e[i].nxt){
int v=e[i].to;
if (!vis[v]) dfs(v);
if (ans[v]!=-1) ans[u]=ans[v]^1;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int T;cin>>T;
while (T--){
cin>>n;
for (int i=1;i<=n;++i)cin>>a[i].x;
for (int i=1;i<=n;++i)cin>>a[i].y;
cin>>m;
for (int i=1;i<=m;++i)cin>>b[i].x;
for (int i=1;i<=m;++i)cin>>b[i].y;
sort(a+1,a+n+1,cmp);
sort(b+1,b+m+1,cmp);
ta[n+1]=0;
for (int i=n;i>=1;--i){
if (a[i].y>ta[i+1]){
ta[i]=a[i].y;
posa[i]=i;
}else{
ta[i]=ta[i+1];
posa[i]=posa[i+1];
}
}
tb[m+1]=0;
for (int i=m;i>=1;--i){
if (b[i].y>tb[i+1]){
tb[i]=b[i].y;
posb[i]=i;
}else{
tb[i]=tb[i+1];
posb[i]=posb[i+1];
}
}
tot=0;
for (int i=1;i<=n+m;++i)hd[i]=0;
for (int i=1;i<=n;++i){
int p=find2(a[i].y);
if (p==-1)continue;
add(i,posb[p]+n);
}
for (int i=1;i<=m;++i){
int p=find1(b[i].y);
if (p==-1)continue;
add(i+n,posa[p]);
}
for (int i=1;i<=n+m;++i){
ans[i]=-1;
vis[i]=0;
}
for (int i=1;i<=n+m;++i){
if (hd[i]==0)ans[i]=1;
}
for (int i=1;i<=n+m;++i){
if (!vis[i])dfs(i);
}
int aa=0,bb=0,cc=0;
for (int i=1;i<=n;++i){
if (ans[i]==1)aa++;
else if (ans[i]==0)bb++;
else cc++;
}
cout<<aa<<" "<<cc<<" "<<bb<<'\n';
}
}

浙公网安备 33010602011771号