洛谷3613睡覺困難綜合徵(LCT維護鏈資訊(前後綴)+貪心)
這個題目還是很好啊QWQ很有紀念意義
首先,如果在序列上且是單次詢問的話,就是一個非常裸的貪心了QWQ這也是NOI當時原題的問題和資料範圍
我們考慮上樹的話,應該怎麼做?
我的想法是,對於每一位建一個LCT來做,然後對一個點x維護,當前是0到鏈頂的結果,當前是1到頂的結果,當前是0到底的結果,當前是1到底的結果(之所以維護後兩個是因為\(reverse\)操作的存在)
這樣的話,對於每次詢問,我就可以直接\(split\),然後貪心的詢問。
不過很可惜,這個演算法的複雜度是\(O(qklogn)\)的
並不能跑過(雖然沒試過硬卡常行不行)
那麼應該怎麼做呢QWQ 這時候,我選擇了看題解。
首先,經過仔細思考,我們會發現,QWQ對於題目來說,其實並不需要按位來做,可以直接維護\(f_0,f_1,g_0,g_1\)四個值,分別表示所有位0到鏈頂的結果,所有二進位制位全是1的數到頂的結果,所有位全是0到底的結果,所有位全是1到底的結果
那麼這個東西應該怎麼轉移呢?
這裡合併的大概思想是 全0走到中間節點之後,是0的那幾位,相當於後面的全0走的,是1的那幾位,相當於是後面全1走的 ,全1的也是同理
而之所以這麼寫,是因為運用了與的美妙的性質,只有兩邊都是1,最終結果才是1。
所以,假設對於當前是全零0來說,考慮前一半,是計算0的那幾位,要是採用了&與這個運算,如果最後走完,\(f_0\)
要是這一位是1,我們要滿足與完 是1的話,同時去除一開始是1的位的貢獻,我們就需要先取反,然後再&
其他的其實也是同理
Node merge(Node a,Node b) //我們預設a在前,b在後 { Node c; c.f0=(~a.f0 & b.f0) + (a.f0 & b.f1); //這裡合併的大概思想是 全0走到中間節點之後,是0的那幾位,相當於後面的全0走的,是1的那幾位,相當於是後面全1走的 c.f1=(~a.f1 & b.f0) + (a.f1 & b.f1); //而之所以這麼寫,是因為運用了與的美妙的性質,只有兩邊都是1,最終結果才是1。所以,假設對於當前是0來說,只有後面全零弄出來是1,我們當前的的答案才是1,那麼為了出來1,我們就需要先取反,然後再& return c; } void update(unsigned long long x) { zheng[x]=fan[x]=val[x]; if (ch[x][0]) { zheng[x]=merge(zheng[x],zheng[ch[x][0]]); fan[x]=merge(fan[ch[x][0]],fan[x]); } if (ch[x][1]) { zheng[x]=merge(zheng[ch[x][1]],zheng[x]); fan[x]=merge(fan[x],fan[ch[x][1]]); } }
其他的LCT操作的話,也就和別的沒什麼區別了。
這裡再講一下底下那個貪心的過程
unsigned long long ans=ling;
split(x,y);
//cout<<1<<endl;
for (long long i=k-1;i>=0;i--)
{
if (fan[y].f0&power[i])
{
ans=ans+power[i];
continue;
}
if (fan[y].f1 & power[i])
{
if (power[i]>z) continue;
ans=ans+power[i];
z-=power[i];
}
//cout<<i<<endl;
}
cout<<ans<<"\n";
從高位向低位考慮,如果當前位填0,最終結果是1的話,那麼就填0。
如果當前位填1,最終結果才能是1。我們就需要比較一下剩餘的值是否比這個位填1的數大,大的話才能填
以此類推
QWQ
記得開\(unsigned\ long\ long\)
上程式碼
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
#define lc ch[x][0]
#define rc ch[x][1]
using namespace std;
inline unsigned long long read()
{
unsigned long long x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 1e6+1e2;
struct Node{
unsigned long long f0,f1;
};
unsigned long long ch[maxn][3];
unsigned long long rev[maxn],fa[maxn];
Node zheng[maxn],fan[maxn];
unsigned long long n,m;
Node val[maxn];
unsigned long long ling =0;
unsigned long long son(unsigned long long x)
{
if (ch[fa[x]][0]==x) return 0;
else return 1;
}
bool notroot(unsigned long long x)
{
return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
void reverse(unsigned long long x)
{
swap(ch[x][0],ch[x][1]);
swap(zheng[x].f0,fan[x].f0);
swap(zheng[x].f1,fan[x].f1);
rev[x]^=1;
}
Node merge(Node a,Node b) //我們預設a在前,b在後
{
Node c;
c.f0=(~a.f0 & b.f0) + (a.f0 & b.f1); //這裡合併的大概思想是 全0走到中間節點之後,是0的那幾位,相當於後面的全0走的,是1的那幾位,相當於是後面全1走的
c.f1=(~a.f1 & b.f0) + (a.f1 & b.f1); //而之所以這麼寫,是因為運用了與的美妙的性質,只有兩邊都是1,最終結果才是1。所以,假設對於當前是0來說,只有後面全零弄出來是1,我們當前的的答案才是1,那麼為了出來1,我們就需要先取反,然後再&
return c;
}
void update(unsigned long long x)
{
zheng[x]=fan[x]=val[x];
if (ch[x][0])
{
zheng[x]=merge(zheng[x],zheng[ch[x][0]]);
fan[x]=merge(fan[ch[x][0]],fan[x]);
}
if (ch[x][1])
{
zheng[x]=merge(zheng[ch[x][1]],zheng[x]);
fan[x]=merge(fan[x],fan[ch[x][1]]);
}
}
void pushdown(unsigned long long x)
{
if (rev[x])
{
if (ch[x][1]) reverse(ch[x][1]);
if (ch[x][0]) reverse(ch[x][0]);
rev[x]=0;
}
}
void rotate(unsigned long long x)
{
unsigned long long y=fa[x],z=fa[y];
unsigned long long b=son(x),c=son(y);
if (notroot(y)) ch[z][c]=x;
fa[x]=z;
ch[y][b]=ch[x][!b];
fa[ch[x][!b]]=y;
ch[x][!b]=y;
fa[y]=x;
update(y);
update(x);
}
unsigned long long st[maxn];
void splay(unsigned long long x)
{
unsigned long long y=x,cnt=0;
st[++cnt]=y;
while (notroot(y)) y=fa[y],st[++cnt]=y;
while (cnt) pushdown(st[cnt--]);
while (notroot(x))
{
unsigned long long y=fa[x],z=fa[y];
unsigned long long b=son(x),c=son(y);
if (notroot(y))
{
if (b==c) rotate(y);
else rotate(x);
}
rotate(x);
}
update(x);
}
void access(unsigned long long x)
{
for (unsigned long long y=0;x;y=x,x=fa[x])
{
splay(x);
ch[x][1]=y;
update(x);
}
}
void makeroot(unsigned long long x)
{
access(x);
splay(x);
reverse(x);
}
unsigned long long findroot(unsigned long long x)
{
access(x);
splay(x);
while (ch[x][0])
{
pushdown(x);
x=ch[x][0];
}
return x;
}
void split(unsigned long long x,unsigned long long y)
{
makeroot(x);
access(y);
splay(y);
}
void link(unsigned long long x,unsigned long long y)
{
makeroot(x);
if(findroot(y)!=x) fa[x]=y;
}
unsigned long long power[maxn];
unsigned long long ymh = 2;
unsigned long long k;
signed main()
{
n=read(),m=read(),k=read();
power[0]=1;
for (unsigned long long i=1;i<64;i++) power[i]=power[i-1]*ymh;
for (unsigned long long i=1;i<=n;i++)
{
unsigned long long y=read(),x=read();
if (y==1)
{
val[i]=(Node){ling,x};
}
if (y==2)
{
val[i]=(Node){x,~ling};
}
if (y==3)
{
val[i]=(Node){x,~x};
}
}
for (unsigned long long i=1;i<n;i++)
{
unsigned long long x=read(),y=read();
link(x,y);
}
//cout<<1<<endl;
for (unsigned long long i=1;i<=m;i++)
{
unsigned long long opt=read(),x=read(),y=read(),z=read();
if (opt==2)
{
splay(x);
if (y==1) val[x]=(Node){ling,z};
if (y==2) val[x]=(Node){z,~ling};
if (y==3) val[x]=(Node){z,~z};
}
if (opt==1)
{
unsigned long long ans=ling;
split(x,y);
//cout<<1<<endl;
for (long long i=k-1;i>=0;i--)
{
if (fan[y].f0&power[i])
{
ans=ans+power[i];
continue;
}
if (fan[y].f1 & power[i])
{
if (power[i]>z) continue;
ans=ans+power[i];
z-=power[i];
}
//cout<<i<<endl;
}
cout<<ans<<"\n";
}
}
return 0;
}