夜闯寡妇村,蒙牛算水题——DP、贪心+组合向

Sort with Swap(0,i)

在这里插入图片描述

Reading

  • if 0 not in pos 0, swicth ,degree–;
  • else if i not in pos i, j not in pos j
  • swap(0,i),swap(0,j)
  • 自动->搜索;手动->贪心
  • 不要求最小的话可以DP/递归
  • 不在指定位置上的数是一条环链,最小次数=环链数目?
  • 所有的链分为含0的环链,不含0的环链;
  • 含0的环链次数=长度-1,不含0的环链次数<=长度+1(可证明是等号?)
  • 因此总数目=含0的环链次数+不含0的环链次数=总长度+不含0的环链数-含0的环链数-正确的位置数
  • 用什么数据结构判断环链数目?——visit数组
#include<iostream>
#include<vector>
using namespace std;

int main(){
    int N; cin>>N;
    vector<int> visit(N);
    vector<int> array(N);
    int right_pos=0;
    for(int i=0;i<N;i++){
        cin>>array[i];
        if(array[i]==i&&i!=0){
            visit[i]=1;
            right_pos++;
        }
    }
    int circle_0=0;
    int circle_1=0;
    for(int i=0;i<N;i++){
        if(visit[i]==0){
            int head=i;
            int sign_circle_0=0;
            while(visit[head]==0){
                visit[head]=1;
                if(array[head]==0)
                    sign_circle_0=1;
                head=array[head];
            }
            if(sign_circle_0==1)
                circle_0++;
            else circle_1++;
        }
    }
    // cout<<"circle_0="<<circle_0<<endl;
    // cout<<"circle_1="<<circle_1<<endl;
    cout<<N+circle_1-circle_0-right_pos;
}

猜的

在这里插入图片描述

Classic Hash-Quadratic-Probing

  • 更新:读错题目,浪费了0.5h,好难过
    在这里插入图片描述

reading

  • Subjection: reappearance of the classic alg
  • Hash dmd1: the table size, prime, need to be larger than MSize user gives if MSize si not prime
  • ~~task1: judge to find minimal primer; ~~
  • task2: perform quadratic probing(with positive increments only) to output the location by visit array
  • task3: how to judge there is no location for hash(key)?
  • hash(i)=i+k^2%Tsize,k=0~TSize-1,若均不行,return false;
#include<cmath>
#include<iostream>
#include<vector>
using namespace std;

bool judge_primer(int n){
    for(int i=2;i<=sqrt(n);i++)
        if(n%i==0)
            return false;
    return true;
}

int minimal_primer(int m){
    if(m==1)
        return 2;
    int ans=m;
    while(!judge_primer(ans))
        ans++;
    return ans;
}

int main()
{
    int MSize,N;
    cin>>MSize>>N;
    int TSize=minimal_primer(MSize);
    vector<int> visit(TSize,0);
    int sign=0;
    for(int i=0;i<N;i++){
        int key;
        cin>>key;
        int hash_key;
        int plausible=0;
        if(sign==0)    sign=1;
        else    cout<<" ";
        for(int i=0;i<TSize;i++){
            hash_key=(key+i*i)%TSize;
            if(visit[hash_key]==0){
                plausible=1;
                visit[hash_key]=1;
                break;
            }
        }
        if(plausible==1)
            cout<<hash_key;
        else 
            cout<<"-";
    }
}

Exp++

  1. judge_primer(1)==true,但1不是primer,minimal_primer(1)==2
  2. Quadratic_probing:Pos(key)=(Hash(key)+i2)%TSize,i ∈ \in [0,TSize-1],由模的性质,其实i ∈ \in [0,TSize/2]
  • 打卡
    在这里插入图片描述

Greedy、DP

从后往前的DP(with greedy)

在这里插入图片描述

reading

  1. 用d[i][v]=d[i+1][v] or d[i+1][v-v[i]]可以找到各个点的最小路径,
  • 但要找的是最小的,可以在这之后遍历;
  • d记录[i][j]的可行性,choice[i][j]记录[i][j]时的选择,
  • 之后注意到d[i][j]时可优先选择当下的v[i],即贪心,符合题意&
  • 之前还以为按d[i][j]和choice[i][j]找路径会挺麻烦的
  1. 或者用递归,时间一样,空间复杂度还不如这个呢——无敌的DP@
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//或者用递归
// bool result(vector<int> v,int request){
//     if(v)

int main(){
    int N,M;    cin>>N>>M;
    vector<int>    v(N);
    for(int i=0;i<N;i++)
        cin>>v[i];
    sort(v.begin(),v.end());
    vector<vector<bool>>    d(N,vector<bool>(M,false));    //d[i][j]==false means it's impossible
    vector<vector<bool>>    choice(N,vector<bool>(M,false));    //choice[i][j]==false means it's possible    
    for(int i=N-1;i>-1;i--){
        for(int j=0;j<=M;j++){
            if(i==N-1){
                if(j==v[i]){
                    d[i][j]=true;    choice[i][j]=true;    //不要把=写成==
                }
                else if(j==0){
                    d[i][j]=true;    choice[i][j]=false;
                }
                // cout<<"d["<<i<<"]["<<j<<"]="<<d[i][j]<<endl;
            }
            else{    //here j may be smaller than v[i]
                //优先选择v[i],即较小的v
                if(j>=v[i]&&d[i+1][j-v[i]]==true){
                    d[i][j]=true;
                    choice[i][j]=true;
                }
                else if(d[i+1][j]==true){
                    d[i][j]=true;
                    choice[i][j]=false;
                }
                else
                    d[i][j]=false;
            }
        }
    }
    vector<int> solution;
    int j=M;
    for(int i=0;i<N;i++){
        if(d[i][j]==false){
            cout<<"No Solution"<<endl;
            return 0;
        }
        else{
            if(choice[i][j]==true){
                solution.push_back(v[i]);
                j=j-v[i];
            }
        }
    }
    int sign=0;
    for(auto i:solution){
        if(sign==0)
            sign=1;
        else
            cout<<" ";
        cout<<i;
    }
    return 0;
}

打卡!
在这里插入图片描述

7-28搜索(DFS)+剪枝

在这里插入图片描述

#include <cstdio>
using namespace std;
int h,n,t0,ti[10],di[10],vis[10];
char name[10][21];
int temp[10],ans[10],max_num,min_time;
void dfs(int num,int atime,int time) {//完成题目数  完成题目的总时间(加上罚时)  当前花费总时间
	if(time > h) return;
	if(num > max_num || (num == max_num && atime < min_time)) {
		max_num = num;
		min_time = atime;
		for(int i = 0; i < num; i ++) {
			ans[i] = temp[i];
		}
	}
	if(num >= n) return;
	for(int i = 0; i < n; i ++) {
		if(!vis[i]) {
			vis[i] = 1;
			temp[num] = i;
			dfs(num + 1,atime + time + ti[i] + (time + ti[i] - 1) / 60 * (di[i] + 20),time + ti[i] + (time + ti[i] - 1) / 60 * di[i]);
			vis[i] = 0;
		}
	}
}
int main() {
	while(scanf("%d",&h) && h >= 0) {
		scanf("%d%d",&n,&t0);
		max_num = 0;
		h = h * 60;
		for(int i = 0; i < n; i ++)
			scanf("%s%d%d",name[i],&ti[i],&di[i]);
		dfs(0,0,t0);
		printf("Total Time = %d\n",min_time);
		for(int i = 0; i < max_num; i ++) {
			printf("%s\n",name[ans[i]]);
		}
	}
	return 0;
}

Exp++

  • 用数组不用类表示,足够且写代码更快,怎么写都行
  • scanf(“%d”,&h)
  • 题目要求做完一题就做到底,很经典的DFS

752 BFS取最短路径

在这里插入图片描述

//这个是BFS遍历取最小值
class Solution {
public:
//This isn't sole DFS,you have to compare the time,
//it's backtrack,when you 
vector<pair<int,int>> act={
    {0,+1},{0,-1},
    {1,+1},{1,-1},
    {2,+1},{2,-1},
    {3,+1},{3,-1}
};
    int openLock(vector<string>& deadends, string target) {
        vector<vector<int>> deadint(deadends.size(),vector<int>{}); //存储死亡数字
        vector<int> traint(4);      //存储胜利数字
        map<vector<int>,int> rec;        //记录结点是否遍历  
        int time=0;
//将死亡、胜利字符转为死亡、胜利数字
        for(int i=0;i<deadends.size();i++){
            for(int j=0;j<4;j++){
                deadint[i].push_back(deadends[i][j]-'0');                
            }
        }
        for(int i=0;i<4;i++)
            traint[i]=(target[i]-'0');
//初始化record
        vector<int> ret(4);
        for(int i=0;i<10000;i++){
            ret[0]=i/1000;
            ret[1]=(i/100)%10;
            ret[2]=(i/10)%10;
            ret[3]=i%10;
            rec[ret]=0;  //初始化为0
        }
        if(traint==vector<int>(4,0))    return time;
        if(find(deadint.begin(),deadint.end(),vector<int>{0,0,0,0})!=deadint.end())    return -1; 
//target[i]+1,-1
        queue<pair<vector<int>,int>> q;   //s为DFS遍历栈
        pair<vector<int>,int> tra;        //tra用来存储队列首元素
        pair<vector<int>,int> b;  //temporary
        q.push({vector<int>{0,0,0,0},0});
        rec[vector<int>(4,0)]=1;

        while(!q.empty()){
            tra=q.front();

            for(auto i:act){
                b=tra;
                b.first[i.first]=(b.first[i.first]+i.second+10)%10;
                b.second++;         //步数++
                if(rec[b.first]==0&&find(deadint.begin(),deadint.end(),b.first)==deadint.end()){
                if(b.first==traint)   return b.second;
                    q.push(b);
                    rec[b.first]=1;
                }
            }
            q.pop();
        }
        return -1;
    }
};

好难的模拟

  • 被杀穿了,这种按时间的模拟,有多个时间序列可作选择:时钟,空闲的生产者队列,消费者队列
  • 这个问题选择消费者队列更为合适
    在这里插入图片描述

reading

  • input:arriving time,playing time,vip card
  • table number,vip tables,vip table number
  • output:arriving time,serving time,waiting time
  • 每次来的是一对不是一个
  • 08:00:00 20 0
  • 08:01:30 15 1
  • 08:02:00 30 0
  • 08:10:00 30 0
  • 08:12:00 10 1
  • 这个是生产者-消费者
  • 用统一的时钟
  • 用队列,在一个玩家进入一个球桌就能知道完成时间,
  • 用队列记录玩家还是桌子?记录玩家,处理桌子空闲时选取vip用户较为麻烦
  • 记录桌子,需要实时对桌子的下一次空闲时刻进行排序,空闲时刻相同,按桌子的序号排序
  • 大佬的回答
  • 有多名乘客、多个vip桌的情形,将vip乘客与桌匹配,其他还原,为下一次迭代
#include <iostream>

using namespace std;

#define maxp 10000 //客人总数不超过10000
#define maxt 100 //桌子总数不超过100

int n, k, m; //共n个客人,k张桌子,其中m张VIP桌子

struct Player //单个客人
{
    int arrtime; //到达时间
    int statime; //开始时间
    int playtime; //比赛时间
    int vipp; //是否为VIP客人
};
struct Player player[maxp]; //客人总数

struct Table
{
    int time; //被占用时间
    int vipt; //是否为VIP桌
};
struct Table table[maxt];

int server[maxt]; //每张桌子服务了多少客人

int start = 8 * 3600; //开门时间,用秒计算,后面会更新为到达时间
int close = 21 * 3600; //关门时间

int findemptytable(int vipp) //找空桌子
{
    int emptytable = -1; //如果没有空桌子,显示为-1
    for(int i = 0; i < k; i++)
    {
        if(table[i].time == 0) //如果有空桌子
        {
            //如果来的是VIP客人,但该空桌子不是VIP桌
            if(vipp == 1 && table[i].vipt != 1)
            {
                continue;
            }
            emptytable = i;
            break;
        }
    }
    return emptytable;
}

int findvip(int waitfirst, int nowtime) //查看等待队列里有没有VIP客人
//waitfirst等待队列里的第一个人
{
    int v = -1;
    for(int j = waitfirst; j < n && player[j].arrtime <= nowtime; j++)
    {
        if(player[j].vipp)
        {
            v = j;
            break;
        }
    }
    return v;
}

int NoOvertime(int t) //超过两小时按两小时计算
{
    if(t > 7200)
    {
        return 7200;
    }
    else
    {
        return t;
    }
}

//把第5题的堆排序搬过来,改成了结构体数组
void subheap(struct Player a[], int i, int n) //比较父结点和子结点,在子树中生成最大堆
{
    int parent = i;
    int child = 2 * i + 1; //堆从0开始,父结点为i,则左孩子为2i+1
    while(child < n)
    {
        if(child + 1 < n && a[child].arrtime < a[child + 1].arrtime) //如果这个父结点有右孩子,并且右孩子比左孩子大
        {
            child += 1; //child的值是左右孩子中较大孩子的位置
        }
        if(a[child].arrtime > a[parent].arrtime) //如果孩子结点比父结点大,交换二者
        {
            struct Player tem = a[child];
            a[child] = a[parent];
            a[parent] = tem;

            parent = child;
        }

        child = child * 2 + 1; //由上至下做比较
    }
}

void trip(struct Player a[], int num) //第一趟堆排序,将最大值放到根结点的位置 //num为数组元素个数
{
    for(int i = num / 2 - 1; i >= 0; i--) // n/2+1是倒数第二排最后一个位置,也就是右下角那棵子树的根结点
    {
        subheap(a, i, num);
    }
}

void heapsort(struct Player a[], int num) //堆排序
{
    trip(a, num);

    for(int i = num - 1; i > 0; i--)
    {
        struct Player f = a[0];
        a[0] = a[i];
        a[i] = f; //交换根结点和最后一个元素的位置

        subheap(a, 0, i);
    }

}

int main()
{
    cin >> n; //共n个客人

    int t1, t2, t3; //时分秒
    for(int i = 0; i < n; i++)
    {
        //用scanf比较方便,不用专门处理字符串
        //用scanf记得变量前加美元符号!
        scanf("%d:%d:%d %d %d", &t1, &t2, &t3, &player[i].playtime, &player[i].vipp);
        player[i].arrtime = t1 * 3600 + t2 * 60 + t3;
        player[i].statime = close + 1;  //no
        player[i].playtime = NoOvertime(player[i].playtime * 60);
    }

    cin >> k >> m; //k张桌子,其中m张vip桌
    for(int i = 0; i < k; i++) //初始化为0
    {
        table[i].time = 0;
        table[i].vipt = 0;
        server[i] = 0;
    }
    for(int i = 0; i < m; i++) //记录哪几张桌子是VIP桌
    {
        int viptnum;
        cin >> viptnum;
        table[viptnum - 1].vipt = 1; //结构体数组里是从0开始的
    }

    heapsort(player, n); //堆排序

    for(int i = 0; i < n; i++)
    {
        if(player[i].arrtime > start) //既筛除了八点前的客人,后面又更新为当前时间
        {
            for(int j = 0; j < k; j++)
            {
                table[j].time = table[j].time - (player[i].arrtime - start); //所有table经过arrtime-nowtime时长
                if(table[j].time < 0) //不需要等待的客人
                {
                    table[j].time = 0;
                }
            }
            start = player[i].arrtime; //如果当前时刻该顾客还未到,时间调到顾客到达时间
        }

        int fastest = 0; //最快结束的桌子
        for(int j = 1; j < k; j++)
        {
            if(table[j].time < table[fastest].time)
            {
                fastest = j;
            }
        }
        start += table[fastest].time; //从桌子最快结束时间开始玩

        if(start >= close) //开始玩的时间超过了关门时间
        {
            n = i; //后面的客人都超时,输出时只输出不超时的客人
            break;
        }

        int t = table[fastest].time; //直接写在t位置,测试点1678答案错误
        for(int j = 0; j < k; j++)
        {
            table[j].time -= t; //所有桌子被占用时间根据当前时间的变化调整
        }

        //空桌子是vip桌时,查看等待队列里有没有vip客人
        int viptable = findemptytable(1);
        int vipplayer = findvip(i, start);

        int emptytable;
        if(viptable != -1 && vipplayer != -1)
        {
            if(i != vipplayer)
            {
                for(int j = vipplayer; j > i; j--) //通过依次与前面的客人交换位置达到移动到最前面的目的
                {
                    struct Player tem = player[j]; 
                    player[j] = player[j - 1];
                    player[j - 1] = tem;
                }
            }
            emptytable = viptable;
        }
        else
        {
            emptytable = findemptytable(0);
            if(emptytable == -1)
            {
                while(1);
            }
        }

        player[i].statime = start;
        table[emptytable].time = player[i].playtime;
        server[emptytable]++;
    }

    int wait;
    for(int i = 0; i < n; i++)
    {
        if(player[i].statime >= close)
        {
            while(1);
        }
        printf("%02d:%02d:%02d %02d:%02d:%02d", player[i].arrtime / 3600, (player[i].arrtime / 60) % 60, player[i].arrtime % 60, player[i].statime / 3600, (player[i].statime / 60) % 60, player[i].statime % 60);
        wait = player[i].statime - player[i].arrtime;
        wait = wait / 60 + (wait % 60) / 30;
        cout << " " << wait << endl;
    }

    for(int i = 0; i < k - 1; i++) //最后没有空格
    {
        cout << server[i] << " ";
    }
    cout << server[k - 1];

    return 0;
}

入门级贪心做了好久呜呜呜

在这里插入图片描述

reading

  • 由贪心性,每次到一个站,找Cmax*Davg以内最便宜的站t1,补充到此路的加油钱,t1到下一个站的距离>Cmax,输出false
  • 迭代假设:到Di处的油量ci是当下的最优选择(在[Di,Di+DavgCmax]处任何油费都比在箱里的贵),Di处油量<终点
  • 递归策略:若Di是[Di,Di+DavgCmax]的xk中pk最低者;在Di加油加到满或者加到终点,
  • 否则,若[Di,Di+DavgCi]中有Dj满足Pj<Pi,在Di处加的油不如在Dj出加的油,跨到下一个Dj
  • 若[Di+DavgCi,Di+DavgCmax]中有Dj未第一个满足Pj<Pi,但当前油量到不了Pj,加油加到(Dj-Di-DavgCi)/Davg
  • 若[Di,Di+DavgCmax]内无加油站,且未到终点,输出Di+DavgCmax

细节!

  • scanf 读浮点数莫名不行,scanf(“%f”,(double)x); 当然不行,单精度float %f,双精度double %lf
  • 变量定义尽量和算法一致,
//由贪心性,每次到一个站,找Cmax*Davg以内最便宜的站t1,补充到此路的加油钱,t1到下一个站的距离>Cmax,输出false
//迭代假设:到Di处的油量ci是当下的最优选择(在[Di,Di+DavgCmax]处任何油费都比在箱里的贵),Di处油量<终点
//递归策略:若Di是[Di,Di+DavgCmax]的xk中pk最低者;在Di加油加到满或者加到终点,
//否则,若[Di,Di+DavgCi]中有Dj满足Pj<Pi,在Di处加的油不如在Dj出加的油,跨到下一个Dj
//若[Di+DavgCi,Di+DavgCmax]中有Dj未第一个满足Pj<Pi,但当前油量到不了Pj,加油加到(Dj-Di-DavgCi)/Davg
//若[Di,Di+DavgCmax]内无加油站,且未到终点,输出Di+DavgCmax

//代码写的稀烂,但大致意思是对的,读题0.5h,写题1h,还没检查,我好菜呀
//scanf读浮点数莫名不行~,scanf("%f",(double)x);当然不行~~,单精度float %f,双精度double %lf

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

int Cmax,Dest,Davg;
double cost=0;
vector<pair<double,double>> station;

void traverse(double Di,double Ci,double Pi){
    // cout<<"visit "<<Di<<",cost="<<cost<<",Ci="<<Ci<<",Pi="<<Pi<<endl;
    int sign1=0;    //是否有Dj在[Di,Xi+DavgCmax]中;
    double last_Pj,last_Dj;
    for(int j=0;j<station.size();j++){
        double Dj=station[j].first,Pj=station[j].second;
        if(Dj>Di&&Dj<=Di+Davg*Cmax&&Dj<=Dest){
            if(Pj<Pi){
                if(Dj<=Di+Davg*Ci){    //有个可以到达的更便宜
                    traverse(Dj,Ci-(Dj-Di)/Davg,Pj);
                    return ;
                }
                else{    //没有Dj<Di+Davg*Ci的,这是第一个满足Pj<Pi的
                    cost+=Pi*(Dj-Di-Davg*Ci)/Davg;
                    traverse(Dj,0,Pj);
                    return ;
                }
            }
            else{
                sign1=1;
                last_Pj=Pj;
                last_Dj=Dj;
            }
        }
    }
    if(sign1==0){    //后面没有加油站了
        if(Di+Davg*Cmax<Dest){
            cout<<"The maximum travel distance = "<<fixed<<setprecision(2)<<(Di+Davg*Cmax)<<endl;
            return ;
        }
        //递归假设之前的不会超过终点;
        else{
            // cout<<"Dest="<<Dest<<",Di="<<Di<<",Ci="<<Ci<<endl;
            cost+=((Dest-Di)/Davg-Ci)*Pi;
            cout<<fixed<<setprecision(2)<<cost<<endl;
        }
    }
    else{
        if(Di+Davg*Cmax>=Dest){
            cost+=Pi*(Dest-Di-Ci*Davg)/Davg;
            cout<<fixed<<setprecision(2)<<cost<<endl;
            return ;
        }
        else{    
            cost+=Pi*(Cmax-Ci);    //装满
            traverse(last_Dj,Cmax-(last_Dj-Di)/Davg,last_Pj);
            return ;
        }
    }
}

int main(){
    string str;    
    int N;   scanf("%d %d %d %d",&Cmax,&Dest,&Davg,&N);
    for(int i=0;i<N;i++){
        double pi,di;
        cin>>pi>>di;    getline(cin,str);
        station.push_back(make_pair(di,pi));   
    }
    sort(station.begin(),station.end(),[](pair<double,double> s1,pair<int,int> s2){    return s1.first<s2.first;});
    if(station.front().first!=0){
        cout<<"The maximum travel distance = 0.00"<<endl;
        return 0;
    }
    double Ci=0,Di=0;    //当前油量,位置
    traverse(Di,Ci,station[0].second);
    return 0;
}

*打卡! 代码写的稀烂,但大致意思是对的,读题0.5h,写题1h,检查1h,我好菜呀
这还是有样例的,没样例的我可能都检查不出来
在这里插入图片描述

1003图的多功能

在这里插入图片描述

  • 传统的DFS,不能访问到指定节点的所有路径(访问过的节点不能再访问),只能保证访问到所有节点

  • 不能用DFS找从源到汇的所有最短路的所有节点,但Dijkstra好像可以

  • 从源到汇的所有路径数可以用Floyd,但只能输出所有路径,

  • 利用Dijk的dist和visit数组,可以通过DFS/BFS结合大标记获得最短路上的节点集合,得到max_team

  • 同时利用路径上节点的前驱数,可得总路径数
    在这里插入图片描述

  • 或者直接暴力,设bi的前驱为{a1,…,ak},则 p [ b i ] = ∑ i = 1 k p [ a i ] p[bi]=\sum_{i=1}^k p[a_i] p[bi]=i=1kp[ai],这俩是等价的,因为

  • a n s ′ = p [ b i ] − 1 = ∑ i = 1 k ( p [ a i ] − 1 ) + k − 1 = a n s + k − 1 ans'=p[bi]-1=\sum_{i=1}^k(p[a_i]-1)+ k-1=ans+k-1 ans=p[bi]1=i=1k(p[ai]1)+k1=ans+k1,从汇到源遍历,每经过一个节点,设其前驱数为k,总路径数+=k-1,感觉算法是正确的,不知道为什么结果有问题,不管了~~

#include<cstdio>
#include<vector>
#include<queue>
#include<iostream>
using namespace std;
int spath;    //shortest path from s to c
int sp_num,max_team;    //num of spath, max_num

//Dijkstra找s和c之间的最短路
void shortest_path(vector<vector<int>> graph,vector<int> team,int s,int c){
    int N=graph.size();
    vector<int> dis(N,-1);    dis[s]=0;
    //用-1=无穷大,加入当前最近的点
    vector<int> visit(N,0);
    for(int iter_time=0;iter_time<N;iter_time++){
        int min_dis=-1;    int min_node=-1;
        for(int i=0;i<N;i++){
            if(visit[i]==0){
                if(min_dis==-1){
                    if(dis[i]!=-1){
                        min_dis=dis[i];
                        min_node=i;
                    }
                }
                else if(dis[i]!=-1&&dis[i]<min_dis){
                    min_dis=dis[i];
                    min_node=i;
                }
            }
        }
        visit[min_node]=1;
        if(min_node==c){
            break;
        }
        //访问min_node
        for(int i=0;i<N;i++){
            //更新dis[i]
            if(visit[i]==0){
                if(graph[min_node][i]!=-1){
                    if(dis[i]=-1)
                        dis[i]=dis[min_node]+graph[min_node][i];
                    else if(dis[i]<dis[min_node]+graph[min_node][i])
                        dis[i]=dis[min_node]+graph[min_node][i];
                }
            }
        }
    }
    // for(int i=0;i<N;i++)    cout<<"i="<<i<<",dis="<<dis[i]<<",visit="<<visit[i]<<endl;
    queue<int>    travel;         //DFS/BFS用于遍历所有最短路节点,BFS/DFS+branch数组可以用于统计s到c的所有路径数,在无环图中,但计算方式有问题吗?
    vector<int>    count(N,0);    //控制首次访问,以求所有路径上的队伍集合
    travel.push(c);
    count[c]=1;
    max_team=team[c];
    sp_num=1;                      //题目保证从c到s至少有一条路径
    vector<int>    branch(N,0);    //记录从s到c的所有最短路上各点的前驱数,前面得到的是s到c的无环图
    while(!travel.empty()){
        int tem=travel.front();
        travel.pop();
        for(int i=0;i<N;i++){
            //条件为真,则i是通往tem的最小路上的点
            if(i!=tem&&graph[i][tem]!=-1&&dis[i]>=0&&dis[i]+graph[i][tem]==dis[tem]&&visit[i]==1){
                if(count[i]==0){        //如果是新加入count的节点,增加其队伍人数
                    // cout<<i<<"->"<<tem<<endl;
                    count[i]=1;
                    max_team+=team[i];
                    travel.push(i);
                }
                branch[tem]++;        //只要有一条边,边的终点的前驱数++,因为路径可以包含已经访问过的节点
            }
        }
    }
    for(int i=0;i<N;i++){
        // cout<<branch[i]<<endl;
        if(count[i]==1&&i!=s)
            sp_num+=branch[i]-1;    //节点有k个前驱,从c到s的总路径数+(k-1)
    }
}

int main(){
    int N,M,C1,C2;    scanf("%d %d %d %d",&N,&M,&C1,&C2);
    vector<int>    team(N);
    for(int i=0;i<N;i++)    scanf("%d",&(team[i]));
    vector<vector<int>>    graph(N,vector<int>(N,-1));
    for(int i=0;i<N;i++)    graph[i][i]=0;
    for(int i=0;i<M;i++){
        int c1,c2,L;
        scanf("%d %d %d",&c1,&c2,&L);
        graph[c1][c2]=L;
    }
    shortest_path(graph,team,C1,C2);
    cout<<sp_num<<" "<<max_team;
    return 0;
}

BackTrack

  • XFS只能访问每个节点各一次
  • 回溯实际上遍历了到任意点的所有可能的路径,递归实现,因此空间复杂度=O(递归深度),so,内存超限~~
int min_path;
vector<int> cnt_team;	//calculate each node's contribution to max_team one time 

void search(vector<vector<int>> graph,vector<int> team,vector<int> &visit,int path,int tem,int c){
    int N=visit.size();
    if(tem==c){
        if(path==min_path){
            //calculate max_team
            for(int i=0;i<N;i++){
                if(visit[i]==1&&cnt_team[i]==0){
                    cnt_team[i]=1;
                    max_team+=team[i];
                }
            }
            //calculate sp_num
            sp_num++;
        }
        return ;
    }
    for(int i=0;i<N;i++){
        if(graph[tem][i]>0&&visit[i]==0){    //节点未访问
            visit[i]=1;
            search(graph,team,visit,path+graph[tem][i],i,c);
            visit[i]=0;
        }
    }
}

int main()
	...
    min_path=shortest_path(graph,team,C1,C2);
    vector<int> visit(N,0);
    cnt_team.assign(N,0);
    visit[0]=1;
    search(graph,team,visit,0,C1,C2);
	cout<<sp_num<<" "<<max_team;
    ...

Exp++

  1. 对图的基本算法之上,结合功能能做点修改了,但希望下次能一次性写对功能——不仅要细心,还要逻辑的严密
  2. 到指定点的路径数,可以用回溯

最程序化的DP要用最傻瓜的写法

在这里插入图片描述

#include<iostream>
#include<iomanip>
#include<vector>
using namespace std;
//1. the old problem can be transfromed into find smallest sum from both sides
//2. common dynamic programming

int main(){
    int K;    cin>>K;
    vector<int> N(K,0);
    for(int i=0;i<K;i++)    cin>>N[i];
    
    vector<int>    max_sum(K,0);
    vector<int> front(K,0);
    vector<int> back(K,0);
    max_sum[0]=N[0];    front[0]=0;    back[0]=0;
    int maxs=max_sum[0],max_front=0,max_back=0;
//handle all numbers are negative
    int sign_neg=1;
    for(int i=0;i<K;i++){
        if(N[i]>=0)    sign_neg=0;
    }
    if(sign_neg==1){
        cout<<"0 "<<N.front()<<" "<<N.back();
        return 0;
    }
    for(int i=1;i<K;i++){
        if(max_sum[i-1]>=0){
            max_sum[i]=max_sum[i-1]+N[i];
            front[i]=front[i-1];    back[i]=i;
        }
        else{
            max_sum[i]=N[i];
            front[i]=i;    back[i]=i;
        }
        if(max_sum[i]>maxs){
            maxs=max_sum[i];    max_front=front[i];    max_back=back[i];    
        }
    }
    //输出首尾数,不是首尾序号
    cout<<maxs<<" "<<N[max_front]<<" "<<N[max_back]<<endl;
    return 0;
}

感想

PTA B区比A区水多了,但我没有勇气刷C区和D区,哎,还是回归leetcode
在这里插入图片描述

Extremely large bag of bear

在这里插入图片描述

mean1: iterative dp

//n,w设置成n+1,w+1的矩阵不仅方便读写,还方便赋初值
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cmath>

using namespace std;

int main(){
    int n,W;    scanf("%d %d",&n,&W);
    vector<int> value(n+1,0);
    vector<int> weight(n+1,0);
    for(int i=1;i<=n;i++)
        scanf("%d %d",&(weight[i]),&(value[i]));
    vector<vector<int>> total_v(n+1,vector<int>(W+1,0));
    int max_value=0;
    for(int i=0;i<=W;i++)
        total_v[0][i]=0;
    for(int j=0;j<=n;j++)
        total_v[j][0]=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=W;j++){
            if(j<weight[i])
                total_v[i][j]=total_v[i-1][j];
            else
                total_v[i][j]=max(total_v[i-1][j],total_v[i-1][j-weight[i]]+value[i]);
            max_value=max(max_value,total_v[i][j]);
            // printf("%d ",total_v[i][j]);
        }
        // printf("\n");
    }
    printf("%d",max_value);
    return 0;
}

mean2: recursive dp

//n,w设置成n+1,w+1的矩阵不仅方便读写,还方便赋初值
//backtrack 是2^n复杂度; dp是nw时间,但n很大时backtrack还是超时~(循环终止值写错~)

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cmath>

using namespace std;
int n,w;
vector<int> value;
vector<int> weight;
vector<vector<int>> dp;
vector<vector<int>> visit;

int backtrack(int n,int w){
    if(visit[n][w]==1)
        return dp[n][w];
    else{
        if(w<weight[n])
            dp[n][w]=backtrack(n-1,w);
        else
            dp[n][w]=max(backtrack(n-1,w),backtrack(n-1,w-weight[n])+value[n]);
        visit[n][w]=1;
        // printf("visit[%d][%d]=%d\n",n,w,dp[n][w]);
        return dp[n][w];
    }
}

int main(){
    scanf("%d %d",&n,&w);
    value.assign(n+1,0);
    weight.assign(n+1,0);
    for(int i=1;i<=n;i++)
        scanf("%d %d",&(weight[i]),&(value[i]));
    dp.assign(n+1,vector<int>(w+1,0));
    visit.assign(n+1,vector<int>(w+1,0));
    for(int i=0;i<=w;i++){
        dp[0][i]=0;
        visit[0][i]=1;
    }
    for(int j=0;j<=n;j++){
        dp[j][0]=0;
        visit[j][0]=1;
    }
    int ans=backtrack(n,w);
    // for(int i=0;i<=n;i++){
    //     for(int j=0;j<=w;j++)
    //         printf("%d",dp[i][j]);
    //     printf("\n");
    // }
    printf("%d",ans);
}

mean3: dp with value-prior strategy

这个算法题的题解基于一种动态规划的方法来求解背包问题,其中背包的容量可能非常大。我们不是直接以背包的容量作为状态,而是采用物品的总价值作为状态,这种方法称为价值优先的动态规划

算法思路

初始化状态数组:首先,计算所有物品的总价值totV,然后初始化一个动态规划数组dp,大小为totV + 1,用来记录达到某个价值所需的最小容量。dp[0] = 0表示价值为0时不需要任何物品,因此容量需求为0。其余元素初始化为一个很大的值(INT_MAX / 2),表示初始状态下这些价值不可达。

动态规划转移:遍历每个物品,然后逆向遍历从totV到该物品价值v[i]的所有价值状态,尝试加入当前物品。对于每个价值j,更新dp[j]为原来的dp[j]和dp[j - v[i]] + w[i]中较小的一个。dp[j - v[i]] + w[i]表示如果当前物品加入背包,达到价值j所需的最小容量。

找到最大价值:从totV开始向下遍历dp数组,找到第一个使得dp[i] <= W的价值i,即为可以实现的最大价值。这是因为我们从总价值最大值开始向下查找,第一个符合条件的价值即为满足背包容量限制下的最大价值。

代码解释

int n, W;读入物品数量和背包容量。
vector w(n), v(n);分别存储每个物品的容量和价值。
int totV = 0;用于累加所有物品的价值,为动态规划数组dp的大小做准备。
vector dp(totV + 1, INT_MAX / 2);初始化动态规划数组,dp[0] = 0。
内层循环中for (int j = totV; j >= v[i]; j–)逆向更新dp数组,保证每个物品只被计算一次。
最后逆向查找第一个使得dp[i] <= W的价值,输出这个最大价值。

总结

这个题解利用了动态规划中的价值优先策略,通过转换状态定义来优化解决大容量背包问题的空间复杂度。其核心在于以物品总价值作为动态规划的维度,从而在容量较大时仍能高效求解。

//n,w设置成n+1,w+1的矩阵不仅方便读写,还方便赋初值
//backtrack 是2^n复杂度; dp是nw时间,但n很大时backtrack还是超时~(循环终止值写错~)
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cmath>

using namespace std;

int main() {
    int n, W;
    cin >> n >> W;
    vector<int> w(n), v(n);
    int totV = 0;
    for (int i = 0; i < n; i++) {
        cin >> w[i] >> v[i];
        totV += v[i];
    }

    vector<int> dp(totV + 1, (pow(2,31)-1)/ 2); //这里dp[i]=(pow(2,31)-1)/2,一是设置初值为尽可能大,
    //但设为pow(2,31)-1导致dp[j - v[i]] + w[i]溢出,因此设为最大的一半左右

    dp[0] = 0;
    for (int i = 0; i < n; i++) {
        // printf("the %d-th turn:\n",i);
        for (int j = totV; j >= v[i]; j--) {
            //dp[j-v[i]]不更新dp[j]必然无效, 有效才会比较,
            //如果是顺序,会导致第i个元素选择两次,so 一元数组只能逆序
            dp[j] = min(dp[j], dp[j - v[i]] + w[i]);
            // printf("dp[%d]=%d\n",j,dp[j]);
        }
    }

    for (int i = totV; i >= 0; i--) {
        if (dp[i] <= W) {
            cout << i << endl;
            break;
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值