Evanyou Blog 彩带

[SCOI2011]糖果 题解

洛谷题面

看到很多题解并没有讲清楚这道题为什么可以用某些方法,套个板子就没了。蒟蒻就发一篇题解装X造福大家吧233

做这道题前,我推荐大家做一下一本通中的1352:【例4-13】奖金一题,因为有可能做完了这道题对于你们会有一点启发。

题目分析

题目对于小朋友的嫉妒一共有\(5\)中情况,分别如下:


·如果 X=1, 表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多;

·如果 X=2, 表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果;

·如果 X=3, 表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果;

·如果 X=4, 表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果;

·如果 X=5, 表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果;


题目想要让每个小朋友都满足且使用最少的糖果数量,如果无解则输出-1。

我们将小朋友当做点,小朋友之间限制条件当做边

对于X=1、X=3、X=5时,我们不难知道,想要使用最少的糖果数量,那么最好的方式就是两个人糖果数量一样。

对于X=2,那么A=B-1最优

对于X=4,那么B=A-1最优


所以说了做一下一本通中的1352:【例4-13】奖金一题对于你们会有一点启发。

现在我们又可以想到,让每个小朋友都满足且使用最少的糖果数量,那么只需要通过别人对它没有限制的小朋友,依次更新每个小朋友要有的糖即可。


那么怎么去依次找小朋友并更新每个小朋友要的糖呢?

拓扑排序!


怎么建图?按什么条件去建?

比如A要少于B,则建一条A->B的边,这样拓扑排序下来,可以保证处理每个点的糖果数量时,可以从小处理到大,符合上面的要求。


那X=1、3、5和X=2、4时有什么区别呢?

我们就需要额外记录边权,X=1、3、5边权为0,X=2、4边权为1。


血的教训:X=1时需要建双向边!即A->B且B->A。

为什么?

因为A必须与B一样多,那么我们就可以使用Tarjan缩点将两个点合并为1个点,且将环变为强连通分量。正好满足了拓扑排序不能有环的性质。


怎么判断无解情况?

在建新图用来拓扑排序时,看两个点是否在同一个环内,在且边权为1则无解。


最重要的一个问题:怎么更新糖果数量?

我们将每个人的糖果当做dp[i],在删i相连的点的入度时,更新i的next的dp值

则有动态转移方程:

dp[当前更新的点] = max(dp[当前更新的点],dp[当前删除的点] + nnei[当前删除的点][第j个邻居].边权)


这些知识本人Blog中都会涉及的233

那么思路就很明显了:

建图(X=1:建边权为0的双向边,X=2、X=4建边权为1的单向边,X=3、X=5时建边权为0的单向边)——>Tarjan——>边建新图边判断无解——>用新图拓扑排序,并更新每个点所需要的糖果数量

代码

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 100000 + 10;

int n,k;
int scc[MAXN],sum,low[MAXN],dfn[MAXN],cnt,tot[MAXN];
//以上是强连通图的必备变量,唯一tot是记录每个强连通分量里面有多少个点 
int dp[MAXN];
//这个用于DP记录答案 
int in[MAXN];
//这个记录入度,用于Topo 
long long ans;
//最终答案 
struct Node{
	int next;//记录每个点的下一个点 
	int v;//记录边权 
};
vector<Node>nei[MAXN];//旧图 
vector<Node>nnei[MAXN];//新图 
bool Stack[MAXN];//用于Tarjan 
stack<int> s;//用于Tarjan

inline int read(){//快速读入 
    int f = 1, x = 0;
    char c = getchar();

    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }

    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }

    return f * x;
}


void Tarjan(int u){//Tarjan模板 
	low[u] = dfn[u] = ++cnt;
	Stack[u] = true;
	s.push(u);
	
	int len = nei[u].size();
	for(int i = 0;i < len; i++){
		int next = nei[u][i].next;
		
		if(dfn[next] == 0){
			Tarjan(next);
			low[u] = min(low[u],low[next]);
		}else
		if(Stack[next]){
			low[u] = min(low[u],dfn[next]);
		}
	}
	
	if(dfn[u] == low[u]){
		sum++;
		scc[u] = sum;
		Stack[u] = false;
		tot[sum]++;
		
		while(s.top() != u){
			Stack[s.top()] = false;
			scc[s.top()] = sum;
			s.pop();
			tot[sum]++;
		}
		s.pop();
	}
}

int main()
{
	n = read(),k = read();//读入 
	
	for(int i = 1;i <= k; i++){
		int z = read(),x = read(),y = read();
		switch(z){//使用开关函数 
			case 1:{//一号情况 
				nei[x].push_back((Node){y,0});
				nei[y].push_back((Node){x,0});
				//这里一定要建两条边! 
				break;
			}
			case 2:{//二号情况 
				nei[x].push_back((Node){y,1});
				break;
			}
			case 3:{//三号情况 
				nei[y].push_back((Node){x,0});
				break;
			}
			case 4:{//四号情况 
				nei[y].push_back((Node){x,1});
				break;
			}
			case 5:{//五号情况 
				nei[x].push_back((Node){y,0});
				break;
			}
		}
	}

	for(int i = 1;i <= n; i++){
    	if(dfn[i] == 0)Tarjan(i);//Tajan 
    }

    for(int i = 1;i <= n; i++){//建新图 
    	int len = nei[i].size();
		
		for(int j = 0;j < len; j++){
			int next = nei[i][j].next;
			int xx = scc[i];
        	int yy = scc[next];
        
        	if(xx == yy && nei[i][j].v == 1){//判断无解  
        		cout<<-1<<"\n";
        		return 0;
			}
        
        	if(xx != yy){//建新图 
        	    nnei[xx].push_back((Node){yy,nei[i][j].v});
        	    in[yy]++;
        	}
		}
    }
    
    queue<int>q;//Topo模板 
    
    for(int i = 1;i <= sum; i++){//将入读为0的压入队列 
    	if(!in[i]){
    		q.push(i);
    		dp[i] = 1;//初始化 
		}
	}
	
	while(!q.empty()){//拓扑模板 
		int cur = q.front();
		q.pop();
		int len = nnei[cur].size();
		
		for(int i = 0;i < len; i++){
			int next = nnei[cur][i].next;
			in[next]--;
			dp[next] = max(dp[next],dp[cur] + nnei[cur][i].v);//Dp方程 
			
			if(!in[next])q.push(next);
		}
	}
	
	for(int i = 1;i <= sum; i++){//累加答案 
		ans += (long long) dp[i] * tot[i];
	}
	cout<<ans;//输出 
	return 0;
}


代码不做非常详细的注释,自己对照前面详细的讲解看看吧233

感谢我的教练 @LiveDream 帮我DeBug!

posted @ 2020-01-15 21:05  御·Dragon  阅读(371)  评论(2编辑  收藏  举报



Contact with me