• Evanyou Blog 彩带


    洛谷题面

    看到很多题解并没有讲清楚这道题为什么可以用某些方法,套个板子就没了。蒟蒻就发一篇题解装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<<"
    ";
            		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!

  • 相关阅读:
    Unity 深入解析合批(Batching)原理及失败原因
    ILRuntime作者林若峰分享:优化 Dots URP 性能优化 (干货满满)
    unity Mesh绘制网格线
    Shder基础知识
    UnityEditor工具链开发的常用小技巧与示例
    磁盘文件分析清理工具
    波函数坍塌算法(Wave Collapse Function)实现随机地图
    视觉小说 文字游戏引擎 ink 可配合Unity
    c# 字典键值排序
    unity文件夹复制
  • 原文地址:https://www.cnblogs.com/CJYBlog/p/12198786.html
Copyright © 2020-2023  润新知