zoukankan      html  css  js  c++  java
  • AT3913XOR Tree【状压dp】

    正题

    题目链接:https://www.luogu.com.cn/problem/AT3913


    题目大意

    给出一棵有边权的树,你每次可以选择一条链让所有的边异或上同一个值,求最少的操作次数使得所有边的权值都为\(0\)

    \(2\leq n\leq 10^5,0\leq w<16\)


    解题思路

    一条边的权值可以视为连接的两个点的权值异或,那么我们就可以把边边为点权了。

    而链的异或就可以变成首尾两个点同时异或上一个值。

    那么问题就变为了给\(n\)个数字你每次可以让两个同时异或上任意一个数,求最少操作次数使得它们都变为\(0\)

    那么显然一样的我们直接异或掉最优,那么现在就只剩下最多\(16\)个不同的数了。

    考虑状压\(dp\),注意到如果一个集合能够操作到\(0\)那么这个集合肯定有所有数字的异或和为\(0\),因为无论怎么操作序列的异或和都不会变。

    那么理论上如果有\(x\)个数字,那么我们的操作次数上限是\(x-1\),但是如果我们能把集合\(S\)分成两个集合\(T,S-T\)且这两个集合的异或和都为\(0\),那么就变成了\(x-2\),也就是最小的值在我们尽量分最多集合得到。

    \(f_S\)表示\(S\)最多分成多少个集合,然后\(O(3^{16})\)转移就好了。

    时间复杂度:\(O(n+3^{16})\)


    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=1e5+10,M=16;
    int n,ans,a[N],v[M],c[1<<M],f[1<<M];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1,x,y,w;i<n;i++){
    		scanf("%d%d%d",&x,&y,&w);
    		a[x]^=w;a[y]^=w;
    	}
    	for(int i=0;i<n;i++)v[a[i]]++;
    	int MS=(1<<16);
    	memset(f,0xcf,sizeof(f));
    	for(int s=1;s<MS;s++)
    		for(int i=0;i<16;i++)
    			if((s>>i)&1){
    				c[s]=c[s-(1<<i)]^i;
    				if(!c[s])f[s]=0;
    				break;
    			}
    	int S=0;f[0]=0;
    	for(int i=1;i<16;i++)
    		ans+=(v[i]+1)/2,S|=((v[i]&1)<<i);
    	if(!S)return printf("%d\n",ans)&0;
    	for(int s=1;s<MS;s++){
    		if(c[s])continue;
    		for(int t=(s-1)&s;t>=(s^t);t=(t-1)&s)
    			f[s]=max(f[s],f[t]+f[s^t]+1);
    	}
    	printf("%d\n",ans-f[S]-1);
    	return 0;
    }
    /*
    4
    0 1 5
    1 2 3
    2 3 5
    */
    
  • 相关阅读:
    安徽.NET俱乐部4月份活动图片
    C++ string和数字间的任意转换
    利用C++特性 析构对象(ScopeGuard.h)
    ffmpeg第三方库
    Apifox软件使用技巧
    工作流撤回(activity5)
    pom文件详解
    Java内部类详解成员内部类,局部内部类,匿名内部类,静态内部类
    Docker 详解
    JDK8 新特性 Lambda表达式
  • 原文地址:https://www.cnblogs.com/QuantAsk/p/15704904.html
Copyright © 2011-2022 走看看