一共打了100+70+0=170的成绩。
题目不算太难,但是感觉思维还是有很多问题,就像英语完型错4个一样的思维错误,不站在作者的角度去理解文意,按自己的来想。同理,这里也要在出题人的角度思考。
T1.异或相加
- 题意:把序列A分成两部分,求两部分异或和再加起来的最大值。
- 思路:x+(xorsum ^ x)当xorsum的某一位是1时,+就相当于^不影响。当这一位是0时,如果x=0也不影响,但是x=1,就会使值再异或的基础上多值。所以我们先删掉等于xorsum等于1的位,然后跑线性基。
不过我考场上的思路很绕,我想的是 \(a+b=a\ xor\ b+(a\&b)<<1\) 所以尽量要a,b两项高位都为1,即两部分分别都要有奇数个该位为1的数,因此如果数列中该位唯一的数有偶数个根本不可能,还会搅局,删掉,然后跑线性基,我的思路很绕,所以qaq…… - code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int M=65;
int up=60;
ll Bs[M],a[N];
int cnt[M];
void Insert(ll y) {
for(int i=up;i>=0;i--) {
if((y>>i)&1) {
if(!Bs[i]) {Bs[i]=y;return;}
else y^=Bs[i];
}
}
}
ll Mx() {
ll res=0;
for(int i=up;i>=0;i--) res=max(res,res^Bs[i]);
return res;
}
int main() {
// freopen("xorfirst.in","r",stdin);
// freopen("xorfirst.out","w",stdout);
int n;ll xos=0,sum=0,del=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),sum^=a[i];
for(int i=1;i<=n;i++) {
for(int j=up;j>=0;j--) {
if((a[i]>>j)&1) cnt[j]++;
}
}
for(int i=0;i<=up;i++)if(cnt[i]%2==0)del^=(1ll<<i);
for(int i=1;i<=n;i++)a[i]&=del;
for(int i=1;i<=n;i++)Insert(a[i]),xos^=a[i];
ll mx=Mx();
printf("%lld\n",sum+((mx&(xos^mx))<<1));
return 0;
}
T2.彩旗
- 题意:有n棵树第i棵要种在xi或yi处,求最小距离的最大值
- 思路:首先二分+2-SAT。关键是建图,我没有写线段树优化建图,因为太菜了。
然后就搞了一个算法(因为线段树优化建图的数据范围都很小),搞了70分,数组开小了其实可以搞85分的。
就是先把所有的x,y搞在一起排序,然后得到新数组p[1...2n],然后再在p中选n个点最小距离最大(这个直接二分判断即可),因此就找到的这个值就是二分的上界up。
这个上界优化非常致命,我不会证明,考场上随机也没有卡掉自己,甚至跑的飞快。不过理论上up最大为\(10^5\),当分布均匀不过这种情况好像连的边又很少。我猜是那种一堆很紧密然后少量很稀疏的,不过还是想的不是很明白……
线段树优化建图要两棵线段树,一棵出数(叶子为p[]),一棵入树(叶子节点为_[p[]])当然线段树加上上界优化就更完美了。 - code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int M=2e7+5;
struct fc {int x,y;}b[N];
struct node {int x,id;}a[N];
struct seg {int l,r;}T[N];
int dta,lf1[N],lf2[N];
int nxt[M],to[M],head[N],ecnt;
int _[N],up,n,val[N],nn,m;
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
bool cmp(node u,node v) {return u.x<v.x;}
void _llaa(int x,int l,int r) {
T[x]=T[x+dta]=(seg){l,r};
if(l==r){lf1[a[l].id]=x,lf2[_[a[l].id]]=x+dta;return;}
int mid=(l+r)>>1,ls=x<<1,rs=ls|1;
add_edge(ls,x),add_edge(rs,x),add_edge(x+dta,ls+dta),add_edge(x+dta,rs+dta);
_llaa(ls,l,mid),_llaa(rs,mid+1,r);
}
void _Link(int x,int l,int r,int fr) {
if(l<=T[x].l&&T[x].r<=r) {add_edge(lf1[fr],x+dta);return;}
int mid=(T[x].l+T[x].r)>>1;
if(l<=mid)_Link(x<<1,l,r,fr);
if(r>mid)_Link(x<<1|1,l,r,fr);
}
void Link_(int x,int l,int r,int tto) {
if(l<=T[x].l&&T[x].r<=r) {add_edge(x,lf2[tto]);return;}
int mid=(T[x].l+T[x].r)>>1;
if(l<=mid)Link_(x<<1,l,r,tto);
if(r>mid)Link_(x<<1|1,l,r,tto);
}
void init_tree() {
_llaa(1,1,nn);
for(int i=1;i<=nn;i++) add_edge(lf2[i],lf1[i]);
}
bool check(int mid) {
int lst=a[1].x,cnt=1;
for(int i=2;i<=nn;i++) {
if(a[i].x>=lst+mid) {
++cnt;lst=a[i].x;if(cnt>=n)return 1;
}
}
return (cnt>=n);
}
void gt_up() {
int l=0,r=1e9;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid))up=mid,l=mid+1;
else r=mid-1;
}
// printf("!%d\n",up);
}
int Bl[N],SCC,dfn[N],low[N],Time,tp,st[N];
bool In_s[N];
void Tarjan(int u) {
dfn[u]=low[u]=++Time;st[++tp]=u;In_s[u]=1;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(!dfn[v]) {Tarjan(v);low[u]=min(low[u],low[v]);}
else if(In_s[v])low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]) {
++SCC;int v;
do {
v=st[tp--];In_s[v]=0;Bl[v]=SCC;
}while(u!=v);
}
}
bool _rd() {for(int i=1;i<=n;i++)if(Bl[lf2[i]]==Bl[lf2[_[i]]])return 0;return 1;}
void Build(int mid) {
init_tree();
int j=1;
for(int i=1;i<=nn;i++) {
while(j<i&&a[j].x+mid<=a[i].x) {j++;}
if(j!=i) {
_Link(1,j,i-1,a[i].id),Link_(1,j,i-1,_[a[i].id]);
}
}
for(int i=1;i<=(dta<<1);i++)if(!dfn[i])Tarjan(i);
}
void Clear() {ecnt=tp=Time=SCC=0;for(int i=1;i<=(dta<<1);i++)low[i]=dfn[i]=Bl[i]=head[i]=0;}
void solve() {
int l=0,r=up,best=0;
while(l<=r) {
int mid=(l+r)>>1;
Build(mid);
if(_rd()) best=mid,l=mid+1;
else r=mid-1;
Clear();
}
printf("%d",best);
}
int main() {
scanf("%d",&n);nn=n<<1;dta=nn<<2;
for(int i=1;i<=n;i++) scanf("%d%d",&b[i].x,&b[i].y),a[(i<<1)-1]=(node){b[i].x,i},a[i<<1]=(node){b[i].y,i+n},_[i]=i+n,_[i+n]=i;
sort(a+1,a+1+nn,cmp);
gt_up();
solve();
return 0;
}
T3.改色找根
- 题意:问你最少的步数存在一个跟,使得所有颜色为c[rt]的点到跟上没有!=c[rt]的点
- 思路:点分治,对于当前子树根,找到c[rt]的所有点(如果有子树外的话,就结束因为存在包含所有该颜色的根的更优),枚举每个点往上跳,如果遇到新的颜色就再看新的颜色的所有点到根又有没有新的颜色(同前)……发现是个递归问题吧。bfs就可以了,队列里面存颜色,每个颜色按上面的操作把新颜色又推进队列,当前答案就是新的颜色个数,每个颜色只需要入队一次这样也保证了子树\(O(sz)\)的复杂度。点分治复杂度\(O(nlog_2n)\)
- code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
vector<int> V[N];
int f[N],ans=1e9,root,sz[N],c[N],nxt[N],to[N],head[N],ecnt,cnt[N],st[N],tp,smx[N];
bool vis[N],mark[N];
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void gt_sz(int u,int fa) {
sz[u]=1;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];if(vis[v]||v==fa)continue;
gt_sz(v,u);sz[u]+=sz[v];
}
}
void gt_rt(int u,int fa,int tot) {
smx[u]=tot-sz[u];
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];if(vis[v]||v==fa)continue;
gt_rt(v,u,tot);smx[u]=max(smx[u],sz[v]);
}
if(smx[u]<smx[root])root=u;
}
void init(int u) {
if(!cnt[c[u]]) {st[++tp]=c[u];}cnt[c[u]]++;
// printf("%d %d\n",u,f[u]);
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(vis[v]||v==f[u])continue;
f[v]=u;init(v);
}
}
int hd,tl,Q[N];
void Clear() {while(tp) {mark[st[tp]]=cnt[st[tp]]=0;tp--;}}
void solve(int u) {
f[u]=0;init(u);Q[hd=tl=1]=c[u];mark[Q[1]]=1;
int res=0;
while(hd<=tl) {
int x=Q[hd++];res++;
if(cnt[x]<V[x].size()) {Clear();return;}
for(int i=0;i<V[x].size();i++) {
int y=V[x][i];
if(y==u)continue;y=f[y];
while(y!=u) {
if(mark[c[y]])break;
Q[++tl]=c[y];mark[c[y]]=1;
y=f[y];
}
}
}
// printf("%d\n",res-1);
Clear();ans=min(ans,res-1);
}
void Divide(int x) {
root=0;gt_rt(x,0,sz[x]);vis[x=root]=1;
gt_sz(x,0);solve(x);
for(int i=head[x];i;i=nxt[i]) {
int v=to[i];if(vis[v])continue;
Divide(v);
}
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++) {int u,v;scanf("%d%d",&u,&v);add_edge(u,v),add_edge(v,u);}
for(int i=1;i<=n;i++) {scanf("%d",&c[i]);V[c[i]].push_back(i);}
smx[0]=ans;gt_sz(1,0);Divide(1);
printf("%d",ans);
return 0;
}
ps.我跑的很慢,接近1s了。