[bzoj2322][线性基]梦想封印

2322: [BeiJing2011]梦想封印

Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 541 Solved: 212
[Submit][Status][Discuss]
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;

Source

Day3

sol:

首先离线下来,从后往前做,删边变加边。
显然线性基对吧。

思路1:高消角度考虑,支持在线

考虑到删除一条边相当于删去了一个向量,如果我们删去一个向量的话,从高消上面考虑,我们相当于删去了一个方程。我们先把这行下面的基对他产生的影响消除,再用这行去消除他对所有的基的影响。然后这行的影响才算被消除了。那我们要记某个行被哪些行消过。容易发现这样要记n(n-1)/2这么多的东西。那我们存不下怎么办呢?我们拿个bitset存,就是n2/32这么多的内存,那这个显然就存的下了。这个做法的好处是你可以在线维护这些询问。

思路2:萌新角度考虑

考虑到删去一个基是如此的强人♂锁男。我们考虑把询问给离线下来,从下往上做,那就是相当于是加边了。我们往线性基里面加入一个元素是非常easy的对吧。

上面的2个思路都只是理清了一下往基里删(加)元素时的操作,但是并没有命中这题的核心。一个显然的结论,图上的一条(非)简单路径的xor值可以用这个图的一个生成树上两点的路径xor xor 上若干个非树边和树边构成的环的xor值。看到图的xor第一个反应应该就是这个结论对吧。我们接下来基于这个结论做题。
考虑到这个结论的前提是你要有一颗生成树,并且维护出f[i]表示1到i的路径xor值。

思路1实现(?)的口胡

删去一条非树边无关紧要,就是删去某个基。但是删去一条树边就很尴尬了,我们要用一个非树边替代这个树边,并且删去非树边的基,然后非1联通块的f值还要重新维护,这里涉及到一个换根的问题。那我们要写个线性基的删除,lct维护生成树,脑洞yy换根,似乎lct上面还要打标记。
还好这个思路是我一开始就不打算用的,只是准备口胡一下,结果居然这么难写。。我是个萌新当然用萌新思路辣(QAQ,如果浪费了各位看官的时间在这里说句对不起了)。

思路2实现的口胡

随便维护一下生成树,再写个线性基即可,生成树连边(u,v)的时候,如果u在生成树里v不在,连边,f[v]=f[u]^val[e],然后搜索v的子树,把所有的f值处理完,加到线性基中,如果都不在生成树里,就先连边。如果u,v都在生成树中,就把f[u]^f[v]^val[e]加到线性基中。我一开始以为线性基里的xor值方案可能是假的,因为我们有可能会选出一大堆环而不选择生成树上的路径。这里我一开始以为要容斥一下,问了一下uoj裙,其实选出一大堆环可以看成在1点,走了一大堆环,其实没问题。
但是!上面的题解有一些地方是错的。因为如果把f[u]和环都丢到线性基里面的话,我们会出现形如f[u]^f[v]^若干环的东西出现。那么这样的话线性基里面能够xor出来的东西其实是图中任意(非)简单路径的xor值。不是1到所有点的xor值。
我们考虑只把环丢到线性基里面。把本质相同的f[u]统计答案(也就是说我们要去重).什么样的f[u]是本质相同的呢?显然我们用线性基里面的东西把f[u]消掉之后,得到的就是f[u]无法被线性基表示出来的部分,如果这部分相同的f[u]就是本质相同的(证明显然).我们考虑用一个set来维护这些f[u]消完后的值。那么我们要维护的操作就很少了,加入一个环到线性基中,并更正set的值,加入一个被线性基消过的f[u]到set中。
这题调了很久,我一开始认为这题的线性基要贪心的消,而不能消成对角线。(我认为前面的基如果受到新的元素的影响,会改变他对set中元素的xor值),但是实际上一个线性基中,一个基xor上另一个基显然不改变这个线性基。那么我还是使用了我最习惯的对角线的形式。(但是注释掉的部分也可以用的,我试了2个写法)

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef double s64;
int n,m,q;
inline int read()
{
    char c;
    int res,flag=0;
    while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
    res=c-'0';
    while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
    return flag?-res:res;
}
const int N=41000;
struct cc
{
    int x,y;
    ll z;
}e[N];
int b[N],fir[N],go[N],nex[N],tot,del[N],top;
ll f[N],c[N],val[N];
bool vis[N];
inline void add(cc x)
{
    nex[++tot]=fir[x.x];fir[x.x]=tot;go[tot]=x.y;val[tot]=x.z;
    nex[++tot]=fir[x.y];fir[x.y]=tot;go[tot]=x.x;val[tot]=x.z;
}
multiset<ll> s;
inline void updata(ll x)//灏濊瘯鎶婁竴涓暟鍔犲叆set
{
    for(int j=60;~j;--j)
    if(b[j]&&((x>>j)&1))
    x^=c[b[j]];
    if(s.find(x)==s.end()) s.insert(x);
}
inline void insert(ll x)//鍔犲叆绾挎€у熀锛岄『渚挎妸set閲岀殑鍏冪礌xor浜?
{
    /*
    bool f=1;
    for(int j=60;~j;--j)
    if((x>>j)&1)
    {
        if(b[j]) x^=c[b[j]];
        else if(f) {b[j]=top+1;f=0;}
    }*/
    int fir=-1;
    for(int j=60;~j;--j)
    if((x>>j)&1)
    {
        if(b[j]) x^=c[b[j]];
        else fir=max(fir,j);
    } 
    if(fir!=-1)
    {
        for(int j=60;~j;--j)
        if(b[j]&&((c[b[j]]>>fir)&1))
        c[b[j]]^=x;
        b[fir]=top+1;
    }
    if(x) c[++top]=x;
    else return;
    multiset<ll>::iterator it;
    ll y;
    for(it=s.begin();it!=s.end();it=s.upper_bound(y))
    {
        y=*it;
        if((*it^x)<*it)
        {
            s.erase(it);
            if(s.find(y^x)==s.end())
            s.insert(y^x);
        }
    }
}
inline void dfs(int u,int fa)
{
    vis[u]=1;
    updata(f[u]);
    int e,v;
    for(e=fir[u];v=go[e],e;e=nex[e])
    if(v!=fa)
    {
        if(vis[v]) insert(f[u]^f[v]^val[e]);
        else
        {
            f[v]=f[u]^val[e];
            dfs(v,u);
        }
    }
}
inline void link(cc x)
{
    add(x);
    if(vis[x.y]) swap(x.x,x.y);
    if(!vis[x.x]&&!vis[x.y]) return;
    if(vis[x.x]&&!vis[x.y])
    {
        f[x.y]=f[x.x]^x.z;
        dfs(x.y,x.x);
    }
    else insert(f[x.x]^f[x.y]^x.z);
}
bool tag[N];
ll Pow[61],ans[N];
inline void debug()
{
    cout<<s.size()<<endl;
    multiset<ll>::iterator it;
    for(it=s.begin();it!=s.end();++it)
    printf("%lld ",*it);
    cout<<endl;
    cout<<top<<endl;
    for(int i=1;i<=top;++i)
    cout<<c[i]<<' ';
    cout<<endl;
}
int main()
{
//  freopen("fantasy.in","r",stdin);
//  freopen("fantasy.out","w",stdout);
    n=read();
    m=read();
    q=read();
    Pow[0]=1;
    for(int i=1;i<=60;++i) Pow[i]=Pow[i-1]<<1;
    for(int i=1;i<=m;++i)
    {
        e[i].x=read();
        e[i].y=read();
        scanf("%lld",&e[i].z);
    }
    for(int i=1;i<=q;++i) {del[i]=read();tag[del[i]]=1;}
    s.insert(0);
    vis[1]=1;
    for(int i=1;i<=m;++i)
    if(!tag[i]) link(e[i]);
    ans[q+1]=s.size()*Pow[top]-1;
    for(int i=q;i>=1;--i)
    {
        link(e[del[i]]);
        ans[i]=s.size()*Pow[top]-1;
//      debug();
    }
    for(int i=1;i<=q+1;++i)
    printf("%lld\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值