• 题解 CF1344C Quantifier Question


    题目链接

    首先,因为小于的关系是可传递的,所以对于一组限制((j_i,k_i)),我们考虑连一条有向边(j_i o k_i)。这样能建出一个图。显然。如果图上有环,肯定无解,答案是(-1)。而只要图上没有环,那么一定有解,因为至少全填(exist)的时候是成立的。所以以下只考虑一个DAG的情况。

    接下来,要让(forall)尽可能多。这就需要你细细地品这个关系了。

    你会发现,每个(Q_i),不是平等的。例如,如果是(x_1<x_2,x_3<x_2),答案就是(Q_1=forall,Q_2=exist,Q_3=exist)。但如果是(x_1<x_3,x_2<x_3),答案就可以变成(Q_1=forall,Q_2=forall,Q_3=exist)。(备注:这个例子其实就是样例3)。你可以看出,在两个一模一样的结构里,仅仅是编号不同((2)(3)交换了),就可以改变(forall)的数量,所以从前往后每个编号是有区别的、不平等的。

    进而发现,对于(i<j),只要(i)能到达(j)(j)能到达(i),那么(Q_j)一定等于(exist),不论(Q_i)等于什么。那既然不论我填(forall)还是(exist),影响是一样的,我当然尽可能多填(forall)。也就是用这样一种贪心的思想:能填(forall)就绝不填(exist)

    于是问题转化为,按编号从小到大,依次标记每个节点。对当前节点(i),如果所有【(i)能到达的】和【能到达(i)的】节点都未被标记,则(Q_i=forall),否则(Q_i=exist)

    当然,你会发现,对所有【(i)能到达的】和【能到达(i)的】节点进行查询,非常困难。转化一下,变成修改操作,也同样困难!如果你这么想这个问题,那你就是用了“数据结构”的思维,总想在过程进行中,大力维护一些什么。但其实,还有另一种思维,也就是“DP”的思维。不在过程中维护,而是预处理出来:用递推的方式预处理出来,不断从已知的,推出未知的。

    具体来说,可以现在DAG上正着跑一遍拓扑排序,就能递推出【能到达每个点的】最小编号。再在反图上跑一遍拓扑排序,就能递推出【每个点能到达的】最小编号。

    时间复杂度(O(n+m))

    参考代码:

    //problem:
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=2e5;
    int n,m,ind[MAXN+5],ind_rev[MAXN+5];
    int dp[MAXN+5],dp_rev[MAXN+5];
    vector<int>G[MAXN+5],RG[MAXN+5];
    
    char ans[MAXN+5];
    int maxnum;
    int main() {
    	cin>>n>>m;
    	for(int i=1;i<=m;++i){
    		int u,v; cin>>u>>v;
    		G[u].pb(v); RG[v].pb(u);
    		ind[v]++; ind_rev[u]++;
    	}
    	queue<int>q;
    	for(int i=1;i<=n;++i){
    		dp[i]=i;
    		if(!ind[i]){
    			q.push(i);
    		}
    	}
    	while(!q.empty()){
    		int u=q.front(); q.pop();
    		for(int i=0;i<SZ(G[u]);++i){
    			int v=G[u][i];
    			ind[v]--;
    			ckmin(dp[v],dp[u]);
    			if(!ind[v]){
    				q.push(v);
    			}
    		}
    	}
    	for(int i=1;i<=n;++i)
    		if(ind[i]){
    			cout<<"-1"<<endl;
    			return 0;
    		}
    	for(int i=1;i<=n;++i){
    		dp_rev[i]=i;
    		if(!ind_rev[i]){
    			q.push(i);
    		}
    	}
    	while(!q.empty()){
    		int u=q.front(); q.pop();
    		for(int i=0;i<SZ(RG[u]);++i){
    			int v=RG[u][i];
    			ind_rev[v]--;
    			ckmin(dp_rev[v],dp_rev[u]);
    			if(!ind_rev[v]){
    				q.push(v);
    			}
    		}
    	}
    	for(int i=1;i<=n;++i){
    		if(dp[i]<i || dp_rev[i]<i){
    			ans[i]='E';
    		}
    		else{
    			ans[i]='A';
    			maxnum++;
    		}
    	}
    	cout<<maxnum<<endl;
    	for(int i=1;i<=n;++i)
    		cout<<ans[i];
    	cout<<endl;
    	return 0;
    }
    
  • 相关阅读:
    Java并发Condition接口
    Java并发ReadWriteLock接口
    简述mapreduce的四个对象
    hadoop IO操作
    java 多线程笔记
    java 四种方式读取文件
    InputStream、InputStreamReader、BufferedReader
    java 自动拆箱与装箱(基本数据类型与引用类型)
    java 之equals与"=="的区别
    vmware搭建hadoop集群完整过程笔记
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13388056.html
Copyright © 2020-2023  润新知