• 病毒Virus


    病毒Virus

    一本通P1396 病毒Virus

    题目简述

    给定(k)个被病毒感染了的字符串,知道这(k)个字符串原本是按字典序从小到大排列,最后给出一个待复原的字符串(s),要求根据上面的(k)个被感染的字符串复原(s)

    • 数据范围

    (k≤50000)


    所需算法

    拓扑排序&模拟

    前置知识

    如果想了解拓扑排序欢迎来踩我的另一篇博客呀qvq


    解题思路

    感觉有点没读懂或者没思路?

    那我们通过思考几个问题来找出思路:

    • (k)个被感染的字符串能为我们带来什么?

    因为这(k)个字符串原本是按字典序排列的,所以是有序的;被病毒感染只会改变原来字符串的样子,但并不会影响原来的排序!

    所以我们可以根据这(k)个字符串得出原字符(x)和感染后的字符(y)之间的对应关系!(可以理解为一一对应的映射关系)

    • 怎么找到这种映射关系?

    这里我想到的做法是:双层循环+逐位查询+拓扑排序

    说白点就是先模拟字典序排序的过程:如果两个字符串的第(i)位相等,则判断第(i+1)位,直到其中一个字符串完结

    在这个过程中我们找到一个两个字符不同的位置,就进行连边和入度累加的操作,之后使用拓扑排序便能得到映射关系

    • 怎么想到的拓扑排序?

    为了方便理解,我们插入理解一下样例:

    1. 先通过模拟字典序排序过程,我们可以得到如下的字符大小序列:(c<e<d<a<b)(e<a)

    2. 那么映射关系即:(c->a,e->b,d->c,a->d,b->e)(注意箭头前的是被感染后的字符,箭头后的是原始字符)

    找到了一个感觉没有?qwq

    (c<e<d<a<b)这个即是拓扑序列!然后根据这个拓扑序列我们就能够找到映射关系!


    代码Code

    思路就是如上这么多了,理解后应该就能A掉这道题了,只是敲代码需要细心一点

    现在先放出每个关键步骤的代码段(有注释),最后会放上完整的AC代码(无注释)

    • 模拟字典序排序
    for(register int i=1;i<=n;i++) {  //循环1-n个字符串,这里的n相当于题目中的k(个人习惯啦qwq)
        for(register int j=i+1;j<=n;j++) {  //循环在i之后的字符串 
            int l=0;  //l枚举字符串的位置 
            while(l<word[i].length()&&l<word[j].length()) {  //匹配长度小于两个字符串的最小长度 
                  flag[word[i][l]-'0']=1;  //标记一下出现过,后面会用 
                  if(word[i][l]==word[j][l]) l++;  //相同则不管 
                  else {
                       flag[word[j][l]-'0']=1;
                       in[word[j][l]-'0']++;  //不同说明i串位置字符的字典序先于j串位置字符的字典序 
                       add(word[i][l]-'0',word[j][l]-'0');  //连边+入度累加 
                       break;  //找到一位后面的就不用比较了 
                  }
             }
         }
    }
    
    • 拓扑排序
    for(register char i='a';i<='z';i++) {  //如果这个字符出现过且入度为0(即字典序最先),就入队 
        if(flag[i-'0']==1&&in[i-'0']==0) q.push(i-'0'); 
    }
    while(!q.empty()) {
          int x=q.front();
          q.pop();
          if(ans[x]!=0) {  //这里是判断一个感染字符对应多个原始字符的错误情况 
             printf("0");
             return 0;
          }
          ans[x]=sum;  //注意ans是一个整型map
          sum++;
          for(register int i=head[x];i;i=e[i].net) {  //遍历所有字典序在x之后的字符 
              int v=e[i].to;
              in[v]--;
              if(in[v]==0) q.push(v);
           }
    }
    
    • 完整代码
    #include <bits/stdc++.h>
    using namespace std;
    queue<int> q;
    map<int,int> ans;
    string word[50002];
    int n,tot,sum=97,in[201],head[201],flag[201];
    
    struct node {
    	int to,net;
    } e[50001];
    
    inline void add(int u,int v) {
    	e[++tot].to=v;
    	e[tot].net=head[u];
    	head[u]=tot;
    }
    
    int main() {
    	scanf("%d",&n); 
    	for(register int i=1;i<=n;i++) {
    		cin>>word[i];
    	}
    	for(register int i=1;i<=n;i++) {  
    		for(register int j=i+1;j<=n;j++) { 
    			int l=0; 
    			while(l<word[i].length()&&l<word[j].length()) { 
    				flag[word[i][l]-'0']=1; 
    				if(word[i][l]==word[j][l]) l++; 
    				else {
    					flag[word[j][l]-'0']=1;
    					in[word[j][l]-'0']++; 
    					add(word[i][l]-'0',word[j][l]-'0'); 
    					break; 
    				}
    			}
    		}
    	}
    	cin>>word[n+1];
    	for(register char i='a';i<='z';i++) { 
    		if(flag[i-'0']==1&&in[i-'0']==0) q.push(i-'0'); 
    	}
    	while(!q.empty()) {
    		int x=q.front();
    		q.pop();
    		if(ans[x]!=0) { 
    			printf("0");
    			return 0;
    		}
    		ans[x]=sum;
    		sum++;
    		for(register int i=head[x];i;i=e[i].net) { 
    			int v=e[i].to;
    			in[v]--;
    			if(in[v]==0) q.push(v);
    		}
    	}
    	for(register int i=0;i<word[n+1].length();i++) {  //如果对应不完,那也是一种错误情况 
    		if(ans[word[n+1][i]-'0']==0) {
    			printf("0");
    			return 0;
    		}
    	}
    	for(register int i=0;i<word[n+1].length();i++) {
    		cout<<char(ans[word[n+1][i]-'0']);
    	}
    	return 0;
    } 
    

  • 相关阅读:
    字符串转换成整数
    回文字符串判断
    字符串包含
    翻转单词顺序VS左旋转字符串
    穷举子集合
    求S=a+aa+aaa+aaaa+aa...a的值
    数组元素去重
    找最长等差数列的长度
    Git pull and push
    Google 开发console查找元素或方法
  • 原文地址:https://www.cnblogs.com/Eleven-Qian-Shan/p/13218119.html
Copyright © 2020-2023  润新知