Codeforces Round 916 (Div. 3) G2. Light Bulbs (Hard Version)(思维题 随机化哈希)

题目

2n(2<=n<=2e5)个灯泡,

灯泡分n种,每种颜色恰有两个,灯泡颜色用1到i表示

你可以执行以下两种操作若干次:

1. 选择两个同色的灯泡i、j,如果一个亮,但是另一个不亮,就把另一个点亮

2. 选择三个不同位置的灯泡i,j,k(i

你初始时,可以手动点亮若干个灯泡,

求初始时最少手动点亮灯泡的个数,以及满足个数等于最少灯泡数时选择灯泡的方案数,

使得后续通过以上两种操作,可以将所有灯泡都点亮

答案对998244353取模

实际t(t<=1e4)组样例,但保证sumn不超过2e5

思路来源

jiangly代码

题解

手玩不难发现,对于一个区间,

如果完整包含另一个区间,1 2 2 1,则内层灯泡无需初始点亮

即区间包含内层区间无需点亮,区间相交时,点亮两个区间任意一个都可以

1 2 3 3 5 1 2 5 6 7 7 6,

对于这种情况,显然是1、2、5三个灯泡中选一个,然后6这个灯泡必取

也就是说,整个2n长度的区间,

首先可以看成是最少个不相交的区间

满足每个区间内所有灯泡都出现两次,称每个这样的区间是一个闭区间

其中1 2 3 3 5 1 2 5是一个区间,6 7 7 6这个是一个区间

这个可以通过随机化哈希的方式实现,为每个颜色random一个哈希值,求前缀异或,

冲突的概率很低,所以前缀异或和为0的位置即认为是合法的分界位置

第一问就解决了,即前缀哈希值为0的个数

考虑第二问,对于形如1 2 3 3 5 1 2 5这样的区间,

希望先1跳到2,2再跳到5,5跳到1跳到2跳到5,

也就是跳跃过程中,忽略不会出现在最小个数的数,

其他位置的值则由于区间相交,则对方案数都有贡献,

最后125的这6个位置任取一个就能覆盖全部

观察跳跃过程,实际每次是跳跃到前缀异或值的最后一个位置,

每个前缀异或值,要么出现一次,要么中间包含了若干闭区间

导致这样的异或值是可以出现两次的,

那么对于出现两次的位置,肯定是选择忽略到中间异或和为0的这一段

所以每次的操作是将当前位置j,

飞到异或值相同的最后一个位置,j=las[sum[j]],再令j++,到下一个区间相交的位置

统计每个闭区间段内有贡献的值,最后将若干个闭区间的值相乘,即为第二问方案数

代码

#include
// #include
// #include
// #include
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<las;
int main(){
    //freopen("1.out","w",stdout);
    sci(t);
    while(t--){
        sci(n);
        rep(i,1,n)hs[i]=rng();
        int cnt=0,ans=1;
        las.clear();
        rep(i,1,2*n){
            sci(a[i]);
            sum[i]=sum[i-1]^hs[a[i]];
            las[sum[i]]=i;
            if(!sum[i])cnt++;
            //printf("i:%d sum:%d\n",i,sum[i]);
        }
        rep(i,0,2*n-1){
            if(sum[i])continue;
            int j=i+1,c=1; // 1 2 2 1
            while(sum[j]){// 1 2 3 1 3 2
                j=las[sum[j]]; // 走到相同异或值的最后一个位置
                j++;
                c++;
                if(!sum[j])break;
                //printf("i:%d j:%d\n",i,j);
            }
            ans=1ll*ans*c%mod;
        }
        printf("%d %d\n",cnt,ans);
    }
    return 0;
}

你可能感兴趣的:(思维题,#,哈希,思维题,随机化哈希)