首页 > 其他分享 >CF1914 G Light Bulbs 题解

CF1914 G Light Bulbs 题解

时间:2023-12-20 12:15:03浏览次数:32  
标签:ch 包含 int 题解 线段 st CF1914 vector Light

Link

CF1914 G Light Bulbs

Question

有 \(2n\) 盏灯摆放在一条直线上,每盏灯有一个颜色 \(a_i\) ,灯的颜色一共有 \(n\) 种,每个颜色的颜色的灯刚好两盏,灯开始都是熄灭的。你选择几盏灯先打开,然后通过以下规则让其他的灯打开

  • 选择 \(i,j\) 是相同颜色的,一盏亮,一盏不亮,你可以使两盏灯都亮起来
  • 选择 \(i,j,k\) 满足 \(i<j<k\) ,\(i,k\) 是相同颜色的,且 \(i,k\) 都是亮着的,你可以使 \(k\) 亮起来

求要让若有灯都亮起来,先打开的灯的最少数量和方案数

Solution

这道题有两个版本,easy 版是 \(O(n^2)\) 的,hard 版是 \(O(n)\) 的

首先,我们思考一个性质,就是一盏灯亮起来,另外一个相同颜色的灯也可以亮起来,这两个相同颜色的灯的中间的灯也能亮起来

我们可以把一种颜色的颜色的灯抽象成一条线段 \([L,R]\)

如果两条线段相交时,点亮其中一盏灯就可以点亮相交线段中的所有灯

image.png

如果两条线段包含,那么点亮外面的那条线段中的一盏灯即可

image.png

这样的相互关系有点像图论,我们就可以建边了

如果一个线段 \(A\) 的端点在另外一个线段 \(B\) 之中,那么建一条 \(B\Rightarrow A\) 的边,表示只需选择 \(B\) 中的一个端点就可以点亮 \(A\) 了

这个图中可能包含环,例如相交的情况,\(B \Rightarrow A\) 且 \(A \Rightarrow B\)

对于一个强连通分量,我们只需要点亮其中一盏灯就可以了

那么用 Tarjan 缩点,对于缩点后入度为 \(0\) 的点就需要手动点亮了,所以需要提前点亮的灯的数量就是缩点后入度为 \(0\) 的数量,方案数就是入度为 \(0\) 的点所包括灯的数量的乘积

这样做的时间复杂度是 \(O(n^2)\) 的,建边应该可以优化,但是我想不到


考虑另外一种解法

定义 \(F[i]\) 为前 \(i\) 个数的异或和,考虑到一个数异或两次对于没有异或,我们可以利用这个性质来解这道题

我们称一条线段里面的所有灯的种类都出现过两次,称这个线段"完全包含"

例如 1,2,2,11,2,3,2,3,1 ,在这两种情况里面 \(1\) 都是完全包含的,如果一个线段完全包含,那么我们只需要取左右两边那个灯,就可以把里面的灯全部点亮了

如果不是完全包含的情况,例如 1,2,1,2 那么,就可以取任意一盏灯

如果要取一盏灯,当且仅当,这盏灯没有 "被包含",用异或的语言标答,就是前面的异或和为 \(0\)

如果这盏灯对应的线段是 "完全包含" 的,那么直接跳到下一盏一样颜色的灯,否则就往下继续寻找 “完全包含” 的线段,然后跳过找到的 "完全包含" 线段

答案的数量就是没有 "被包含" 的线段数量,方案数就是没有 “被包含” 的线段块 中灯的数量 的乘积

每个数最多被跳 \(1\) 次,所以复杂度为 \(O(n)\)

Code

Tarjan缩点

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL TT=998244353;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

struct Line{
    int L,R;
    Line(int L=0,int R=0):L(L),R(R){}
    bool operator <(const Line B) const{return L<B.L;}
};

vector<vector<int> > E;
struct Tarjan{
    int n;
    vector<int>dfn,low;int dfn_cnt;
    vector<int> scc;int sc; //节点 i 所在 SCC 的编号
    vector<int> siz; //强连通 i 的大小
    stack<int> st;
    vector<int> in_st;//判断是否在栈内

    void init(int n){
        this->n=n;
        dfn.resize(n+1);low.resize(n+1);dfn_cnt=0;
        scc.assign(n+1,0);sc=0;siz.assign(n+1,0);
        while(!st.empty())st.pop();
        in_st.assign(n+1,0);
    }

    void tarjan(int u){
        low[u]=dfn[u]=++dfn_cnt;st.push(u);in_st[u]=1;
        for(int i=0;i<E[u].size();i++){
            int& v=E[u][i];
            if(!dfn[v]){//没有访问过
                tarjan(v);
                low[u]=min(low[u],low[v]);
            }else if(in_st[v]){
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u]==low[u]){ //找到双连通分量了
            ++sc; 
            while(st.top()!=u){ //从栈顶到 u 都是这个强连通分量里面的
                scc[st.top()]=sc;siz[sc]++;
                in_st[st.top()]=0;st.pop();
            }
            scc[st.top()]=sc;siz[sc]++;
            in_st[st.top()]=0;st.pop();
        }
        return ;
    }
};

void solve(){
    int n=read();
    LL ans1=0,ans2=1;
    Tarjan F;F.init(n);
    vector<int> a(2*n+1);
    vector<Line> L(n+1,Line(0,0));
    E.assign(n+1,vector<int>());
    for(int i=1;i<=2*n;i++) a[i]=read();
    for(int i=1;i<=2*n;i++){
        if(L[a[i]].L==0) L[a[i]].L=i;
        else L[a[i]].R=i;
    }
    sort(L.begin()+1,L.begin()+1+n);
    for(int i=1;i<=n;i++)
    for(int j=max(1,i-1000);j<=min(n,i+1000);j++){
        if(i==j)continue;
        if((L[i].L<L[j].L&&L[i].R>L[j].L)||(L[i].L<L[j].R&&L[i].R>L[j].R)){ //j in i
            // printf(":%d %d\n",i,j);
            E[i].push_back(j);
        }
    }
    for(int i=1;i<=n;i++) if(!F.dfn[i])
        F.tarjan(i);
    
    vector<int> du(n+1,0);
    for(int u=1;u<=n;u++){
        for(int& v:E[u]){
            if(F.scc[u]!=F.scc[v])
                du[F.scc[v]]++;
        }
    }
    for(int i=1;i<=F.sc;i++){
        if(du[i]==0){
            ans1++;
            ans2=ans2*(F.siz[i]*2)%TT;
        }
    }
    printf("%lld %lld\n",ans1,ans2);
}
int main(){
    freopen("G1.in","r",stdin);
    int t=read();
    while(t--) solve();
}

Xor

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL TT=998244353;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int brand(){return (rand()<<15)|rand();}
LL bbrand(){
    return ((LL)brand()<<30)|brand();
}
void solve(){
    int n=read();LL ans1=0,ans2=1;
    vector<int> a(n*2,0);
    vector<LL> x(n,0);
    vector<LL> F(n*2+1,0);
    for(int i=0;i<n;i++)
        x[i]=bbrand()^2333;
    for(int i=0;i<2*n;i++){
        a[i]=read()-1;F[i+1]=F[i]^x[a[i]];  //F[i] 表示前 i-1 个数的异或和
    }
    map<LL,int> lst;
    for(int i=0;i<2*n;i++)
        lst[F[i]]=i;
    for(int i=0;i<2*n;i++){
        if(F[i]==0){
            LL cnt=1;ans1++;
            int j=i+1;
            while(F[j]!=0){
                cnt++;
                j=lst[F[j]];
                j++;
            }
            ans2=ans2*cnt%TT;
        }
    }
    cout<<ans1<<" "<<ans2<<endl;
}
int main(){
    freopen("G2.in","r",stdin);
    srand(time(0));
    int T=read();
    while(T--) solve();
}

标签:ch,包含,int,题解,线段,st,CF1914,vector,Light
From: https://www.cnblogs.com/martian148/p/17916234.html

相关文章

  • CF1914F Programming Competition
    原题链接感觉有点类似agc034eCompleteCompress,但那题比这个难得多。定义\(f_x\)为以\(x\)为根的子树中,尽可能组队后最多剩下多少人,\(siz_x\)为子树大小。记\(y\inson(x)\)中\(f_y\)最大的点为\(hson_x\)。当\(\sum\limits_{y\inson(x),y\not=hson_x}siz_y<......
  • 阿里云ECS自建K8S_IPV6重启后异常问题解决过程
    阿里云ECS自建K8S_IPV6重启后异常问题解决过程背景最近安装了一个单节点的K8S_IPV6昨天不知道何故突然宕机了.然后只能在阿里云的控制台后台重启了ECS启动之后看K8S的状态一开始是正常的.但是查看ing的时候,发现IP地址却变成了IPV4的地址,感觉比较奇怪.这里整理一下......
  • CF1872C-Non-coprime-Split-题解
    title:CF1872CNon-coprimeSplit题解date:2023-09-1821:09:14categories:-题解一个很怪的分讨想法。当\(l\neqr\)时,区间内一定有一个偶数。设最大的偶数为\(x\),那么当\(x>2\)时,可以得到一组解\((2,x-2)\),此时\(\gcd(2,x-2)=2\)。当\(l=r\)时,问题......
  • CF1870B-Friendly-Arrays-题解
    title:CF1870BFriendlyArrays题解date:2023-09-2010:32:12categories:-题解翻译给出长度为\(n\)的序列\(a\)和长度为\(m\)的序列\(b\),选出\(b\)中的任意个数(可以不选),让\(a\)的每个数都或上它们,求\(a_1\oplusa_2\oplus\dots\oplusa_n\)的最大值......
  • CF1861C-Queries-for-the-Array-题解
    title:CF1861CQueriesfortheArray题解date:2023-09-0607:53:53categories:-题解因为插入和删除操作都在队尾,所以对序列前缀分析一下:若一个序列的答案为YES,那么它前缀的答案也为YES。(对于没检查过的序列)若一个序列的答案为NO,那么它前缀的答案不确定。(对于没......
  • CF1703E-Mirror-Grid-题解
    title:CF1703EMirrorGrid题解date:2022-07-1511:54:20categories:-题解题目大意给出一个由\(0,1\)组成的矩阵,求最少改变矩阵中的多少个数,使得矩阵旋转\(0^\circ,90^\circ,180^\circ,270^\circ\)后相同。思路在一个\(n\timesn\)的矩阵中,对于任意一......
  • CF1593E-Gardener-and-Tree-题解
    title:CF1593EGardenerandTree题解date:2022-05-2721:30:48categories:-题解原题面题意:给出一个\(n\)个点的树,删除\(k\)次叶子节点,求剩下的节点数。思路:设\(cnt_i\)为\(k\)最小为多少时点\(i\)会被删除。当\(i\)将要被删除时,它一定是现在的叶子,联......
  • CF1866B Battling with Numbers 题解
    前置知识:如果\(p=x^a,q=x^b\),那么\(\gcd(p,q)=x^{\min(a,b)},\operatorname{lcm}(p,q)=x^{\max(a,b)}\)。对于每个\(x\ina_i\),令\(x\)在\(Y\)中的指数为\(d_i\)(实际上不一定),计算贡献时,考虑将\(b_i\)与\(d_i\)分别放入\(p\)和\(q\)中:如果\(b_i<d_i\),贡献为......
  • CF1814B Long Legs 题解
    建议降黄令\(m\)最后的值为\(a\),那么此时最佳答案为\(a-1+\left\lceil\frac{x}{a}\right\rceil+\left\lceil\frac{y}{a}\right\rceil\),每次加尽量大的\(m\)一定最优。当\(x,y\)增大时,答案显然不降,考虑找到\(a\)的上界。用\(O(n)\)的暴力跑极限数据,发现答......
  • CF468C Hack it! 题解
    题意:给出一个数\(a\),构造一组\(l,r\)使得\(\sum_{i=l}^rf(i)\equiv0\pmoda\)。其中\(a\leq10^{18}\),\(l,r\leq10^{200}\)。分析:以下用\((l,r)\)表示构造出来的一对\(l,r\),\(f(l,r)=\sum_{i=l}^rf(i)\)。考虑从某个值一步一步移动到模\(a\)余\(0\)的情况。......