【学习笔记】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 u′∈S,v′∈T之间的连边,当然边权不变。其原因在于,如果 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]);
}
}

浙公网安备 33010602011771号