【学习笔记】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]);
	}
}
posted @ 2023-02-28 16:23  仰望星空的蚂蚁  阅读(9)  评论(0)    收藏  举报  来源