zoukankan      html  css  js  c++  java
  • HNOI/AHOI2018

    毒瘤

    Luogu
    LOJ
    首先我们可以把题意转化为图的独立集计数。显然这个东西是个NP-Hard的。
    然后我们可以注意到(mle n+10),也就是说最多有(11)条非树边。
    我们现在先考虑一下,树上独立集计数怎么做。
    (f_{u,0/1})表示(u)点选/不选的方案数。
    那么转移方程就是:
    (f_{u,0}=prodlimits_{vin son_u}(f_{v,0}+f_{v,1}))
    (f_{u,1}=prodlimits_{vin son_u}f_{v,0})
    最后的答案就是(f_{1,0}+f_{1,1})
    然后我们知道现在有最多(11)条非树边,我们可以枚举每条非树边两端的状态,然后再跑一遍dp。
    这样每条非树边有((0,0),(0,1),(1,0))三种情况。可以发现((0,0),(0,1))这两种情况可以合并。这样就变成枚举每条非树边的起点选不选。
    所以我们就得到了一个(O(n2^{m-n+1}))优秀做法。根据常数可以获得(70sim80)分的好成绩。
    然后我们来想一件事情,我们每次做dp的时候,有很多转移是浪费的。
    我们可以把每个点的(f)看做一个向量,那么转移就可以看做是一个矩阵。
    然后我们会发现转移基本上都是不变的,变的只是我们枚举强制选不选的那些点的向量。
    这启发我们用虚树来处理dp部分。
    把枚举强制选不选的那些点看做是关键点,然后对于它们建一棵虚树,然后dp。
    所以现在我们需要求的就是虚树上的一条边(对应原树上的一条链)的转移的系数,这个东西我们可以通过在原树上自下而上dfs一遍求得。这个过程可能有点麻烦。
    实际上建虚树的过程也可以在上面这个dfs的过程中来完成。
    然后我们就可以在虚树上面跑dp了,时间复杂度为(O(n+(m-n+1)2^{m-n+1}))

    #include<bits/stdc++.h>
    #define pb push_back
    #define pi pair<int,int>
    #define fi first
    #define se second
    using namespace std;
    namespace IO
    {
        char ibuf[(1<<21)+1],*iS,*iT;
        char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
        int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
    }using namespace IO;
    const int N=100020,P=998244353;
    int inc(int a,int b){return a+=b,a>=P? a-P:a;}
    int mul(int a,int b){return 1ll*a*b%P;}
    int n,m;
    namespace Graph
    {
        vector<int>E[N];int T,cnt,dfn[N],size[N],vis[N],fa[N];
        struct edge{int u,v;}e[N];
        void add(int u,int v){E[u].pb(v),E[v].pb(u);}
        void dfs(int u)
        {
    	dfn[u]=++T;
    	for(int v:E[u])
    	    if(v^fa[u])
    		if(!dfn[v]) fa[v]=u,dfs(v),size[u]+=size[v];
    		else if(dfn[v]<dfn[u]) e[++cnt]={u,v},vis[u]=vis[v]=1;
    	vis[u]|=size[u]>=2,size[u]=size[u]||vis[u];
        }
    }using namespace Graph;
    namespace ITree
    {
        int f[N][2],g[N][2],p[N][2],is[N];pi k[N][2];
        pi operator+(pi a,pi b){return pi(inc(a.fi,b.fi),inc(a.se,b.se));}
        pi operator*(pi a,int k){return pi(mul(k,a.fi),mul(k,a.se));}
        struct node{int v;pi a,b;};vector<node>G[N];
        void Add(int u,int v,pi a,pi b){G[u].pb({v,a,b});}
        int build(int u)
        {
    	p[u][0]=p[u][1]=1,is[u]=1;int pos=0,w;
    	for(int v:E[u])
    	    if(!is[v])
    	    {
    		w=build(v);
    		if(!w) p[u][1]=mul(p[u][1],p[v][0]),p[u][0]=mul(p[u][0],inc(p[v][0],p[v][1]));
    		else if(vis[u]) Add(u,w,k[v][0]+k[v][1],k[v][0]);
    		else k[u][1]=k[v][0],k[u][0]=k[v][0]+k[v][1],pos=w;
    	    }
    	if(vis[u]) k[u][0]=pi(1,0),k[u][1]=pi(0,1),pos=u; else k[u][0]=k[u][0]*p[u][0],k[u][1]=k[u][1]*p[u][1];
    	return pos;
        }
        void dp(int u)
        {
    	(f[u][0]=g[u][1]? 0:p[u][0]),(f[u][1]=g[u][0]? 0:p[u][1]);
    	for(auto [v,a,b]:G[u])
    	{
    	    dp(v);int p=f[v][0],q=f[v][1];
    	    f[u][0]=mul(f[u][0],inc(mul(a.fi,p),mul(a.se,q)));
    	    f[u][1]=mul(f[u][1],inc(mul(b.fi,p),mul(b.se,q)));
    	}
        }
    }using namespace ITree;
    int main()
    {
        n=read(),m=read();int ans=0;
        for(int i=1,u,v;i<=m;++i) u=read(),v=read(),add(u,v);
        dfs(1),vis[1]=1,build(1);
        for(int S=0,i;S<1<<cnt;++S)
        {
    	for(i=1;i<=cnt;++i) if(S>>(i-1)&1) g[e[i].u][1]=g[e[i].v][0]=1; else g[e[i].u][0]=1;
    	dp(1),ans=inc(ans,inc(f[1][1],f[1][0]));
    	for(i=1;i<=cnt;++i) if(S>>(i-1)&1) g[e[i].u][1]=g[e[i].v][0]=0; else g[e[i].u][0]=0;
        }
        printf("%d",ans);
    }
    

    游戏

    Luogu
    LOJ
    我们要求出(l_i,r_i)表示(i)最远能够到达的最左边和最右边的格子。
    首先有一个比较简单的暴力,就是每次我们选择一个格子,然后从当前格子开始往左右暴力扩展,找到能够到达的最远的格子。
    然后对于这个暴力,我们有一个小小的优化:就是假如我们从左往右处理,当前的点是(i),新扩展到了一个左边的节点(j),那么我们可以直接拿(l_j)赋给(l_i),如此重复。右边同理。
    这样的暴力是可以被卡成(O(n^2))的。但是题目数据水所以可以通过。
    现在我们通过一些优化来使得这个暴力的复杂度变得正确。
    对于一段连续的没有门的格子,我们可以把它缩成一个格子。
    然后对于一个门和钥匙((x,y)),如果(y>x),我们就从(x)(x+1)建一条边,意思是无法从(x)走到(x+1)(y<x)同理。
    这样我们再拓扑排序,因为点(u)的答案区间一定不会包含拓扑序在它后面的点的答案区间,所以按照拓扑序转移就能够保证复杂的正确性了,最后复杂度为(O(n+m))

    #include<bits/stdc++.h>
    #define pb push_back
    using namespace std;
    namespace IO
    {
        char ibuf[(1<<21)+1],*iS,*iT;
        char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
        int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
    }
    using namespace IO;
    const int N=1000007;
    vector<int>E[N];queue<int>q;
    int n,m,Q,x[N],y[N],pos[N],cnt=1,l[N],r[N],f[N],deg[N],bel[N];
    void add(int u,int v){E[u].pb(v),++deg[v];}
    int check(int x,int y)
    {
        if(!y||y==cnt+1) return 0;
        if(x<y) --y;
        return l[x]<=pos[y]&&pos[y]<=r[x];
    }
    void toposort()
    {
        for(int i=1;i<=cnt;++i) if(!deg[i]) q.push(i);
        for(int u,f;!q.empty();)
        {
    	u=q.front(),q.pop(),f=1;
    	while(f)
    	{
    	    f=0;
    	    while(check(u,l[u]-1)) l[u]=l[l[u]-1],f=1;
    	    while(check(u,r[u]+1)) r[u]=r[r[u]+1],f=1;
    	}
    	for(int v:E[u]) if(!(--deg[v])) q.push(v);
        }
    }
    int main()
    {
        n=read(),m=read(),Q=read();
        for(int i=1;i<=m;++i) x[i]=read(),y[i]=read(),f[x[i]]=1;
        f[n]=1;
        for(int i=1;i<=n;++i) if(bel[i]=cnt,f[i]) l[cnt]=r[cnt]=cnt,++cnt;
        --cnt;
        for(int i=1,u,v;i<=m;++i)
        {
    	u=bel[x[i]],v=bel[y[i]],pos[u]=v;
    	if(v<=u) add(u+1,u); else add(u,u+1);
        }
        toposort();
        for(int u,v;Q;--Q) u=bel[read()],v=bel[read()],puts(l[u]<=v&&v<=r[u]? "YES":"NO");
    }
    

    排列

    Luogu
    LOJ
    我们先转化一下题意。
    对于(a_j)(a_j)一定在(j)前面,所以我们从(a_j)(j)连一条边。
    显然每个点入度为(1),以(0)为根dfs,如果图不联通或者存在环,那么一定无解。
    我们知道,对于最小的(w_i)(i)要尽量在前。
    也就是如果(fa_i=0),那么我们直接选,否则在选了(fa_i)之后立刻选(i)
    所以我们每次选一个(w_i)最小的(i),把(i)合并到(fa_i)上去。合并之后一个点就变成了一个序列。
    合并两个序列时,我们可以发现,(w)平均值越小的序列排在前面越优秀。
    所以我们把每个点上的序列放进堆,按平均值升序排序,每次取出堆顶和其(fa)合并,再把新的加入堆。期间需要使用并查集维护序列长度(防止一个点访问多次)以及某个编号所属序列。
    答案边合并边求和即可。

    #include<bits/stdc++.h>
    #define LL long long
    #define pb push_back
    using namespace std;
    const int N=500007;
    int n,cnt,fa[N],f[N],size[N],vis[N];LL ans,w[N];
    vector<int>E[N];
    struct node{int u,size;LL w;}t;
    int operator<(node a,node b){return a.w*b.size>b.w*a.size;}
    priority_queue<node>q;
    void dfs(int u){vis[u]=1,++cnt; for(int v:E[u]) if(vis[v])puts("-1"),exit(0); else dfs(v);}
    int Find(int x){return f[x]==x? x:f[x]=Find(f[x]);}
    int read(){int x=0;char c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;}
    int main()
    {
        n=read();int i,u,v;
        for(i=1;i<=n;++i) fa[i]=read(),E[fa[i]].pb(i);
        dfs(0);if(cnt<=n) return !printf("-1");
        for(i=0;i<=n;++i) f[i]=i,size[i]=1;
        for(i=1;i<=n;++i) w[i]=read(),q.push((node){i,1,w[i]});
        while(!q.empty())
        {
            t=q.top(),q.pop();
            if(size[u=Find(t.u)]^t.size) continue;
            v=f[u]=Find(fa[u]),ans+=w[u]*size[v],w[v]+=w[u],size[v]+=size[u];
            if(v) q.push((node){v,size[v],w[v]});
        }
        return !printf("%lld",ans);
    }
    

    道路

    Luogu
    LOJ
    注意到(n)不大并且深度不大。
    ((u,ls_u))(L)边,((u,rs_u))(R)边。
    所以我们可以设(f_{p,i,j})表示从根到(p)(i)条未标记的(L)边和(j)条未标记的(R)边的最小答案。
    对于叶子结点,枚举(i,j)套题目给的公式。
    对非叶子节点,(f_{p,i,j}=min(f_{ls_p,i+1,j}+f_{rs_p,i,j+1},f_{ls_p,i,j+1}+f_{rs_p,i+1,j}))
    注意到我们是在二叉树上dfs,所以对于一个点,我们计算完其儿子后,其儿子的(f)就不需要再用了。这个可以省空间。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int N=40007;
    int n,a[N],b[N],c[N],ls[N],rs[N];ll f[81][41][41];
    int read(){int x;scanf("%d",&x);return x;}
    int get(){int x=read();return x>0? x:n-x;}
    void dfs(int u,int k,int l,int r)
    {
        if(!ls[u])
        {
    	for(int i=0,j;i<=l;++i) for(j=0;j<=r;++j) f[k][i][j]=1ll*c[u]*(a[u]+i)*(b[u]+j);
    	return ;
        }
        dfs(ls[u],k+1,l+1,r),dfs(rs[u],k+2,l,r+1);
        for(int i=0,j;i<=l;++i) for(j=0;j<=r;++j) f[k][i][j]=min(f[k+1][i+1][j]+f[k+2][i][j],f[k+1][i][j]+f[k+2][i][j+1]);
    }
    int main()
    {
        n=read();int i;
        for(i=1;i<n;++i) ls[i]=get(),rs[i]=get();
        for(i=1;i<=n;++i) a[i+n]=read(),b[i+n]=read(),c[i+n]=read();
        dfs(1,1,0,0),cout<<f[1][0][0];
    }
    
  • 相关阅读:
    将make的输出重定向到文件
    ubuntu mount u盘以及cp拷贝文件夹
    Emacs Tutorial摘录
    c#实现每隔一段时间执行代码(多线程)
    socket.BeginReceiveFrom异步接收信息时,回调函数无法正常进入
    23个C#实用技巧
    C#中实现Form的透明属性变化即渐隐效果
    C#键位定制:设置一个文本框只能输入数字键
    byte 与 bit 的转换
    C# Socket UDP 案例 2
  • 原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/13043837.html
Copyright © 2011-2022 走看看