zoukankan      html  css  js  c++  java
  • 【BZOJ2322】[BeiJing2011]梦想封印 高斯消元求线性基+DFS+set

    【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、MQ

    接下来的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;

    题解:又一道神题

    我们回忆2155那道题的做法:所有从1到一个点的路径的异或和 都可以表示成 任意一条从1到该节点的路径 和 某些简单环 的异或和。

    那么本题变成了动态求异或和的种类数,我们首先的思路就是将删边变成倒着往图中加边。然后思考一下我们具体都需要维护些什么。

    从答案的角度分析,答案=(所有本质不同的路径数)*2^(本质不同的环的个数)-1,注意本质不同的路径包括哪里都不走,所以最后要-1。具体本质不同是什么意思呢?

    回忆2155那道题,本质不同的环的意思就是:将所有环高斯消元后得到的线性基,因为这样就可以表示任意环的异或和。

    那么本质不同的路径的意思也就出来了,我们最终得到的不简单的路径的异或和=任意一条简单路径的异或和^任意一些环的异或和,所以两条简单路径本质不同当且仅当它们用线性基消元后,得到的值不同。具体地,我们可以用set来维护这样的路径。

    好了,分析了这么多,现在我们终于可以得出一个可行的做法了:当加入边(a,b)时:

    1.若a,b都已经被访问过(或者说加入的是一条非树边),我们此时得到了一个简单环,将这个简单环的异或和放到线性基中消元,如果没有消成0,那就说明我们应该将这个圆加入到线性基中去,直接把它加入到线性基中对应的位置就行了。但是注意一点,更改线性基后,之前的路径并没有被这条边消元,所以要把set里的边一个一个取出来,重新消元再塞回去。

    2.若a被访问过b没有被访问过(或者说加入了一条树边),我们应该DFSb所在的连通块。在DFS的过程中,每搜到一个点,我们就计算出从1到这个点的路径的异或和,消元后看一下跟以前的是否重复,如果不重复就扔到set里。此外,每搜到一条树边,我们就继续向下搜索;每搜到一条非树边,我们就又得到了一个简单环,按照1中的处理方法去处理就行了。

    3.若a,b都没有被访问过(我们也不知道它将会是树边还是非树边),不用管就好。

    感觉实现要比理论简单一些。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <set>
    #include <algorithm>
    typedef long long ll;
    using namespace std;
    int n,m,cnt,q;
    int to[40010],next[40010],head[10010],pa[40010],pb[40010],del[40010],tag[40010],vis[10010];
    ll val[40010],dis[10010],pc[40010],ans[40010];
    ll v[110];
    set<ll> s;
    set<ll>::iterator it;
    bool cmp(ll a,ll b)
    {
    	return a>b;
    }
    ll query(ll x)
    {
    	for(int j=1;j<=v[0];j++)	if((x^v[j])<x)	x^=v[j];
    	return x;
    }
    void updata(ll x)
    {
    	if(!x)	return ;
    	ll tmp;
    	for(it=s.begin();it!=s.end();it=s.upper_bound(tmp))
    	{
    		tmp=*it;
    		if((tmp^x)<tmp)	s.erase(it),s.insert(tmp^x);
    	}
    	v[++v[0]]=x;
    	for(int j=v[0];j>=2;j--)
    	{
    		if(v[j]>v[j-1])	swap(v[j],v[j-1]);
    		else break;
    	}
    }
    void add(int a,int b,ll c)
    {
    	to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++;
    }
    void dfs(int x,int fa)
    {
    	vis[x]=1;
    	ll tmp=query(dis[x]);
    	if(tmp&&s.find(tmp)==s.end())	s.insert(tmp);
    	for(int i=head[x];i!=-1;i=next[i])
    	{
    		if(to[i]==fa)	continue;
    		if(!vis[to[i]])	dis[to[i]]=dis[x]^val[i],dfs(to[i],x);
    		else	updata(query(dis[x]^dis[to[i]]^val[i]));
    	}
    }
    void insert(int a,int b,ll c)
    {
    	add(a,b,c),add(b,a,c);
    	if(vis[a]&&vis[b])
    	{
    		updata(query(dis[a]^dis[b]^c));
    		return ;
    	}
    	if(!vis[a]&&!vis[b])	return ;
    	if(vis[b])	swap(a,b);
    	dis[b]=dis[a]^c,dfs(b,a);
    }
    int main()
    {
    	memset(head,-1,sizeof(head));
    	vis[1]=1;
    	scanf("%d%d%d",&n,&m,&q);
    	int i;
    	for(i=1;i<=m;i++)	scanf("%d%d%lld",&pa[i],&pb[i],&pc[i]);
    	for(i=1;i<=q;i++)	scanf("%d",&del[i]),tag[del[i]]=1;
    	for(i=1;i<=m;i++)	if(!tag[i])	insert(pa[i],pb[i],pc[i]);
    	s.insert(0);
    	ans[q+1]=(s.size()*1ll<<v[0])-1;
    	for(i=q;i>=1;i--)
    	{
    		insert(pa[del[i]],pb[del[i]],pc[del[i]]);
    		ans[i]=(s.size()*(1ll<<v[0]))-1;
    	}
    	for(i=1;i<=q+1;i++)	printf("%lld
    ",ans[i]);
    	return 0;
    }
  • 相关阅读:
    Linux中大括号{}的应用
    shell script编程(1)>>学生成绩管理系统
    不同版本的Linux防火墙关闭和开启
    shell script的执行方式区别
    包管理介绍(DPKG,APT,RPM,YUM,DNF)
    MBR与GPT,BIOS与UEFI..总结
    Windows10下安装Ubuntu的错误总结
    学生管理系统及票务管理系统总结
    python 3.x和python 2.x下的换行问题
    输出整数各位数字
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7044719.html
Copyright © 2011-2022 走看看