题解 P3627 【[APIO2009]抢掠计划】

这道题前面的思路和其他题解一样,都是tarjan缩点。然后是要求金币的最大值,我用的是记忆化搜索,用jiyi[i]来表示从编号为i的联通块出发,最多可以抢到多少钱,搜索时如果遇到已经搜过的点(知道从那个点开始最多能抢多少钱)就直接return。因为记忆化搜索每个点只会遍历一次,所以记忆化搜索的复杂度应该是O(n)。

另外我的码风有点奇怪,先解释一下:stk[tk]分别是栈和栈的指针(tarjan用),num表示联通块个数,tim时间戳,shuyu[i]表示点i属于哪个联通块,he[]表示某个联通块内总共有多少钱,vis[]表示一个点是否在栈中,jiuba[]表示某个点是否有酒吧,suojb[]表示缩点后的连通块中是否有酒吧。bian[],head[],ecnt都是缩点前图中的边,如果后面有后缀x,则表示缩点后新图中的边;

#include<bits/stdc++.h>
using namespace std;
const int N=500005;
int n,m,ecnt,s,p,tk,num,tim,ecntx,ans;
int qian[N],head[N],dfn[N],low[N],stk[N],shuyu[N],he[N],headx[N],jiyi[N];
bool vis[N],jiuba[N],suojb[N];
struct aaa
{
	int to,nxt;
}bian[N],bianx[N];
inline int read()//标准快读
{
    int x=0;bool f=0;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return f?-x:x;
}
inline void add(int a,int b)//链式前向星加边;
{
	bian[++ecnt].to=b;
	bian[ecnt].nxt=head[a];
	head[a]=ecnt;
}
inline void addx(int a,int b)//缩点后联通块与联通块加边;
{
	bianx[++ecntx].to=b;
	bianx[ecntx].nxt=headx[a];
	headx[a]=ecntx;
}
void tarjan(int u)//标准tarjan
{
	dfn[u]=low[u]=++tim;
	stk[++tk]=u;vis[u]=1;
	for(int i=head[u];i;i=bian[i].nxt)
	{
		int v=bian[i].to;
		if(!dfn[v])
			tarjan(v),low[u]=min(low[u],low[v]);
		else if(vis[v])low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u])
	{
		num++;
		while(stk[tk]!=u)
		{
			shuyu[stk[tk]]=num;//记录点属于的联通块;
			vis[stk[tk]]=0;
			stk[tk]=0;tk--;
		}
		shuyu[stk[tk]]=num;//此时栈顶还有一个点在此联通块中,再执行一次
		vis[stk[tk]]=0;
		stk[tk]=0;tk--;
	}
}
void suo()//缩点;
{
	for(int i=1;i<=n;i++)
	{
		he[shuyu[i]]+=qian[i];//更新每个联通块内的总钱数
		if(jiuba[i])suojb[shuyu[i]]=1;更新联通块内是否有酒吧
		for(int j=head[i];j;j=bian[j].nxt)
		{
			int v=bian[j].to;
			if(shuyu[i]!=shuyu[v])
				addx(shuyu[i],shuyu[v]);//如果起点和终点不在同一个联通块,就在这两个联通块之间加边;
		}
	}
}
int dfs(int dian)//缩了点,新图为有向无环图,此时才能dfs
{
	if(jiyi[dian])return jiyi[dian];//如果这个联通块已经被搜索过,即我们知道从这个点开始的最大钱数,就直接返回;
	if(headx[dian]==0&&suojb[dian]==1)//如果这个联通块出度为0并且此处有酒吧,就可以在此处停止,返回这个联通块的总钱数;
	{
		jiyi[dian]=he[dian];
		return he[dian];
	}
	if(headx[dian]==0&&suojb[dian]==0)return 0;//如果这个联通块出度为0但是没有酒吧,就不能停止在此处,所以它对答案的贡献是0;
	for(int i=headx[dian];i;i=bianx[i].nxt)
	{
		jiyi[dian]=max(jiyi[dian],dfs(bianx[i].to));//一个联通块的多个出度中取一个最大的;
	}
	if(jiyi[dian]==0&&suojb[dian]==0)return 0;
   	//注意:我们此时少考虑了一种情况,如果一条路径上一连串的点都没有酒吧,那么劫匪是不可以在这条路径上停下的,但如果没了这句话,程序还是会走这条路;加上这句话,如果后面的点没有酒吧 并且 此处也没有酒吧,那么它对答案的贡献是0,直接return 0;
	jiyi[dian]+=he[dian];//把自己的点权加进记忆化数组中;
	return jiyi[dian];//返回从这点开始的最大钱数;
}
int main()
{
	int a,b;
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		a=read();b=read();
		add(a,b);
                //有个奇怪的错误,不能写add(read(),read());否则会出错,我也不知道为什么;
	}
	for(int i=1;i<=n;i++)
		qian[i]=read();
	s=read();p=read();
	for(int i=1;i<=p;i++)
		jiuba[read()]=1;
	for(int i=1;i<=n;i++)
		if(!dfn[i])
			tarjan(i);
	suo();
	ans=dfs(shuyu[s]);
	cout<<ans;
	return 0;
}

  

转载于:https://www.cnblogs.com/oierjzy/p/11229466.html

题目描述 有 $n$ 个人,每个人有一个编号 $i$,每个人都跳舞,但是每个人都只会一种舞蹈。现在要求他们排成一个圆圈跳舞,使得相邻两个人跳的舞蹈不同。求方案数。 输入格式 一个整数 $n$。 输出格式 一个整数,表示方案数,由于答案可能很大,输出对 $10^9+7$ 取模的结果。 数据范围 $1\leq n\leq 10^5$ 输入样例1: 5 输出样例1: 20 输入样例2: 10 输出样例2: 14684570 算法 数学,组合数学,动态规划 思路 题目要求的是排成一个圆圈跳舞,而且相邻两个人跳舞的舞蹈不同,这就意味着最后一个人的舞蹈类型必须和第一个人不同。因为它们是相邻的。 如果我们考虑将最后一个人的舞蹈类型和第一个人不同的方案数,那么实际上就是将 $n$ 个人分成两组: - 第一组是前 $n-1$ 个人,需要满足相邻两个人跳舞的舞蹈不同。 - 第二组是第 $n$ 个人,需要满足和第一个人跳舞的舞蹈不同。 对于第一组,我们可以定义 $f[i]$ 表示前 $i$ 个人,最后一个人和第一个人跳舞的舞蹈类型不同的方案数。因为需要满足相邻两个人跳舞的舞蹈不同,所以有两种情况: - 如果第 $i$ 个人和第 $i-1$ 个人跳舞的舞蹈类型不同,那么最后一个人的舞蹈类型可以是除了第 $i-1$ 个人和第一个人外的所有舞蹈类型,即共有 $n-2$ 种选择。 - 如果第 $i$ 个人和第 $i-1$ 个人跳舞的舞蹈类型相同,那么最后一个人的舞蹈类型只能是第 $i-1$ 个人和第一个人的舞蹈类型中的一种,即共有 $2$ 种选择。 综上所述,递推式为: $$f[i]=\begin{cases} (n-2)\times f[i-1] + 2\times f[i-2], & a[i]\neq a[i-1] \\ (n-1)\times f[i-1], & a[i]=a[i-1] \end{cases}$$ 对于第二组,最后一个人的舞蹈类型只能是除了第一个人的所有舞蹈类型,即共有 $n-1$ 种选择。 因此我们可以得到最终的方案数: $$ans=(n-1)\times f[n-1]$$ 代码 时间复杂度 $O(n)$ 空间复杂度 $O(n)$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值