zoukankan      html  css  js  c++  java
  • AtCoder

    Problem Statement

    You are given a tree with N vertices. The vertices are numbered 0 through N−1, and the edges are numbered 1 through N−1. Edge i connects Vertex xi and yi, and has a value ai. You can perform the following operation any number of times:

    • Choose a simple path and a non-negative integer x, then for each edge e that belongs to the path, change ae by executing aeaex (⊕ denotes XOR).

    Your objective is to have ae=0 for all edges e. Find the minimum number of operations required to achieve it.

    Constraints

    • 2≤N≤105
    • 0≤xi,yiN−1
    • 0≤ai≤15
    • The given graph is a tree.
    • All input values are integers.

    Input

    Input is given from Standard Input in the following format:

    N
    x1 y1 a1
    x2 y2 a2
    :
    xN−1 yN−1 aN−1
    

    Output

    Find the minimum number of operations required to achieve the objective.

    Sample Input 1

    5
    0 1 1
    0 2 3
    0 3 6
    3 4 4
    

    Sample Output 1

    3

    The objective can be achieved in three operations, as follows:

    • First, choose the path connecting Vertex 1,2, and x=1.
    • Then, choose the path connecting Vertex 2,3, and x=2.
    • Lastly, choose the path connecting Vertex 0,4, and x=4.

    Sample Input 2

    2
    1 0 0
    

    Sample Output 2

    0


    (我什么时候能做ATCODER 1000分题了2333)
    首先路径异或x 等于 路径两个端点到 树根 的路径 分别异或x,因为lca以上的边被异或了两次,所以就相当于路径异或。
    于是我们不妨把 从点i到根的路径异或x 叫做一次 基本操作,然后我们随便选一个点当根,最后所有边都为0 需要满足:
    除了根节点以外,涉及一个点i的所有基本操作的x(一个点i会被在j产生的基本操作涉及当且仅当i是j的祖先)的异或和等于 i到父亲的边。

    当然我们知道,两次x一样的基本操作是可以合并为 一个 题目中描述的操作的,那么我们先假设不知道这个,而是贪心的让基本操作次数最少。
    那么如何让基本操作次数最少呢?
    发现一个节点x的儿子son这颗子树对它的影响始终是 val(x,son) ,所以我们可以让每个节点只操作一次,取它所有邻接的边 异或起来的值(如果是0当然可以不操作)。
    于是就得到了 基本操作最少的 方案, 并且这个方案是唯一的,因为从子树到根每一步都是唯一的。

    但是如果可以合并的话,反例就很好找了: 1,4,5. 不能合并的话只能三步达成,但是合并的话,可以 1,4,5->0,5,5->0,0,0,两步就行了。
    不过我们可以很容易的发现,如果很多点的操作x一样,可以先把这些一样的x合并起来,这样答案总是最优的。
    这样之后,剩下的操作x互不相同,且1<=x<=15 (0没有意义),很容易想到用 状态压缩下的dp来解决。但是状态之间可能有后效性,所以这个时候就不能
    直接在DAG上dp了,要跑spfa。

    所以最后我们枚举哪个点是根(也就是哪个点的 邻接的边的异或和 可以不计入统计),更新一下答案的最小值即可。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=100005;
    int val[maxn*20],hd[maxn],n,m;
    int to[maxn*20],ne[maxn*20],num,S;
    int XS[maxn],now,ci[20],d[maxn],C[233];
    inline void add(int x,int y,int z){ to[++num]=y,ne[num]=hd[x],hd[x]=num,val[num]=z;}
    
    inline void build(){
    	for(int i=1;i<ci[15];i++)
    	    for(int j=1;j<=15;j++) if(ci[j-1]&i){
    	    	now=i^ci[j-1],add(now,i,1);
    	    	for(int k=1;k<=15;k++) if(ci[k-1]&now) add(now^ci[k-1]^ci[(j^k)-1],i,1+((now&ci[(j^k)-1])?1:0));
    		}
    }
    
    inline void spfa(){
    	queue<int> q; bool v[maxn];
    	memset(d,0x3f,sizeof(d));
    	memset(v,0,sizeof(v));
    	d[0]=0,q.push(0),v[0]=1;
    	
    	int x;
    	while(!q.empty()){
    		x=q.front(),q.pop();
    		for(int i=hd[x];i;i=ne[i]) if(d[x]+val[i]<d[to[i]]){
    			d[to[i]]=d[x]+val[i];
    			if(!v[to[i]]) v[to[i]]=1,q.push(to[i]);
    		}
    		v[x]=0;
    	}
    }
    
    inline void init(){
    	ci[0]=1;
    	for(int i=1;i<=15;i++) ci[i]=ci[i-1]<<1;
    	
    	build();
    	spfa();
    }
    
    inline void solve(){
    	int uu,vv,ww,ans=1<<30;
    	scanf("%d",&n);
    	for(int i=1;i<n;i++){
    		scanf("%d%d%d",&uu,&vv,&ww);
    		uu++,vv++,XS[uu]^=ww,XS[vv]^=ww;
    	}
    	
    	for(int i=1;i<=n;i++) C[XS[i]]++;
    	for(int i=1;i<=n;i++){
    		C[XS[i]]--;
    		
    		S=0,now=0;
    		for(int j=1;j<=15;j++){
    			now+=C[j]>>1;
    			S|=(C[j]&1)*ci[j-1];
    		}
    		now+=d[S];
    		ans=min(ans,now);
    		
    		C[XS[i]]++;
    	}
    	
    	printf("%d
    ",ans);
    }
    
    int main(){
    	init();
    	solve();
    	return 0;
    }
    

      



  • 相关阅读:
    C#计算代码的执行耗时
    c#值类型和引用类型
    C#类、接口、虚方法和抽象方法
    15,了解如何在闭包里使用外围作用域中的变量
    函数闭包,golbal,nonlocal
    init())函数和main()函数
    函数的命名空间
    函数的默认参数是可变不可变引起的奇怪返回值
    遍历目录
    super顺序
  • 原文地址:https://www.cnblogs.com/JYYHH/p/8861628.html
Copyright © 2011-2022 走看看