• CF1149D Abandoning Roads


    一、题目

    点此看题

    二、解法

    套路:当只有两个关键状态量时,我们以一个量为主,一个量为辅思考问题。

    那么我们以 (a) 边为主,因为不可能表示出原图的最小生成树所以我们开始找结论。根据 ( t kruskall) 算法我们先把所有 (a) 边连起来,那么会形成若干个 (a) 边连通块,考虑 (b) 边会把这些连通块串成一棵生成树。

    结论:(a ightarrow b) 的路径可能在最小生成树中出现,当且仅当这条路径没有通过 (b) 边走出 (a) 连通块再通过 (b) 边走回来,并且不经过 (a) 连通块内的 (b),因为走出去和走回来的两条 (b) 边是不可能同时出现的,而 (a) 连通块内的 (b) 边无论如何都不在最小生成树中。但 (a) 连通块内部的 (a) 边路径和经过 (b) 边走到其他连通块的路径都是合法的,证毕。

    知道这个结论以后就得到了一个最短路问题,由于限制有不能走回以前的连通块,所以我们记录已经离开的连通块有哪些。设 (dp[s][u])离开过的连通块集合(s),现在所处的点为 (u),然后跑 ( t dijkstra) 即可。

    但是 (s) 可能很大,考虑大小 (leq 3) 的连通块是不需要记录在 (s) 中的,因为最优决策下绝不会出现通过 (b) 边走出去再走回来的情况,那么时间复杂度 (O(2^{n/4}cdot mcdot log))

    三、总结

    首先是开头那个很重要的套路。

    此外猜结论要对着要求的东西猜,比如这道题要求的是最短路就找最短路合法的条件。

    最后是有关连通块的状压,可以讨论一些较小的连通块可以大幅度降低复杂度。

    #include <cstdio>
    #include <cstring>
    #include <queue>
    using namespace std;
    const int M = 75;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a,b,tot,cnt,tmp,id[M],vis[M],f[M],dp[1<<17][M];
    struct edge
    {
    	int v,c,next;
    }e[M*M];
    struct node
    {
    	int s,u,c;
    	bool operator < (const node &b) const
    	{
    		return c>b.c;
    	}
    };
    void dfs(int u,int c)
    {
    	id[u]=c;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(id[v]==-1 && e[i].c==a) dfs(v,c);
    	}
    }
    int cal(int u)
    {
    	vis[u]=1;int r=1;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(!vis[v] && e[i].c==a) r+=cal(v);
    	}
    	return r;
    }
    void dijk()
    {
    	memset(dp,0x3f,sizeof dp);
    	priority_queue<node> q;
    	q.push(node{0,1,0});dp[0][1]=0;
    	while(!q.empty())
    	{
    		int s=q.top().s,u=q.top().u,c=q.top().c;
    		q.pop();
    		if(c>dp[s][u]) continue;
    		for(int i=f[u];i;i=e[i].next)
    		{
    			int v=e[i].v,ts=s;
    			//leave the block before
    			if(id[v]<tmp && (s&(1<<id[v]))) continue;
    			//in the same block but use b
    			if(id[u]==id[v] && e[i].c==b) continue;
    			//enter a brand new block
    			if(id[u]<tmp && id[u]!=id[v]) ts|=(1<<id[u]);
    			if(dp[ts][v]>dp[s][u]+e[i].c)
    			{
    				dp[ts][v]=dp[s][u]+e[i].c;
    				q.push(node{ts,v,dp[ts][v]});
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		int ans=1e9;
    		for(int j=0;j<(1<<tmp);j++)
    			ans=min(ans,dp[j][i]);
    		printf("%d ",ans);
    	}
    }
    signed main()
    {
    	n=read();m=read();a=read();b=read();
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read(),c=read();
    		e[++tot]=edge{v,c,f[u]},f[u]=tot;
    		e[++tot]=edge{u,c,f[v]},f[v]=tot;
    	}
    	memset(id,-1,sizeof id);
    	for(int i=1;i<=n;i++)
    		if(!vis[i] && cal(i)>=4) dfs(i,cnt++);
    	tmp=cnt;
    	memset(vis,0,sizeof vis);
    	for(int i=1;i<=n;i++)
    		if(!vis[i] && cal(i)<4) dfs(i,cnt++);
    	dijk();
    }
    
  • 相关阅读:
    CAS与ABA问题产生和解决
    OnCheckedChangeListener和setChecked之间冲突问题解决
    【二】 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否函数该整数。
    【一】设计一个类,我们只能生成该类的一个实例。
    深入学习semaphore
    RF-For循环使用
    【RF库Collections测试】List Should Contain Value
    RF采用SSHLibary库执行sudo命令,提示sudo: sorry, you must have a tty to run sudo错误的解决办法
    【RF库Collections测试】List Should Contain Value
    RF判断列表、字典、整数、字符串类型是否相同方法
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15072516.html
Copyright © 2020-2023  润新知