1 条题解

  • 0
    @ 2025-8-24 22:42:53

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar GavinCayne
    绝岭衔延不可攀,我自信步留蹊去。

    搬运于2025-08-24 22:42:53,当前版本为作者最后更新于2023-08-04 01:15:28,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    P8826 游戏 题解

    传送门

    这是当年传智杯初赛最难的题,说是黄题中的佼佼者也不为过。甚至过了三年提交 14001400 通过人数竟不满 100100!而且无一篇题解被我捡漏)!一开始本蒟蒻也没有思路,后来看了讨论区别的大佬的思路才豁然开朗(此题数据有问题)。

    题目大意

    给定 nn 个数,两种操作:每次选 iijj,若 a[i]xora[j]a[i]\operatorname{xor}a[j] 的值二进制只有 11 位为 11,则代价 ansans 需要加上 C1C_1,如果大于 11 位则代价 ansans 加上 C2C_2。做完操作删去其中一个数,问剩最后一个数时 ansans 最小是多少。

    整理一波思路

    1. C1=C2C_1=C_2 无论异或情况如何代价都不变。直接用操作次数 n1n-1 乘上一次代价 C1C_1 出结果。
    2. 一位一位判断异或结果实在麻烦,需要转成二进制再倒着(短除法性质)判断每一位上的值是不是 11要循环两次。有什么方法一下就能知道异或的值是不是 11 位是 11 呢? lowbit\operatorname{lowbit} 函数lowbit(n)\operatorname{lowbit}(n) 表示 nn 最低位为 11 的数和它后面的数构成的数值。例:lowbit(6)\operatorname{lowbit}(6) 表示 6(110)6(110) 最低位为 11 的数及它后面的 00 构成的数 2(10)2(10)。那么当 lowbit(n)=n\operatorname{lowbit}(n)=n 时,nn 一定是 22 的整数次幂(除最高位全是 00),它的二进制为 11 的位只可能是最高位
    3. 如何写 lowbit\operatorname{lowbit}假设 nn 最低位的 11 在第 kk 位上,那么 nn 取反后 kk 以后所有的位数全是 11kk 位为 00nn 是正数,取反后变成负数,补码要加 11,刚好 kk 位又回到 11。那么 nn 与取反后的 n-n 做与运算(常识:与运算是将两个数的补码运算)由于 kk 位前面的数全由于取反导致与的值为 00,所以结果就是 kk 位为 11 其它全是 00。即 lowbit(n)\operatorname{lowbit}(n) 可写作 nand(n)n\operatorname{and}(-n)
    4. 处理好判断异或值了,接下来就要考虑正式操作。统计两种操作中代价较小的次数ansans,则代价大的操作次数为 n1ansn-1-ans。用两者分别乘上每次的代价 min(C1,C2)\min(C_1,C_2)max(C1,C2)\max(C_1,C_2) 就能得出正确结果!
    5. 考虑并查集(是的,你没听错,这题还与并查集挂钩)。不难理解,操作不会改变数本身的大小,只需要删掉即可。那么把操作过的数并在一个集合里,每操作一次判断操作的两个数在不在同一集合,不在则并在一起,同时操作次数加一。

    喜闻乐见的代码时间

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int M=1e4+5;
    int n,c1,c2,a[M],ans=0,f[M];
    int findfather(int x)//并查集标准模板:找爹 
    {
    	if(f[x]!=x)f[x]=findfather(f[x]);
    	return f[x];
    }
    int lowbit(int x)//找最低的值为1的位 
    {
    	return x&(-x);
    }
    signed main()
    {
    	cin>>n>>c1>>c2;
    	for(int i=1;i<=n;i++)
    	{
    		cin>>a[i];f[i]=i;
    	}
    	sort(a+1,a+n+1);//可不排 
    	if(c1==c2)//判断,如果相等则怎么操作代价都一样 
    	{
    		cout<<(n-1)*c1;
    		return 0;
    	}	
    	for(int i=1;i<n;i++) 
    	{
    		for(int j=i+1;j<=n;j++)
    		{
    			int pd=0,xornum=a[i]^a[j];
    			if((xornum||n<=10)&&lowbit(xornum)==xornum)pd=1;//xornum表示异或值不为0,也就是不等 
    			if((c1<c2&&pd)||(c1>c2&&!pd))//C1划算就要异或值只有一位为1,也就是pd成立 
    			{
    				int f1=findfather(i),f2=findfather(j);//并查集 
    				if(f1!=f2)
    				{
    					ans++;f[f1]=f2;
    				}
    			}
    		}
    	}
    	cout<<(max(c1,c2)*(n-1-ans)+min(c1,c2)*ans);//简单乘法 
    	return 0;
    }
    //最难的黄题搞定了!!! 
    

    参考资料:lowbit\operatorname{lowbit} 详解。 参考 ShanireZ 大佬的做法。 大佬的讨论 同时也是判断异或中 n10n\le10 的由来(数据的锅,不能保证数据正确)。完结撒花~

    • 1

    [传智杯 #3 初赛] 游戏(征集数据)

    信息

    ID
    6322
    时间
    1000ms
    内存
    128MiB
    难度
    3
    标签
    递交数
    0
    已通过
    0
    上传者