专项训练3
数据结构专题
干脆直接叫线段树专题算了。
1.【模板】线段树分裂
link:https://www.luogu.com.cn/problem/P5494
线段树的所有板子套到一起,个人觉得也不是很难,也不想多浪费口舌了。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=2e5+5; int n, m, a[maxn], bac[maxn], cnt, tot, rt[maxn], idx=1; struct seg_tree{ int l, r, num; }tr[maxn<<4]; int newnode() { return cnt?bac[cnt--]:++tot; } void del(int id) { bac[++cnt]=id; tr[id].l=tr[id].r=tr[id].num=0; } void update(int &id,int l,int r,int v,int t) { if(!id) id=newnode(); tr[id].num+=t; if(l==r) return ; int mid=(l+r)>>1; if(v<=mid) update(tr[id].l, l, mid, v, t); else update(tr[id].r, mid+1, r, v, t); } int query(int id,int l,int r,int ll,int rr) { if(r<ll||l>rr) return 0; if(l>=ll&&r<=rr) { return tr[id].num; } int mid=(l+r)>>1; if(rr<=mid) return query(tr[id].l, l, mid, ll, rr); else if(ll>mid) return query(tr[id].r, mid+1, r, ll, rr); else return query(tr[id].l, l, mid, ll, mid)+query(tr[id].r, mid+1, r, mid+1, rr); } int kth(int id,int l,int r,int k) { if(l==r) return l; int mid=(l+r)>>1; int s1=tr[tr[id].l].num, s2=tr[tr[id].r].num; if(k<=s1) return kth(tr[id].l, l, mid, k); else return kth(tr[id].r, mid+1, r, k-s1); } int merge(int a, int b,int l,int r) { if(!a||!b) { return a+b; } if(l==r) { tr[a].num+=tr[b].num; del(b); return a; } int mid=(l+r)>>1; tr[a].l=merge(tr[a].l, tr[b].l, l, mid); tr[a].r=merge(tr[a].r, tr[b].r, mid+1, r); tr[a].num=tr[tr[a].l].num+tr[tr[a].r].num; del(b); return a; } void split(int a,int &b,int k) { if(!a) return ; b=newnode(); int v=tr[tr[a].l].num; if(k>v) split(tr[a].r, tr[b].r, k-v); else swap(tr[a].r, tr[b].r); if(k<v) split(tr[a].l, tr[b].l, k); tr[b].num=tr[a].num-k; tr[a].num=k; } signed main() { cin>>n>>m; for(int i=1;i<=n;i++) { cin>>a[i]; update(rt[1], 1, n, i, a[i]); } while(m--) { int opt, p, x, y; cin>>opt; if(opt==0) { cin>>p>>x>>y; int k1=query(rt[p], 1, n, 1, y), k2=query(rt[p], 1, n, x, y); int tmp=0; split(rt[p], rt[++idx], k1-k2); split(rt[idx], tmp, k2); rt[p]=merge(rt[p], tmp, 1, n); } else if(opt==1) { cin>>p>>x; rt[p]=merge(rt[p], rt[x], 1, n); } else if(opt==2) { cin>>p>>x>>y; update(rt[p], 1, n, y, x); } else if(opt==3) { cin>>p>>x>>y; cout<<query(rt[p], 1, n, x, y)<<endl; } else { cin>>p>>x; if(tr[rt[p]].num<x) cout<<-1<<endl; else cout<<kth(rt[p], 1, n, x)<<endl; } } return 0; }
2. 三元上升子序列
link:https://www.luogu.com.cn/problem/P1637
煎蛋题,发现只需要枚举中间数,然后用权值线段树统计 前面比当前数小的个数 和 后面比当前数大的个数的乘积,最后相加即可。
Tips:RE
越界不只是循环,一些+1、-1啊注意特判!!!
3. Sanae and Giant Robot
详见专项训练2-并查集专项。
4. [COCI2010-2011#6] STEP
link:https://www.luogu.com.cn/problem/P6492
小清新版山海经,合并很套路。
遇到要求一些满足特殊要求的子序列、子串长度,考虑维护最大子段和版线段树。
5. [USACO11DEC] Grass Planting G
link:https://www.luogu.com.cn/problem/P3038
树链剖分板子题(话说这个提单前面怎么这么水啊)。
Tips:RE
越界不只是循环,一些+1、-1啊注意特判!!!
6.「TOCO Round 1」History
link:https://www.luogu.com.cn/problem/P7394
收获很大啊。
首先遇到第三个操作:回到第
然后回过头来继续想想怎么查询,显然是要求
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=1e5+5; int n, m, bfn[maxn], f[maxn][20], idx, dep[maxn], a[maxn], rk[maxn], ans[maxn]; #define lowbit(x) (x&(-x)) int sum[maxn]; void update(int pos,int val) { while(pos<=n) { sum[pos]+=val; pos+=lowbit(pos); } } int query(int pos) { int res=0; while(pos>0) { res+=sum[pos]; pos-=lowbit(pos); } return res; } struct node{ int opt, x, y, id; }q[maxn]; struct edge{ int next, to; }edge[maxn<<1], e[maxn<<1]; int head[maxn], edgenum, head1[maxn], edgenum1; void add(int from,int to) { edge[++edgenum].next=head[from]; edge[edgenum].to=to; head[from]=edgenum; } void add1(int from,int to) { e[++edgenum1].next=head1[from]; e[edgenum1].to=to; head1[from]=edgenum1; } void bfs() { queue<int> q; q.push(1); while(q.size()) { int u=q.front(); q.pop(); dep[u]=dep[f[u][0]]+1; bfn[u]=++idx; rk[idx]=u; for(int i=1;i<=18;i++) f[u][i]=f[f[u][i-1]][i-1]; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(bfn[v]) continue; q.push(v); f[v][0]=u; } } } int kth(int x,int k) { for(int i=18;i>=0;i--) { if((1<<i)<=k&&dep[f[x][i]]<=dep[x]) { x=f[x][i]; k-=(1<<i); } } return bfn[x]; } int get(int x,int k) { int l=1, r=n, z=0, y=0; while(l<r)//bfn越大,k级祖先bfn越大 { int mid=(l+r)>>1; if(kth(rk[mid], k)>=x) r=mid; else l=mid+1; } z=l; l=1, r=n; while(l<r) { int mid=(l+r+1)>>1; if(kth(rk[mid], k)<=x) l=mid; else r=mid-1; } y=l; return query(y)-query(z-1); } void dfs(int u,int fa) { int opt=q[u].opt, x=q[u].x, y=q[u].y; if(opt==1) { if(a[x]) update(bfn[x], -1); else update(bfn[x], 1); a[x]^=1; } else if(opt==2) { if(y%2) ans[u]=0; else { y/=2; if(y==0) ans[u]=a[x]; else if(dep[x]-1<y) ans[u]=0; else ans[u]=get(kth(x, y), y)-get(kth(x, y-1), y-1); } } for(int i=head1[u];i;i=e[i].next) { int v=e[i].to; if(v==fa) continue; dfs(v, u); } if(opt==1) { if(a[x]) update(bfn[x], -1); else update(bfn[x], 1); a[x]^=1; } } signed main() { cin>>n; for(int i=1;i<n;i++) { int u, v; cin>>u>>v; add(u, v), add(v, u); } cin>>m; for(int i=1;i<=m;i++) { int opt, x, y; cin>>opt; q[i].id=i; if(opt==1) { cin>>x; q[i].opt=opt, q[i].x=x; add1(i-1, i); } else if(opt==2) { cin>>x>>y; q[i].opt=opt, q[i].x=x, q[i].y=y; add1(i-1, i); } else { cin>>x; q[i].opt=opt, q[i].x=x; add1(x, i); } } bfs(), dfs(0, 0); for(int i=1;i<=m;i++) { if(q[i].opt==2) cout<<ans[i]<<endl; } return 0; }
7. Points
link:https://www.luogu.com.cn/problem/CF19D
一开始到luogu上搜这个题名结果看成一个蓝题了。。。想了半个小时才发现不对劲。
话说这个笛卡尔坐标系也没体现出来啊,亏我还专门去学了好久。
自己怎么想的已经不记得了,看了题解后觉得不是特别难啊,把add的值的横坐标作为线段树叶子节点,然后维护当前横坐标的最纵坐标,套一个set。在线段树上二分找到答案,其实应该是可以自己写出来的(但写了一半感觉很怪异就弃了),其他的难点好像也没什么了,哦,还要记得离散化。
点击查看代码
#include<bits/stdc++.h> #define int long long #define lid id<<1 #define rid id<<1|1 using namespace std; const int maxn=4e5+5; int n, b[maxn], tot; set<int> s[maxn]; struct seg_tree{ int l, r, maxx; }tr[maxn<<2]; void pushup(int id) { tr[id].maxx=max(tr[lid].maxx, tr[rid].maxx); } void build(int id,int l,int r) { tr[id].l=l, tr[id].r=r; if(l==r) { return ; } int mid=(l+r)>>1; build(lid, l, mid); build(rid, mid+1, r); } void update(int id,int l,int val) { if(tr[id].l==tr[id].r) { tr[id].maxx=max(tr[id].maxx, val); return ; } int mid=(tr[id].l+tr[id].r)>>1; if(l<=mid) update(lid, l, val); else update(rid, l, val); pushup(id); } void modify(int id,int l,int val) { if(tr[id].l==tr[id].r&&tr[id].l==l) { tr[id].maxx=val; return ; } int mid=(tr[id].l+tr[id].r)>>1; if(l<=mid) modify(lid, l, val); else modify(rid, l, val); pushup(id); } int query(int id,int l,int v) { if(tr[id].l==tr[id].r) { if(tr[id].maxx<=v) return -1; return tr[id].l; } int mid=(tr[id].l+tr[id].r)>>1; int res=-1; if(l<=mid&&tr[lid].maxx>v) res=query(lid, l, v); if(res!=-1) return res; if(tr[rid].maxx>v) return query(rid, l, v); return -1; } struct node{ int x, y; string opt; }q[maxn]; signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin>>n; for(int i=1;i<=n;i++) { cin>>q[i].opt>>q[i].x>>q[i].y; b[++tot]=q[i].x, b[++tot]=q[i].y; } sort(b+1, b+tot+1); int m=unique(b+1, b+tot+1)-b-1; for(int i=1;i<=n;i++) { q[i].x=lower_bound(b+1, b+m+1, q[i].x)-b; q[i].y=lower_bound(b+1, b+m+1, q[i].y)-b; } build(1, 1, m); for(int i=1;i<=n;i++) { int x=q[i].x, y=q[i].y; string opt=q[i].opt; if(opt[0]=='a') { update(1, x, y); s[x].insert(y); } else if(opt[0]=='r') { s[x].erase(y); if(s[x].empty()) { modify(1, x, 0); continue; } set<int>::iterator it=s[x].end(); it--; modify(1, x, *it); } else { int ans=query(1, x+1, y); if(ans==-1) { cout<<ans<<endl; continue; } cout<<b[ans]<<" "<<b[*s[ans].upper_bound(y)]<<"\n"; } } return 0; }
Tips:线段树的边界值搞清楚啊。
单调栈/单调队列
1. Cashback
link:https://www.luogu.com.cn/problem/CF940E
很可惜啊,就剩最后一个性质没有往下想了(即区间长度为c是最优的),思考的还是太少,然后设
贴板子
head=1, tail=0; for(int i=1;i<=n;i++) { while(head<=tail&&q[head]<=i-k) head++; while(head<=tail&&a[q[tail]]>a[i]) tail--; q[++tail]=i; //转移方程 }
2. Cutlet
link:https://www.luogu.com.cn/problem/CF939F
神奇巧妙地dp。称正在煎的那一面为背面,裸露的为正面,设
. .
首先能观察到只有i在区间
- 只翻一次
- 枚举翻转后煎了k秒,那么
。 - 单调队列维护,枚举j+k,当k>r-l时就不合法了,所以当决策点<l-j时就不合法了。
- 枚举翻转后煎了k秒,那么
- 翻两次
- 枚举翻转后背面煎了k秒,那么
. - 单调队列维护,枚举j-k,当k>r-l时就不合法了,所以当决策点<j-r+l时就不合法了。
- 枚举翻转后背面煎了k秒,那么
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=105, N=1e5+5; int n, k, l, r, dp[2][N*2], fl[N], head, tail, q[N*2]; signed main() { cin>>n>>k; memset(dp[0], 0x3f, sizeof(dp[0])); dp[0][0]=0; for(int i=1;i<=k;i++) { cin>>l>>r; for(int j=0;j<=n;j++) { dp[i&1][j]=dp[!(i&1)][j]; } head=1, tail=0; for(int j=r;j>=0;j--) { while(head<=tail&&q[head]<l-j) head++; while(head<=tail&&dp[!(i&1)][q[tail]]>dp[!(i&1)][r-j]) tail--; q[++tail]=r-j; dp[i&1][j]=min(dp[!(i&1)][q[head]]+1, dp[i&1][j]); } head=1, tail=0; for(int j=0;j<=r;j++) { while(head<=tail&&q[head]<j+l-r) head++; while(head<=tail&&dp[!(i&1)][q[tail]]>dp[!(i&1)][j]) tail--; q[++tail]=j; dp[i&1][j]=min(dp[!(i&1)][q[head]]+2, dp[i&1][j]); } } if(dp[k&1][n]==0x3f3f3f3f3f3f3f3f) cout<<"Hungry"; else cout<<"Full"<<endl<<dp[k&1][n]; return 0; }
3.「CZOI-R1」消除威胁
link:https://www.luogu.com.cn/problem/P10798
嗯,先把每个点赋为他的绝对值。我的想法是找到每个点i后第一个比他大的记为r[i],然后以i为左端点,i到r[i]中任意一个为右端点的就是可能构成威胁区间,但巨大的问题是难以判断左右端点是否相同而且还有更多的区间满足要求但我没统计。会发现我们想要的就只是所有威胁区间 并判断 左右端点是取反会不会使得答案更优。所以用单调栈求出cnt[i]表示以i为左端点的威胁区间的个数(代码实现只需要在加入新点是判断是否与栈顶相等)。对于当前a[i],如果为0,那么取不取反没啥区别,他的贡献为
设c=cnt[i]+1,先不看除以2
4. Kuzya and Homework
link:https://www.luogu.com.cn/problem/CF1582G
蛮有意思的一道题。一个值序列全为整数就说明每一个除数的质因子都相消了,是不是很像括号序列一样消右括号?那么就给每一个质因子都开一个栈来记录含有这个质因子的位置(有多个相同质因子要多次记录),为什么要用栈呢?根据后进先出的性质,假如一段把除数都消完了,那么留下的都是尽量靠前的位
置,找出消的数的最前的位置(l[i]),这个位置就表示只有左端点在l[i]及其左边时,才能满足值序列全为整数。处理出每个数的 l[i] ,答案就是满足
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=1e6+5; int n, a[maxn], l[maxn], ans, p[maxn], st[maxn], tot, minn[maxn]; char b[maxn]; vector<int> s[maxn]; struct node{ int l, val; }; stack<node> t; void prime() { st[1]=1; for(int i=2;i<maxn;i++) { if(!st[i]) { p[++tot]=i; minn[i]=i; } for(int j=1;j<=tot&&p[j]*i<maxn;j++) { st[p[j]*i]=1; minn[p[j]*i]=p[j]; } } } signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); prime(); cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; } for(int i=1;i<=n;i++) { cin>>b[i]; } for(int i=1;i<=n;i++) { int x=a[i]; if(b[i]=='*') { l[i]=i; while(x>1) { s[minn[x]].push_back(i); x/=minn[x]; } } else { l[i]=i; bool flag=0; while(x>1) { if(s[minn[x]].size()==0) { l[i]=0; flag=1; break; } l[i]=min(l[i], s[minn[x]].back()); s[minn[x]].pop_back(); x/=minn[x]; } } } for(int i=n;i>=1;i--) { // cout<<l[i]<<" "; int res=1; while(!t.empty()&&t.top().l>=l[i]) { res+=t.top().val; t.pop(); } t.push((node){l[i],res}); if(l[i]==i) ans+=res; } cout<<ans; return 0; }
5. [HNOI2016] 序列
link:https://www.luogu.com.cn/problem/P3246
想办法找到每一个i数左边第一个比他小的数的位置
数学2
1. [六省联考 2017] 期末考试
link:https://www.luogu.com.cn/problem/P3745
先把这个题放前面是因为此题的一种简单做法比T1好想。但应该也只仅限于好想了。第一篇题解思路。很一眼的想法就是找到一个统一公布时间t,比t晚的科目想办法调到t这个位置,使得总花费最少。答案没有单调性,不能二分,干脆直接枚举统一时间。现在考虑如何求当前时间的最少花费。题目中的两种操作都可以使得一些科目时间前移,每次操作第一个的代价为A+一科目后移一天,第二个代价为B。分类讨论:
- A>=B(=放在这个位置还是相对合适一点)
肯定选B了,A的代价要多很多。 - A<B
有能往后拖的科目就拿来和往前移的科目替换,每次代价为A,如果实在没有<t的科目了,再用B来前移。
复杂度看起来很玄学,实际上只需要前缀和就可以优化到 O(n).
讲一下代码实现:正序枚举时间,sum记录下所有时间≤t的科目的总时间,t1记录对应的个数,同样记录下>t的相应的两个值记为sumb和nb,这样就能很轻松地表示出>t的总天数和<t的总天数。学生需求以同样的方式计算。
点击查看代码
//代码太过巧妙 #include<bits/stdc++.h> #define ull unsigned long long using namespace std; const int maxn=1e5+5; ull A, B, C, n, m, t[maxn], b[maxn], maxx, tt[maxn], tb[maxn]; ull sumt, sumb, nt, nb, sum, t1, ans=2e18; signed main() { cin>>A>>B>>C>>n>>m; for(int i=1;i<=n;i++) { cin>>t[i]; tt[t[i]]++; } for(int i=1;i<=m;i++) { cin>>b[i]; tb[b[i]]++; sumb+=b[i]; nb++; maxx=max(maxx, b[i]); } for(int i=0;i<=maxx;i++) { ull res=0; sum+=i*tb[i], t1+=tb[i]; sumb-=i*tb[i], nb-=tb[i]; sumb=max(0ull, sumb), nb=max(0ull, nb); sumt+=i*tt[i], nt+=tt[i]; if(sum==0) continue; if(A>=B) { res+=max(0ull, sumb-nb*i)*B; } else { ull r=max(0ull, sumb-nb*i), l=max(0ull,t1*i-sum); res+=max(0ull, min(l, r))*A; r-=max(0ull, min(l, r)); if(r) res+=r*B; } res+=max(0ull, nt*i-sumt)*C; ans=min(ans, res); } cout<<ans; return 0; }
2. [NOI2015] 寿司晚宴
link:https://www.luogu.com.cn/problem/P2150
不像其他数论题那么恶心,但是也不好想。
3. 追寻 | Pursuit of Dream
link:https://www.luogu.com.cn/problem/P8967
趁热打铁。一晚上终于看懂了。会发现有用的点只有k个,剩下的点的期望都是一样的。设
- 对于前者
先算出n维的坐标与终点的距离,即 ,这就是总共要走的步数。但是我们并不确定这些步数的前后关系(即不知道先走的哪一维),可以抽象成 中有 个x, 个y等等,要求 的全排列。这是多重集全排列问题。总方案数为: ,设他为t,即合法步数。那么概率为 ,期望为 。 - 对于后者
首先想到的是从一个点散入天际后回到终点这个过程的期望是可以提前算出来的,为 。现在缺的是从i点出发,在终点前散入天际的期望,显然不好求,考虑容斥,它就等于从i点走散入天际的期望-从i点走到达终点后散入天际的期望,前者用求导可以证明为 ,后者为 ,最终狮子就是 。
然后推一下狮子把g解出来再带到dp里算一下就好了。
4. 随机漫游
link:https://www.luogu.com.cn/problem/P4321
方程是很好推的,但状态设定是想不到的。观察到n很小,直接状压。设
点击查看代码
//方程推对,状态设错 #include<bits/stdc++.h> #define int long long #define eps 1e-6 using namespace std; const int maxn=1e6+5, mod=998244353; int n, e, m, c[maxn], du[maxn], edge[maxn], deg[maxn]; int pos[maxn], id[maxn], cnt, g[maxn], a[30][30], dp[maxn][30]; inline int qpow(int a,int b) { int res=1; while(b) { if(b&1) res=res*a%mod; a=a*a%mod; b>>=1; } return res; } inline void gauss(int n) { for(int i=1;i<=n;i++) { int r=i; for(int k=i;k<=n;k++) { if(a[k][i]) { swap(a[k], a[i]); break; } } // swap(a[r], a[i]); for(int k=1;k<=n;k++) { if(!a[k][i]||k==i) continue; int x=a[k][i]*qpow(a[i][i],mod-2)%mod; for(int j=i;j<=n+1;j++) { a[k][j]=((a[k][j]-x*a[i][j]%mod)%mod+mod)%mod; } } } for(int i=1;i<=n;i++) a[i][n+1]=a[i][n+1]*qpow(a[i][i],mod-2)%mod; return; } inline void init() { g[0]=1; for(int i=1;i<=n;i++) { g[i]=(g[i-1]*qpow(i, mod-2))%mod; } } signed main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin>>n>>e; int tot=(1<<n)-1; for(int i=1;i<=e;i++) { int u, v; cin>>u>>v; edge[u]|=(1<<v-1), edge[v]|=(1<<u-1); du[u]++, du[v]++; } for(int s=tot-1;s>=1;s--)//按集合大小倒推 { memset(a, 0, sizeof(a)); for(int i=1;i<=n;i++) { if(s&(1<<i-1)) { a[i][i]=a[i][n+1]=1; int x=qpow(du[i],mod-2); for(int j=1;j<=n;j++) { if(edge[i]&(1<<j-1)) { if(s&(1<<j-1)) a[i][j]=mod-x; else a[i][n+1]=(a[i][n+1]+x*dp[s|(1<<j-1)][j]%mod)%mod; } } } } gauss(n); for(int i=1;i<=n;i++) { if(s&(1<<i-1)) dp[s][i]=a[i][n+1]; } } cin>>m; while(m--) { int n, u=0; cin>>n; for(int i=1;i<=n;i++) cin>>c[i], u|=(1<<(c[i]-1)); int s=0; cin>>s; cout<<dp[(tot^u)|(1<<(s-1))][s]<<endl; } return 0; }
5. [JLOI2014] 路径规划
link:https://www.luogu.com.cn/problem/P3259
平均时间是什么?平均数?期望?答案是期望,这里直接给出:一个红绿灯的期望为
这道题就是在最短路上加了两个限制:红绿灯和加油站,考虑使用分层图,由于k相比于加油站要小,考虑先建k层(称这个分层图为G1),跑最短路,这样子就只剩加油站一个限制了。可以把加油这一限制也看成边,把起点、终点和加油站全部单拎出来(所有层的),称这些点为G2,会发现其他点已经没什么用了,我们要的也就只是个边权,将G2里面两点最短路小于limit的重新连边,边权为G1上的最短路+cost,形成了一个新的分层图。再在这个新图上跑最短路即可。复杂度可以证明是能过的。
点击查看代码
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=1e6+5; int n, m, k, lim, cost, st, ed; int head[maxn], edgenum, vis[maxn], edgenum1, head1[maxn]; double dis[maxn]; map<string,pair<int,double> > id; struct edge{ int next; int to; double w; }edge[maxn<<1], edge1[maxn<<1]; void add(int from,int to,double w) { edge[++edgenum].next=head[from]; edge[edgenum].to=to; edge[edgenum].w=w; head[from]=edgenum; } void add1(int from,int to,double w) { edge1[++edgenum1].next=head1[from]; edge1[edgenum1].to=to; edge1[edgenum1].w=w; head1[from]=edgenum1; } struct node{ double dis; int pos; bool operator<(const node &x) const { return x.dis<dis; } }; inline void dijkstra(int s) { for(int i=1;i<=n*(k+1);i++) dis[i]=0x3f3f3f3f3f3f3f3f, vis[i]=0; priority_queue<node> q; dis[s]=0, q.push((node){0, s}); while(q.size()) { int u=q.top().pos; q.pop(); if(vis[u]) continue; vis[u]=1; for(int i=head[u];i;i=edge[i].next) { int v=edge[i].to; if(dis[v]>dis[u]+edge[i].w) { dis[v]=edge[i].w+dis[u]; q.push((node){dis[v], v}); } } } } inline void dij(int s) { for(int i=1;i<=n*(k+1);i++) dis[i]=0x3f3f3f3f3f3f3f3f, vis[i]=0; priority_queue<node> q; dis[s]=0, q.push((node){0, s}); while(q.size()) { int u=q.top().pos; q.pop(); if(vis[u]) continue; vis[u]=1; for(int i=head1[u];i;i=edge1[i].next) { int v=edge1[i].to; if(dis[v]>dis[u]+edge1[i].w) { dis[v]=edge1[i].w+dis[u]; q.push((node){dis[v], v}); } } } } vector<int> gas; signed main() { cin>>n>>m>>k>>lim>>cost; for(int i=1;i<=n;i++) { string s; cin>>s; if(s.find("gas")!=string::npos) { gas.push_back(i); } if(s=="start") st=i, gas.push_back(i); else if(s=="end") ed=i, gas.push_back(i); int a, b; cin>>a>>b; id[s]=make_pair(i, a*a*1.0/(2*(a+b))); } for(int i=1;i<=m;i++) { string s1, s2, s3; int w; cin>>s1>>s2>>s3; for(int j=0;j<=k;j++)//建分层图 { if(id[s2].second) add(j*n+id[s1].first, (j+1)*n+id[s2].first, w+id[s2].second); else add(j*n+id[s1].first, j*n+id[s2].first, w); if(id[s1].second) add(j*n+id[s2].first, (j+1)*n+id[s1].first, w+id[s1].second); else add(j*n+id[s2].first, j*n+id[s1].first, w); } } for(int i:gas) { dijkstra(i); for(int j:gas) { if(i==j) continue; int w=(i==st||i==ed?0:cost); for(int l=0;l<=k;l++) { if(dis[l*n+j]<=lim) { for(int p=0;p+l<=k;p++) { add1(p*n+i, (p+l)*n+j, dis[l*n+j]+w); add1((p+l)*n+j, p*n+i, dis[l*n+j]+w); } } } } } dij(st); // cout<<"qwq"; double ans=1e9; for(int i=0;i<=k;i++) { ans=min(ans, dis[i*n+ed]); } printf("%.3lf",ans); return 0; }
逆着风吹干眼泪,说不出口的痛越藏越多,腐烂在肚子里,却又不知道彼此心知且肚明,所以无法孕育出美好的结局,只会是恋者相残的戏码不停上演。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】