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]);
}