【学习笔记】NOIP爆零赛8

trash ,但不完全是trash

t 1 t1 t1考了一个神奇的结论还没有证明, t 2 t2 t2玩了一些复杂度的花样, t 3 t3 t3稍微阳间一点,是一个并不复杂的容斥,如果放在 t 1 t1 t1可能更合适一些, t 4 t4 t4就是在原题的基础上改了一下然后就成了一道毒瘤数据结构题,

t 1 t1 t1总之感觉还是出的很烂,所以就不管它了

t 2 t2 t2暴力能过,很显然留在最后补

t 3 t3 t3赛时过了,那没什么了

所以只要胡一下 t 4 t4 t4就好是吗

补数据结构题是最痛苦的

最小生成树

首先有一道原题:[HNOI2010]城市建设 ,于是你不需要用这道题的任何性质就可以得到 80 p t s 80pts 80pts

这就是场上的最优解了,毕竟正解的思路非常人能及,而且也就少了 20 p t s 20pts 20pts而已,不过唯一的缺点是码量有点大

不过正解的话从链入手似乎非常合理,但是只有 40 p t s 40pts 40pts,最后的数据结构维护还是非常难想,所以这道题的性价比真的不高啊

对于链的情况,可以看成是 [ 1 , n ] [1,n] [1,n]的若干不相交区间 [ l i , r i ] [l_i,r_i] [li,ri]通过与 0 0 0节点连边从而联通,因此在用线段树维护区间信息时,只用处理中间两个连通块。如果都不与 0 0 0联通,那么不合法;如果都与 0 0 0联通,不需要花费代价就可以合并,如果只有一边与 0 0 0联通,那么需要花费中间那条二类边的代价。结合画图不难理解。

搞清楚链的情况后,我们就有了 40 p t s 40pts 40pts 好少啊,考场上思考数据结构完全没有动力啊

推广到一般情况,我们只需要一步:求出一棵树对应的等效链 。这看起来非常不可思议,但是如果你把两棵树合并看成两条链合并,然后套用链的维护方式就不难理解了。

这个地方很容易给人一个误解,就是直接将结论扩展好像可以一步到位。

事实上,我们还需要下一个结论:假设当前加的边是 u , v u,v u,v,其分属于连通块 S S S, T T T,那么我们可以把 u , v u,v u,v这条边等效成任意 u ′ ∈ S , v ′ ∈ T u'\in S,v'\in T uS,vT之间的连边,当然边权不变。其原因在于,如果 u , v u,v u,v这条边在 M S T MST MST中,那么此时 S , T S,T S,T一定是联通的(假设不是联通的,那么跑 kruskal \text{kruskal} kruskal算法的流程就会出现矛盾)。因此,我们可以把一棵树 彻底等效成一条链

基于上述观察,我们不难得到将所有的二类边构成的森林等价转化成若干条链,然后用线段树维护答案的做法。

最后是一些 d p dp dp赋初值以及状态转移的细节,如果看题解的话问题可能不太大,不过自己做的时候可能要多尝试一下

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)考场上能想到标算还是挺 n b nb nb

问题来了,为什么一个 log ⁡ \log log的代码跑得这么慢呢?

#include<bits/stdc++.h> #define ll long long #define pb push_back #define fi first #define se second #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int N=3e5+5; int n,m,Q,A[N],a[N],p[N],fa[N]; vector<int>w[N],v[N]; ll t[N<<2][2][2],val[N]; struct node{ int x,y,z; bool operator <(const node&a)const{ return z<a.z; } }e[N]; int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(int x,int y,int z){ x=find(x),y=find(y); if(x!=y){ if(v[x].size()<v[y].size())swap(x,y); fa[y]=x; for(auto X:v[y])v[x].pb(X); w[x].pb(z); for(auto X:w[y])w[x].pb(X); } } void pushup(int p,int val){ for(int i=0;i<2;i++){ for(int j=0;j<2;j++){ t[p][i][j]=inf; for(int k=0;k<2;k++){ for(int l=0;l<2;l++){ if(k|l){ t[p][i][j]=min(t[p][i][j],t[p<<1][i][k]+t[p<<1|1][l][j]+((k&l)?0:val)); } } } } } } void init(int p,int l){ for(int i=0;i<2;i++){ for(int j=0;j<2;j++){ t[p][i][j]=(i&j)?a[l]:0; } } } void build(int p,int l,int r){ if(l==r){ init(p,l); return; } int mid=l+r>>1; build(p<<1,l,mid),build(p<<1|1,mid+1,r); pushup(p,val[mid+1]); } void upd(int p,int l,int r,int x){ if(l==r){ init(p,l); return; } int mid=l+r>>1; x<=mid?upd(p<<1,l,mid,x):upd(p<<1|1,mid+1,r,x); pushup(p,val[mid+1]); } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>m;for(int i=1;i<=n;i++)cin>>A[i]; for(int i=1;i<=m;i++){ int x,y,z; cin>>x>>y>>z,e[i]={x,y,z}; } sort(e+1,e+1+m); for(int i=1;i<=n;i++)v[i].pb(i),fa[i]=i; for(int i=1;i<=m;i++){ merge(e[i].x,e[i].y,e[i].z); } for(int i=1;i<n;i++){ merge(i,i+1,inf); } int rt=0; for(int i=1;i<=n;i++)if(fa[i]==i)rt=i; assert(v[rt].size()==n); for(int i=0;i<v[rt].size();i++){ p[v[rt][i]]=i+1; } assert(w[rt].size()==n-1); for(int i=0;i<w[rt].size();i++){ val[i+2]=w[rt][i]; }val[1]=inf; for(int i=1;i<=n;i++)a[p[i]]=A[i]; build(1,1,n); cin>>Q; for(int i=1;i<=Q;i++){ int x,y; cin>>x>>y,a[p[x]]=y; upd(1,1,n,p[x]); cout<<t[1][1][1]<<"\n"; } }

最后还是补一下 t 2 t2 t2代码就算了,能过的代码为什么要优化呀

二进制的世界

用暴力来优化暴力

正解不如暴力

16 16 16位分为两部分:前 8 8 8位和后 8 8 8位。相信大家都猜到复杂度了吧,不过用乱搞优化位运算的确令人烦躁

f i , j f_{i,j} fi,j表示前 8 8 8位为 i i i的数,与某个后 8 8 8为是 j j j的数进行位运算,后 8 8 8位结果的最大值以及方案数。

那么加入一个数 x x x的时候,设它的前 8 8 8位为 a a a,后八位为 b b b,只需要枚举 j j j,用 j   o p t   b j\ opt\ b j opt b更新所有 f a , j f_{a,j} fa,j。查询 x x x的时候,用所有 ( i   o p t   a ) < < 8 ∣ f i , b (i\ opt\ a)<<8|f_{i,b} (i opt a)<<8∣fi,b更新答案。

复杂度 O ( n m ) O(n\sqrt{m}) O(nm )

代码出奇好写

#include<bits/stdc++.h> #define ll long long #define pb push_back #define fi first #define se second using namespace std; const int N=1e5+5; int n,type,a[N],f[1<<8][1<<8],g[1<<8][1<<8]; string op; int calc(int x,int y){ if(op[0]=='x')return x^y; else if(op[0]=='o')return x|y; return x&y; } void ins(int x){ int a=x>>8,b=x^(a<<8); for(int i=0;i<1<<8;i++){ if(calc(b,i)>f[a][i]){ f[a][i]=calc(b,i); g[a][i]=1; } else if(calc(b,i)==f[a][i]){ g[a][i]++; } } } pair<int,int>solve(int x){ int a=x>>8,b=x^(a<<8),res=0,res2=0; for(int i=0;i<1<<8;i++){ if(g[i][b]&&((calc(a,i)<<8)|f[i][b])>res){ res=((calc(a,i)<<8)|f[i][b]); } } for(int i=0;i<1<<8;i++){ if(g[i][b]&&((calc(a,i)<<8)|f[i][b])==res){ res2+=g[i][b]; } }return {res,res2}; } int main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>op>>type; for(int i=1;i<=n;i++)cin>>a[i]; ins(a[1]); for(int i=2;i<=n;i++){ pair<int,int>res=solve(a[i]); if(!type){ cout<<res.fi<<"\n"; } else { cout<<res.fi<<" "<<res.se<<"\n"; } ins(a[i]); } }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530020.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
历史上的今天:
2022-02-28 动态树胡胡胡
点击右上角即可分享
微信分享提示