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++
- judge_primer(1)==true,但1不是primer,minimal_primer(1)==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
- 用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]找路径会挺麻烦的
- 或者用递归,时间一样,空间复杂度还不如这个呢——无敌的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)+k−1=ans+k−1,从汇到源遍历,每经过一个节点,设其前驱数为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++
- 对图的基本算法之上,结合功能能做点修改了,但希望下次能一次性写对功能——不仅要细心,还要逻辑的严密
- 到指定点的路径数,可以用回溯
最程序化的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;
}
}
}