梦想封印
题意
原题面:
Problem 2322. -- [BeiJing2011]梦想封印 2322: [BeiJing2011]梦想封印
Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 607 Solved: 240
[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
[Submit][Status][Discuss]
给定一张无向带权有根图, 求从根出发的任意一条路径上的边的异或和的种类数量.
题解
果然车万题不少都是高端题
首先如果这题是树的话其实就等价于求根到所有结点的异或和. 因为跑到其他地方必定要跑回来于是就又把原来的贡献异或掉了
所以问题在于环.
考虑一个环会产生什么高端操作? 沿着环走一圈回到原点, 然后总贡献变了.
也就是说对于环我们可以任意选择要不要加入路径贡献中. (走过去再走回来, 沿途的贡献便被消去了)
对于环的情况我们求一遍线性基那么对于种类的贡献就是 (2^k) 了
然后考虑剩下的简单路径. (作出贡献的必然是简单路径. 重复的会抵消. 于是等价于从根出发到所有其他结点的路径)
注意到只有DFS树上的简单路径是有用的(如果有多种方案可以从根到达某个点, 那么另一条路径的贡献可以通过树上路径异或上一个环得到). 于是有用的路径只有 (O(V)) 条.
用环的线性基消这些简单路径, 剩下的杂值就是无法被表出的部分, 也就是这个简单路径的特殊部分. 如果两个路径的特征值相等, 那么其中一个路径选一些环后得到的值必然也可以在另一条路径的基础上选一些环表出. 那么它们对总方案作出的贡献是重复的. 需要去重.
所以把这些值丢到 set
里就好了
最后的答案就是 (p imes 2^k-1). (p) 是 set
的大小, (k) 是线性基的大小. (-1) 是因为 (0) 的情况不做贡献.
然而上面解决的是静态问题
动态删边果断离线然后变成加边.
考虑加入一条边后有什么高端操作. 假设加入的边端点为 (u,v).
- 如果 (u) 和 (v) 都不与根连通, 则这条边对答案没有贡献, 先扔着就好了
- 如果 (u) 和 (v) 中有一个与根联通, 那么 (v) 所在的整个联通块都可以作出贡献,
DFS
计算这些贡献.- 首先计算根到这个点的树上路径的异或和, 用线性基消一下丢到
set
里 - 如果遇到了环, 把它插入线性基. 然后用新的线性基更新整个
set
.
- 首先计算根到这个点的树上路径的异或和, 用线性基消一下丢到
- 如果 (u) 和 (v) 都与根联通, 则一定有一个环出现. 注意到只要把其中一个包含边 ((u,v)) 的环丢进线性基就可以了, 其他的新环都可以通过原来已经存在的环异或上这个环构造出来. 那么我们直接选最容易搞的环, 把根到 (u) 的异或和异或上根到 (v) 的异或和最后异或上边 ((u,v)) 的值就可以得到一个环的值了, 插进线性基即可.
预处理也可以参考上面的策略用一次 DFS
解决.
参考代码
#include <bits/stdc++.h>
const int MAXV=5010;
const int MAXE=1e5+10;
typedef long long intEx;
struct Edge{
int from;
int to;
intEx val;
Edge* next;
};
Edge E[MAXE];
Edge Ex[MAXE];
Edge* head[MAXV];
Edge* top=E;
int v;
int e;
int q;
int brk[MAXE];
int pos[MAXE];
int prt[MAXV];
bool vis[MAXV];
intEx sum[MAXV];
intEx ans[MAXE];
std::set<intEx> path;
std::vector<intEx> base;
void DFS(int);
inline intEx Elim(intEx);
inline intEx Insert(intEx);
inline void AddCycle(intEx);
inline void Insert(int,int,intEx);
int main(){
scanf("%d%d%d",&v,&e,&q);
for(int i=1;i<=e;i++)
scanf("%d%d%lld",&Ex[i].from,&Ex[i].to,&Ex[i].val);
for(int i=1;i<=q;i++){
scanf("%d",pos+i);
brk[pos[i]]=i;
}
for(int i=1;i<=e;i++){
if(brk[i]==0){
Insert(Ex[i].from,Ex[i].to,Ex[i].val);
Insert(Ex[i].to,Ex[i].from,Ex[i].val);
}
}
DFS(1);
ans[q]=(intEx(path.size())<<base.size())-1;
for(int i=q;i>0;i--){
Edge* k=Ex+pos[i];
Insert(k->from,k->to,k->val);
Insert(k->to,k->from,k->val);
int cnt=vis[k->from]+vis[k->to];
if(cnt==2)
AddCycle(sum[k->from]^sum[k->to]^k->val);
else if(cnt==1){
if(vis[k->from]){
sum[k->to]=sum[k->from]^k->val;
DFS(k->to);
}
else{
sum[k->from]=sum[k->to]^k->val;
DFS(k->from);
}
}
ans[i-1]=(intEx(path.size())<<base.size())-1;
}
for(int i=0;i<=q;i++)
printf("%lld
",ans[i]);
return 0;
}
void AddCycle(intEx val){
if((val=Insert(val))){
std::set<intEx> tmp;
for(auto i:path)
tmp.insert(std::min(i,i^val));
std::swap(tmp,path);
}
}
void DFS(int root){
vis[root]=true;
path.insert(Elim(sum[root]));
for(Edge* i=head[root];i!=NULL;i=i->next){
if(vis[i->to])
AddCycle(sum[root]^sum[i->to]^i->val);
else{
sum[i->to]=sum[root]^i->val;
DFS(i->to);
}
}
}
inline intEx Elim(intEx x){
for(auto i:base)
x=std::min(x,x^i);
return x;
}
inline intEx Insert(intEx x){
x=Elim(x);
if(x!=0)
base.push_back(x);
return x;
}
inline void Insert(int from,int to,intEx val){
top->from=from;
top->to=to;
top->val=val;
top->next=head[from];
head[from]=top++;
}