相当有难度的一道题。可以用分阶段的贪心算法做,但需要大致证明局部最优解可以收敛到全局最优。
假设有服务器ABCD,访问序列去除掉无关项、只剩下CBDA...CD
假设A是一步之内可以到达的最远点。由于要从A换成别的服务器,第二步可以到达除A外单步之内最远的字母。
又由于A是从起点出发、第一步可到达的最远点,则其它字母必然在A之前出现至少一次(否则假设不成立)。
假设第一步取的不是A而是其他字母,则必然有:第二步到达的位置<=第一步取A在第二步可到达的位置。

【详细论证】任取非A字母B,也可以换成CD
第一步从起点到B,则第二步从B到最远的非B字母,也就是字母A、或者非A非B的字母X
//注意,我们假设了A是第一步可到达的最远位置,故第一个B在第一个A之前
如果第二步选A,显然刚到达第一个A,肯定要比第一步就选A要近
如果这个字母在第一个B和第一个A之间出现,那么甚至都摸不到第一个A
如果这个字母在第一个B和第一个A之间不出现,那么第二步到达的距离一样远

因此,在本题中使用贪心算法可以从局部最优收敛到全局最优。相当于只是剪了个枝。

【分阶段贪心】自己随便起了个名字,就是每步都选能走最远距离的字母(代理服务器),直到抵达目标


【需要注意的坑】vector的pop_back是删除最后一个,不是第一个,删第一个要用erase

#include<iostream>
#include<vector>
#include<map>
#include<string> 
#include<algorithm>
using namespace std;



void Question7_1(){//习题7.1  清华大学  代理服务器 
    int n,m;
    while(cin>>n){
        string ip;
        //初始化代理服务器
        map<string,vector<int>> agent;
        vector<int> empty;
        empty.push_back(5000);//5000表示在访问序列中未出现、可以直接干到底
        for(int i=0;i<n;i++){
            cin>>ip;
            agent[ip] = empty;
        }
        cin>>m;
        //初始化访问列表
        vector<string> visit;
        for(int j=0;j<m;j++){
            cin>>ip;
            visit.push_back(ip);
            //要访问的ip不是代理服务器,可以不用管
            map<string,vector<int>>::iterator finder = agent.find(ip);
            if(finder==agent.end()) continue;
            //访问的ip是代理服务器时,记录在访问列表中出现的位置
            //也就相当于这个代理服务器每次可到达的最远距离
            //注意补正,如果不能直接干到底就把那个5000给去掉
            if(finder->second[0]==5000) finder->second.pop_back();
            finder->second.push_back(j);
        }
        //分阶段贪心策略
        int step = 0;
        string ban;
        int maxPos = 0;
        string maxKey;
        //只有一台服务器且不能直通末尾,无法完成目标 
        if(n==1&&agent.begin()->second[0]<m){
            step = -1;
        }
        while(step!=-1 && maxPos<m){
            //搜寻本轮能到达的最远距离
            map<string,vector<int>>::iterator it = agent.begin();
            while(it!=agent.end()){
                //上一轮要换代理服务器,因此本轮ban掉这个
                if(it->first!=ban && it->second[0]>maxPos){//寻找最远
                    maxKey = it->first;
                    maxPos = it->second[0];
                }
                it++;
            }
            ban = maxKey;
            //到达末尾,跳出
            if(maxPos>=m) break;
            //否则,清除掉已经经过的位置
            it = agent.begin();
            while(it!=agent.end()){
                while(it->second[0]<=maxPos){
                    it->second.erase(it->second.begin());//pop_back是删除最后一个,第一个要用erase
                    if(it->second.size()==0){
                        //不应该erase掉,应该当作可以直达末尾、填充一个5000
                        it->second.push_back(5000);
                        break;
                    }
                }
                it++;
            }
            //所经过的步数+1
            step++;
        }
        
        //最终输出
        cout<<step<<endl;
    }
}

int main(){
	Question7_1();
	 
	return 0;
}