[BZOJ2322][BeiJing2011]梦想封印-线性基-高斯消元

梦想封印

Description

渐渐地,Magic Land上的人们对那座岛屿上的各种现象有了深入的了解。
为了分析一种奇特的称为梦想封印(Fantasy Seal)的特技,需要引入如下的概念:
每一位魔法的使用者都有一个“魔法脉络”,它决定了可以使用的魔法的种类。
一般地,一个“魔法脉络”可以看作一个无向图,有N个结点及M条边,将结点编号为1~N,其中有一个结点是特殊的,称为核心(Kernel),记作1号结点。每一条边有一个固有(即生成之后再也不会发生变化的)权值,是一个不超过U的自然数。
每一次魔法驱动,可看作是由核心(Kernel)出发的一条有限长的道路(Walk),可以经过一条边多次,所驱动的魔法类型由以下方式给出:
将经过的每一条边的权值异或(xor)起来,得到s。
如果s是0,则驱动失败,否则将驱动编号为s的魔法(每一个正整数编号对应了唯一一个魔法)。
需要注意的是,如果经过了一条边多次,则每一次都要计入s中。
这样,魔法脉络决定了可使用魔法的类型,当然,由于魔法与其编号之间的关系尚未得到很好的认知,此时人们仅仅关注可使用魔法的种类数。
梦想封印可以看作是对“魔法脉络”的破坏:
该特技作用的结果是,“魔法脉络”中的一些边逐次地消失。
我们记总共消失了Q条边,按顺序依次为Dis1、Dis2、……、DisQ。
给定了以上信息,你要计算的是梦想封印作用过程中的效果,这可以用Q+1个自然数来描述:
Ans0为初始时可以使用魔法的数量。
Ans1为Dis1被破坏(即边被删去)后可以使用魔法的数量。
Ans2为Dis1及Dis2均被破坏后可使用魔法的数量。
……
AnsQ为Dis1、Dis2、……、DisQ全部被破坏后可以使用魔法的数量。

Input

第一行包含三个正整数N、M、Q。
接下来的M行,每行包含3个整数,Ai、Bi、Wi,表示一条权为Wi的与结点Ai、Bi关联的无向边,其中Wi是不超过U的自然数。
接下来Q行,每行一个整数:Disi。

Output

一共包Q+1行,依次为Ans0、Ans1、……、AnsQ。

Sample Input

【输入样例1】

3 3 2
1 2 1
2 3 2
3 1 4
1
3

【输入样例2】

5 7 7
1 2 1
1 3 1
2 4 2
2 5 2
4 5 4
5 3 9
4 3 1
7
6
5
4
3
2
1

Sample Output

【输出样例1】

5
2
0

【样例1解释】

初始时可使用编号为1、3、4、6、7的魔法。
在删去第1条边(连结1、2结点的边)后,可使用4和6号魔法。
第3条边(连结第1、3结点的边)也被删去后,核心(Kernel)即结点1孤立,易知此时无法使用魔法。

【输出样例2】

15
11
5
2
2
1
1
0

HINT

【数据规模和约定】
所有数据保证该无向图不含重边、自环。
所有数据保证不会有一条边被删除多次,即对于不同i和j,有Disi≠Disj
30%的数据中N ≤ 50,M ≤ 50,Q ≤50,U≤100;
60%的数据中N ≤ 300,M ≤ 300,Q ≤50,U≤10^9;
80%的数据中N ≤ 300,M ≤ 5000,Q ≤5000,U≤10^18;
100%的数据中N ≤ 5000,M ≤ 20000,Q ≤20000,U≤10^18;


看见标题就点了进来……
然后得出结论:拿幻想乡当标题的题质量几乎都很高
比如这题真难……


思路:
首先研究一下路径的特点。
接下来提到的路径默认均指不存在环的路径。

考虑异或互相抵消的性质,可以发现,图上的每个环均可以作为贡献算进任意一条路径中,因为明显可以走到环那边再走回来。

因此咱们能把结果拆成若干个环和若干个不存在环的简单路径异或结果情况数。

现在先考虑图已经给出并且保持不变的情况下如何统计答案:
考虑到环可以任意选择添加或不添加,那么显然,可以先把所有环找出来,丢进一个线性基里。
然后环对于每条路径的答案贡献便是2的线性基内元素数次方了~

再考虑路径。
首先显然有一个方案是原地不动,相当于只走环。
然后,考虑对每一条路径,插入当前维护的环的线性基内进行消元,最后得到的消元结果作为这条路径的“特征值”。

可以发现这个特征值是当前路径无法用环凑出来的部分。
如果两条路径特征值相同,那么不论之前它们的权值是否相等,在使用一些环后它们必定能凑成相等的状况。
或者用线性代数的观点说,它们不是本质不同的。
那么进行去重后剩余路径的个数就是不同有贡献的路径的数量了~

最后考虑删边操作。
显然可以时间倒流一发让它变成不停加边。
然后,考虑维护一棵以核心为根的dfs生成树,在每次加边后分类讨论:

1.新边两端点均未被加入生成树
那么让这条边安安静静地做一条咸鱼就好~
反正对答案暂时毫无影响。

2.新边两端点均被加入生成树
那么肯定出现了新环。
可以发现只需要加所有出现新环的其中一个便可以了,因为其他环必定能被当前环和其他环拼凑出来。
那么对每个点记录一个距根节点的距离 dis[x] ,对于当前边 uv ,直接加入 dis[u]dis[v]wuv 即可。

3.只有一个端点被加入生成树
那么这条边的加入可能唤醒了某些沉睡的上古咸鱼边……
因此以未被加入的节点为起点dfs一下,根据上面的方法,把新环都加入线性基,把新路径加入路径集合即可~

此外,每次线性基被成功改变时,都需要暴力重新计算所有路径的特征值并重新去重(当然以前就被去掉了的无需重新计算)

那么这就完成了~

#include<bits/stdc++.h>

using namespace std;

typedef unsigned long long ll;
typedef pair<int,ll> pr;
typedef set<ll>::iterator it;
const int N=5009;
const int M=20009;
const int Q=20009;
const int K=64;

int n,m,q,siz;
vector<pr> g[N];
set<ll> s;
bool vis[N];
ll ans[Q],dis[N],bas[K],stk[N];

struct e
{
    ll u,v,w,t;
    bool operator < (e o)const
    {
        return t<o.t;
    }
}ed[M];

inline ll read()
{
    ll x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline void update(ll x)
{
    int top=0;
    for(it i=s.begin();i!=s.end();i++)
        if(((*i)^x)<(*i))
            stk[++top]=(*i)^x;
        else
            stk[++top]=(*i);
    s.clear();
    for(int i=1;i<=top;i++)
        s.insert(stk[i]);
}

inline void insert(ll x)
{
    for(int i=K-1;i>=0 && x;i--)
        if((x>>i)&1)
            x^=bas[i];
    if(!x)return;
    for(int i=K-1;i>=0;i--)
        if(((x>>i)&1) && !bas[i])
        {
            bas[i]=x;
            siz++;
            update(x);
            return;
        }   
}

inline ll query(ll x)
{
    for(int i=K-1;i>=0 && x;i--)
        if(x>>i&1)
            x^=bas[i];
    return x;
}

inline ll calc()
{
    return (1ull<<siz)*s.size()-1;
}

inline void dfs(int u,int fa)
{
    vis[u]=1;
    s.insert(query(dis[u]));
    for(int i=0,ei=g[u].size(),v;i<ei;i++)
        if(!vis[(v=g[u][i].first)])
        {
            dis[v]=dis[u]^g[u][i].second;
            dfs(v,u);
        }
        else if(v!=fa)
            insert(dis[u]^dis[v]^g[u][i].second);
}

inline void add(int u,int v,ll w)
{
    g[u].push_back(pr(v,w));
    g[v].push_back(pr(u,w));
    if(vis[u] && vis[v])
        insert(dis[u]^dis[v]^w);
    else if(vis[u] || vis[v])
    {
        if(vis[v])swap(u,v);
        dis[v]=dis[u]^w;
        dfs(v,u);
    }
}

int main()
{
    n=read();m=read();q=read();
    for(int i=1;i<=m;i++)
    {
        ed[i].u=read();ed[i].v=read();
        ed[i].w=read();ed[i].t=0;
    }

    for(int i=1;i<=q;i++)
        ed[read()].t=q-i+1;

    sort(ed+1,ed+m+1);

    s.insert(0);
    int ptr=1;vis[1]=1;
    for(int t=0;t<=q;t++)
    {
        while(ptr<=m && ed[ptr].t==t)
            add(ed[ptr].u,ed[ptr].v,ed[ptr].w),ptr++;
        ans[t]=calc();
    }

    for(int i=q;i>=0;i--)
        printf("%llu\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值