题解:P11831 [省选联考 2025] 追忆

\(\LARGE {P11831 [省选联考 2025] 追忆}\)

题解原出处

请阅读完彼题解再阅读此题解,此题解不对解题思路分析有帮助

仅仅提供代码上的解惑

我只是对他的代码进行了非常详细的注释处理,orz大佬


题意:

大哥图,考虑的是连通性和区间最大值维护 , bitset 闭包传递很好地解决连通性的问题
键值的交换直接进行 ; 权值的交换因为涉及 bitset 的更新 , 我们考虑记录下来 , 查询的时候再更新


\(\mathcal {Step\ 1:}\)初始化和图构建

// 读取测试用例数量
int testid, t; cin >> testid >> t; while(t--)

// 清空邻接表,准备构建新图
for(int i = 1; i <= n; ++i) e[i].clear();

// 构建有向图,e[u]存储u的所有出边邻居
for(int i = 1; i <= m; ++i)
{
    int u, v; cin >> u >> v;
    e[u].push_back(v); // 添加从u到v的有向边
}

\(\mathcal {Step\ 2:}\)数据初始化

// 读取每个节点的键值(用于后续查询)
for(int i = 1; i <= n; ++i) cin >> a[i];

// 初始化权值系统和映射关系
// b[i]:节点i的权值
// wz1[b[i]] = i:权值b[i]对应的节点编号
// wz2[b[i]] = a[i]:权值b[i]对应的键值
for(int i = 1; i <= n; ++i) 
    cin >> b[i], wz1[b[i]] = i, wz2[b[i]] = a[i];

\(\mathcal {Step\ 3:}\)bitset预处理

// 反图处理节点,确保依赖关系正确
for(int i = n; i >= 1; --i)
{
    // 清空当前节点的位集
    memset(bit[i], 0, sizeof(bit[i]));
    
    // 设置当前节点权值的位标记
    // b[i]>>6:确定在哪个64位块中
    // b[i]&63:确定在块中的具体位置(0-63)
    bit[i][b[i] >> 6] = (1llu << (b[i] & 63));
    
    // 合并所有后继节点的位集(可达性传播)
    for(int j : e[i])
    {
        for(int k = 0; k <= (n >> 6); ++k) 
            bit[i][k] |= bit[j][k];
    }
}

\(\mathcal {Step\ 4:}\)查询处理核心逻辑

1: 交换键值

if(op == 1)
{
    cin >> x >> y;
    // 交换两个节点权值对应的键值映射
    swap(wz2[b[x]], wz2[b[y]]);
}

2: 交换权值

if(op == 2)
{
    cin >> x >> y;
    // 更新权值到节点的映射
    swap(wz1[b[x]], wz1[b[y]]);
    // 更新权值到键值的映射  
    swap(wz2[b[x]], wz2[b[y]]);
    // 记录交换历史,用于延迟更新
    xx[++tot] = b[x], yy[tot] = b[y];
    // 实际交换节点的权值
    swap(b[x], b[y]);
}

3: 查询操作(最复杂部分)

if(op == 3)
{
    cin >> x >> l >> r;
    
    // 延迟更新:处理所有未应用的交换操作
    while(pos[x] <= tot)
    {
        // 检查交换涉及的权值在当前节点的位集中是否存在
        int a = (bit[x][xx[pos[x]] >> 6] >> (xx[pos[x]] & 63)) & 1;
        int b = (bit[x][yy[pos[x]] >> 6] >> (yy[pos[x]] & 63)) & 1;
        
        // 如果交换影响了当前节点的可达性,更新位集
        if(a != b)
        {
            if(a == 0) 
            {
                // 添加xx权值,移除yy权值
                bit[x][xx[pos[x]] >> 6] += (1llu << (xx[pos[x]] & 63));
                bit[x][yy[pos[x]] >> 6] -= (1llu << (yy[pos[x]] & 63));
            }
            else
            {
                // 移除xx权值,添加yy权值
                bit[x][xx[pos[x]] >> 6] -= (1llu << (xx[pos[x]] & 63));
                bit[x][yy[pos[x]] >> 6] += (1llu << (yy[pos[x]] & 63));
            }
        }
        ++pos[x]; // 移动到下一个交换操作
    }
    
    // 执行实际查询
    bool flag = 0;
    // 从高位到低位遍历所有64位块
    for(int j = (n >> 6); j >= 0 && !flag; --j)
    {
        if(bit[x][j] == 0) continue; // 跳过空块
        
        unsigned long long now = bit[x][j];
        // 遍历当前块中的所有设置位
        while(now != 0)
        {
            // 计算具体的权值:块基址 + 位偏移
            int wz = (j << 6) + __lg(now);
            
            // 检查该权值对应的键值是否在查询范围内
            if(l <= wz2[wz] && wz2[wz] <= r)
            {
                cout << wz << '\n'; // 输出满足条件的权值
                flag = 1;
                break;
            }
            // 移除当前处理的位,继续处理下一个
            now -= (1llu << __lg(now));
        }
    }
    if(!flag) cout << 0 << '\n'; // 无结果
}

算法核心思想

1. \(\textstyle \large 位集压缩‌:\)使用 unsigned long long 数组模拟大位集,每个元素管理64个权值
2. \(\textstyle \large‌ 延迟更新‌:\)交换操作不立即更新所有节点,而是记录历史,在查询时按需更新
3. \(\textstyle \large‌ 可达性传播‌:\)通过反向遍历确保子节点的信息正确传播到父节点
4. \(\textstyle \large‌ 高效查询‌:\)利用位运算快速检查权值存在性和范围条件


给出全部的代码

#include<bits/stdc++.h>
using namespace std;

int n,m,q;
vector<int> e[100010];
int a[100010],b[100010];
unsigned long long bit[100010][(int)1e5/64+10];
int wz1[100010],wz2[100010];
int tot,xx[100010],yy[100010];
int pos[100010];

int main()
{
    ios::sync_with_stdio(false),cin.tie(0);
    int testid,t; cin>>testid>>t; while(t--)

    {
        cin>>n>>m>>q;
        for(int i=1; i<=n; ++i) e[i].clear();
        for(int i=1; i<=m; ++i)
        {
            int u,v; cin>>u>>v;
            e[u].push_back(v);
        }
        for(int i=1; i<=n; ++i) cin>>a[i];
        for(int i=1; i<=n; ++i) cin>>b[i],wz1[b[i]]=i,wz2[b[i]]=a[i];
        for(int i=n; i>=1; --i)
        {
        	memset(bit[i],0,sizeof(bit[i]));
        	bit[i][b[i]>>6]=(1llu<<(b[i]&63));
        	for(int j:e[i])
        	{
        		for(int k=0; k<=(n>>6); ++k) bit[i][k]|=bit[j][k];
			}
        }
        tot=0;
        for(int i=1; i<=n; ++i) pos[i]=1;
        while(q--)
        {
            int op,x,y,l,r; cin>>op;
            if(op==1)
            {
                cin>>x>>y;
                swap(wz2[b[x]],wz2[b[y]]);
            }
            if(op==2)
            {
                cin>>x>>y;
                swap(wz1[b[x]],wz1[b[y]]);
                swap(wz2[b[x]],wz2[b[y]]);
                xx[++tot]=b[x],yy[tot]=b[y];
                swap(b[x],b[y]);
            }
            if(op==3)
            {
                cin>>x>>l>>r;
                while(pos[x]<=tot)
                {
                	int a=(bit[x][xx[pos[x]]>>6]>>(xx[pos[x]]&63)&1);
                	int b=(bit[x][yy[pos[x]]>>6]>>(yy[pos[x]]&63)&1);
                	if(a!=b)
                	{
                		if(a==0)
                		{
                			bit[x][xx[pos[x]]>>6]+=(1llu<<(xx[pos[x]]&63));
                			bit[x][yy[pos[x]]>>6]-=(1llu<<(yy[pos[x]]&63));
						}
						else
						{
							
                			bit[x][xx[pos[x]]>>6]-=(1llu<<(xx[pos[x]]&63));
                			bit[x][yy[pos[x]]>>6]+=(1llu<<(yy[pos[x]]&63));
						}
					}
                	++pos[x];
				}
				bool flag=0;
				for(int j=(n>>6); j>=0 && !flag; --j)
				{
					if(bit[x][j]==0) continue;
					unsigned long long now=bit[x][j];
					while(now!=0)
					{
						int wz=(j<<6)+__lg(now);
						if(l<=wz2[wz] && wz2[wz]<=r)
						{
							cout<<wz<<'\n';
							flag=1;
							break;
						}
						now-=(1llu<<__lg(now));
					}
				}
				if(!flag) cout<<0<<'\n';
            }
        }
    }
    return 0;
}
posted @ 2025-10-23 20:10  Noivelist  阅读(25)  评论(0)    收藏  举报