P4426 [HNOI/AHOI2018]毒瘤

挺不错的一个题。

题意即为求一个图的独立集方案数。

如果原图是一棵树,可以直接大力f[x][0/1]来dp。

由于非树边很少,考虑2^11容斥,强制某些点必选,然后再O(n)dp,这样应该过不了。

发现这个容斥本质上是对一些点进行修改,修改的形式是强制它必须选。

直接xjb上一个ddp就没了。

这里由于有可能会/0,考虑维护一下结尾0的个数就行。

复杂度的话,直接乱写是3^11*log^2的,但是实际上我们可以优化加点和删点的顺序。

考虑按照某一个特定的顺序去容斥。

我的想法是一个简单的贪心,每次找一个新加入的尽可能少的状态,复杂度玄学。

比较优秀的做法是dfs来构造集合,这样的修改总量是2^n的,十分优秀。

#include<bits/stdc++.h>
#define N 220000
#define eps 1e-7
#define inf 1e9+7
#define db double
#define ll long long
#define ldb long double
using namespace std;
inline int read()
{
    char ch=0;
    int x=0,flag=1;
    while(!isdigit(ch)){ch=getchar();if(ch=='-')flag=-1;}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*flag;
}
const int mo=998244353;
int ksm(int x,int k)
{
    int ans=1;
    while(k)
    {
        if(k&1)ans=1ll*ans*x%mo;
        k>>=1;x=1ll*x*x%mo; 
    }
    return ans;
}
int inv(int x){return ksm(x,mo-2);}
struct node
{
    int x,y;
    int w(){return y?0:x;}
};
node operator*(node a,int b)
{
    node ans=a;
    if(b)ans.x=1ll*ans.x*b%mo;else ans.y++;
    return ans;
}
node operator/(node a,int b)
{
    node ans=a;
    if(b)ans.x=1ll*ans.x*inv(b)%mo;else ans.y--;
    return ans;
}
struct matrix
{
    int s[2][2];
    void clear()
    {
        memset(s,0,sizeof(s));
    }
};
matrix operator*(matrix a,matrix b)
{
    matrix ans;ans.clear();
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<2;j++)for(int k=0;k<2;k++)
        ans.s[i][j]=(ans.s[i][j]+(1ll*a.s[i][k]*b.s[k][j]%mo))%mo;
    }
    return ans;
}
struct edge{int to,nxt;}e[N*2];
int num,f[N],head[N];
inline void add(int x,int y){e[++num]={y,head[x]};head[x]=num;}
int find(int x){if(x!=f[x])f[x]=find(f[x]);return f[x];}
bool merge(int x,int y){x=find(x);y=find(y);if(x==y)return false;f[x]=y;return true;}
struct link{int x,y;}q[N];
bool vis[N];
node a[N][2],b[N][2];
int times,p[N],d[N],id[N],fs[N],sz[N],fa[N],son[N],top[N],low[N],flag[N];
struct Segment_Tree
{
    #define lson o<<1
    #define rson o<<1|1
    #define mid ((l+r)>>1)
    matrix dp[N*4];
    inline void pushup(int o){dp[o]=dp[lson]*dp[rson];}
    void build(int o,int l,int r)
    {
        if(l==r)
        {
            int x=p[l];
            dp[o]={{{b[x][0].w(),b[x][0].w()},{b[x][1].w(),0}}};
            return;
        }
        build(lson,l,mid);build(rson,mid+1,r);pushup(o);
    }
    void optset(int o,int l,int r,int q)
    {
        if(l==r)
        {
            int x=p[l];
            dp[o]={{{b[x][0].w(),b[x][0].w()},{b[x][1].w(),0}}};
            return;
        }
        if(q<=mid)optset(lson,l,mid,q);
        else optset(rson,mid+1,r,q);
        pushup(o);
    }
    matrix query(int o,int l,int r,int ql,int qr)
    {
        if(ql<=l&&r<=qr)return dp[o];
        if(ql<=mid&&qr>mid)return query(lson,l,mid,ql,qr)*query(rson,mid+1,r,ql,qr);
        if(ql<=mid)return query(lson,l,mid,ql,qr);
        if(qr>mid)return query(rson,mid+1,r,ql,qr);
    }
}T;
matrix get(int x){return T.query(1,1,times,id[x],id[low[top[x]]]);}
void update(int x,int o)
{
    b[x][0]=b[x][0]/flag[x];flag[x]=o;b[x][0]=b[x][0]*flag[x];
    while(true)
    {
        matrix A=get(top[x]);
        T.optset(1,1,times,id[x]);
        matrix B=get(top[x]);
        
        x=fa[top[x]];if(!x)return;
        
        b[x][0]=b[x][0]/((A.s[0][0]+A.s[1][0])%mo);
        b[x][0]=b[x][0]*((B.s[0][0]+B.s[1][0])%mo);
        b[x][1]=b[x][1]/A.s[0][0];b[x][1]=b[x][1]*B.s[0][0];
    }
}
void dfs1(int x)
{
    sz[x]=1;
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa[x])continue;
        fa[to]=x;dfs1(to);sz[x]+=sz[to];
        if(sz[son[x]]<sz[to])son[x]=to;
    }
}
void dfs2(int x,int tp)
{
    p[++times]=x;id[x]=times;top[x]=tp;
    if(son[x])dfs2(son[x],tp);else low[tp]=x;
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int to=e[i].to;
        if(top[to])continue;
        dfs2(to,to);
    }
}
void DP(int x)
{
    a[x][0]=a[x][1]=b[x][0]=b[x][1]={1,0};
    for(int i=head[x];i!=-1;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa[x])continue;
        DP(to);
        a[x][0].x=1ll*a[x][0].x*(a[to][0].x+a[to][1].x)%mo;
        a[x][1].x=1ll*a[x][1].x*a[to][0].x%mo;
        if(to==son[x])continue;
        b[x][0].x=1ll*b[x][0].x*(a[to][0].x+a[to][1].x)%mo;
        b[x][1].x=1ll*b[x][1].x*a[to][0].x%mo;
    }
}
int main()
{
    int n=read(),m=read(),cnt=0;
    num=-1;memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)f[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        if(merge(x,y))add(x,y),add(y,x);
        else q[++cnt]={x,y};
    }   
    dfs1(1);dfs2(1,1);DP(1);T.build(1,1,times);
    for(int i=1;i<=n;i++)flag[i]=1;
    for(int i=0;i<(1<<cnt);i++)fs[i]=fs[i>>1]+(i&1);
    int ans=0;
    for(int o=0,lst=0;o<(1<<cnt);o++)
    {
        int s=-1;
        for(int i=0;i<(1<<cnt);i++)
        if(!vis[i])if(s==-1||fs[i^lst]<fs[s^lst])s=i;
        for(int i=1;i<=cnt;i++)if((1<<(i-1))&(s^lst))
        {
            int x=q[i].x,y=q[i].y;
            if(1<<(i-1)&s)
            {
                if(d[x]++==0)update(x,0);
                if(d[y]++==0)update(y,0);
            }
            else
            {
                if(d[x]--==1)update(x,1);
                if(d[y]--==1)update(y,1);
            }
        }
        vis[s]=true;lst=s;
        matrix t=get(1);
        if(fs[s]%2==0)ans=(ans+((t.s[0][0]+t.s[1][0])%mo))%mo;
        else ans=(ans-((t.s[0][0]+t.s[1][0])%mo))%mo;
    }
    printf("%d",(ans%mo+mo)%mo);
    return 0;
}

转载于:https://www.cnblogs.com/Creed-qwq/p/10629929.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值