0%

题目大意

给定一个图,边有权值。在节点上每经过 k 条边必须休息一次(或者说每走 k 步必须停顿)。求从起点到终点的最短时间(或路径权值和),考虑休息带来的限制/代价。

AC代码

参考了官方题解思路,就是在分层图上跑最短路

image

相比于TLE代码,只加了个这个优化

while(!q.empty() && dist[n+1]==INF)

如果dist[n+1]已经确定,那就不用再跑了。

然后代码实现上有比较多的细节,主要是在初始化方面,比如说vis[i][0]也要清理什么的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// https://ac.nowcoder.com/acm/contest/91355/D
#include <iostream>
#include <set>
#include <vector>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const ll N=2e5+10,INF=1e18+7;
ll T,n,m,k,a[N],u,v,dist[N];
vector<ll> e[N];
bool vis[N][11];
struct edge {
ll node,dis,nrest; // nrest 为多久没休息了
};
struct cmp {
bool operator()(edge a,edge b) {
if(a.dis!=b.dis)
return a.dis>b.dis;
return a.node<b.node;
}
};
priority_queue<edge,vector<edge>,cmp > q;
edge make_edge(ll node,ll dis,ll nrest) {
edge temp;
temp.node=node;temp.dis=dis;temp.nrest=nrest;
return temp;
}
void dijkstra(int t) {
q.push(make_edge(1,0,0));
while(!q.empty() && dist[n+1]==INF) {
ll node=q.top().node,dis=q.top().dis,nrest=q.top().nrest;
q.pop();
if(vis[node][nrest]) // 之前访问过,弹出
continue;
vis[node][nrest]=true;
dist[node]=min(dis,dist[node]);
for(int i=0;i<e[node].size();i++) {
if(nrest>=k)
q.push(make_edge(e[node][i],dis+a[node],0));
else {
q.push(make_edge(e[node][i],dis+a[node],0));
q.push(make_edge(e[node][i],dis+1,nrest+1));
}
}
}
}
void clear() {
priority_queue<edge,vector<edge>,cmp > empty;
swap(empty,q);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
for(int _=1;_<=T;_++) {
cin>>n>>m>>k;
clear();
for(int i=1;i<=n+1;i++)
e[i].clear();
for(int i=1;i<=n;i++) {
cin>>a[i];
}
fill(&dist[1],&dist[n+2],INF);
for(int i=1;i<=n+1;i++)
for(int j=0;j<=k;j++)
vis[i][j]=false;
for(int i=1;i<=m;i++) {
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
e[n].push_back(n+1);
e[n+1].push_back(n);
dijkstra(_);
cout<<dist[n+1]<<endl;
}
return 0;
}
// TLE https://ac.nowcoder.com/acm/contest/view-submission?submissionId=72128029
// AC https://ac.nowcoder.com/acm/contest/view-submission?submissionId=72128343

心路历程(WA,TLE,MLE……)

过了40% TLE https://ac.nowcoder.com/acm/contest/view-submission?submissionId=72128029

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// https://ac.nowcoder.com/acm/contest/91355/D
#include <iostream>
#include <set>
#include <vector>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const ll N=2e5+10,INF=1e18+7;
ll T,n,m,k,a[N],u,v,dist[N];
vector<ll> e[N];
bool vis[N][11];
struct edge {
ll node,dis,nrest; // nrest 为多久没休息了
};
struct cmp {
bool operator()(edge a,edge b) {
if(a.dis!=b.dis)
return a.dis>b.dis;
return a.node<b.node;
}
};
priority_queue<edge,vector<edge>,cmp > q;
edge make_edge(ll node,ll dis,ll nrest) {
edge temp;
temp.node=node;temp.dis=dis;temp.nrest=nrest;
return temp;
}
void dijkstra(int t) {
q.push(make_edge(1,0,0));
while(!q.empty()) {
ll node=q.top().node,dis=q.top().dis,nrest=q.top().nrest;
q.pop();
if(vis[node][nrest]) // 之前访问过,弹出
continue;
vis[node][nrest]=true;
dist[node]=min(dis,dist[node]);
for(int i=0;i<e[node].size();i++) {
if(nrest>=k)
q.push(make_edge(e[node][i],dis+a[node],0));
else {
q.push(make_edge(e[node][i],dis+a[node],0));
q.push(make_edge(e[node][i],dis+1,nrest+1));
}
}
}
}
void clear() {
priority_queue<edge,vector<edge>,cmp > empty;
swap(empty,q);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
for(int _=1;_<=T;_++) {
cin>>n>>m>>k;
clear();
for(int i=1;i<=n+1;i++)
e[i].clear();
for(int i=1;i<=n;i++) {
cin>>a[i];
}
fill(&dist[1],&dist[n+2],INF);
for(int i=1;i<=n+1;i++)
for(int j=0;j<=k;j++)
vis[i][j]=false;
for(int i=1;i<=m;i++) {
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
e[n].push_back(n+1);
e[n+1].push_back(n);
dijkstra(_);
cout<<dist[n+1]<<endl;
}
return 0;
}
// TLE https://ac.nowcoder.com/acm/contest/view-submission?submissionId=72128029

题目大意

求解无限连分数 x = a + 1/(a + 1/(a + …)) 的值。即求解方程 x = a + 1/x 的正根。

AC代码 牛顿迭代法

x=a+1a+1a+1a+x=a + \frac{1}{a + \frac{1}{a + \frac{1}{a + \cdots}}}

因为是无限的递归,可以把a下面的看成一个整体,其实也是x,然后你就把这个问题转化成了求一元二次方程。

x=a+1xx = a + \frac{1}{x}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;

int T;
double a;

double solve(double a) {
// 使用公式直接计算连分数的解
return (a + sqrt(a * a + 4)) / 2;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);

cin >> T;
for(int _ = 1; _ <= T; _++) {
cin >> a;
double ans = solve(a);
cout << setprecision(24) << ans << endl;
}

return 0;
}
// AC https://ac.nowcoder.com/acm/contest/view-submission?submissionId=71962585

题目大意

有 N 个候选人争夺 M 个席位,共有 K 张票。目前已知每个候选人已获得的票数,以及剩余未投的票数。对于每个候选人,判断在最坏情况下(即剩余票数的分配对他最不利时),他是否还能通过获得一定数量的剩余票数来确保当选(进入前 M 名)。如果是,求出他至少需要再获得多少票;否则输出 -1。

AC代码

二分答案,检查器的设计重点在于
// 防止选用自己作为自己的竞争对手

1
2
if(idx-needOver-rectifyIdx<0)  // <0 说明人不够了,一共就没那么多人(n=m)
return true;

https://atcoder.jp/contests/abc373/submissions/58823933

二分检查器的核心行(加粗行)思路

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
const ll N=2e5+10;// 离散化
ll n,m,k,a[N],countedBallots,remain,li[N],ans[N],oa[N],sumA[N];
ll stand=1e18;
inline bool check(ll gainBallots,ll indexOfCandidate) {
ll currBallots=oa[indexOfCandidate]+gainBallots;
if(currBallots<stand)
return false;
ll idx=upper_bound(&a[1],&a[n+1],currBallots)-&a[0]-1;
ll diff=0;
ll overi=n+1-(upper_bound(&a[1],&a[n+1],currBallots)-&a[0]); // 返回的是比他大的数的索引,要包含这个数,n+1-。。。
ll needOver=m-overi,rectify=0,rectifyIdx=0;
// 防止选用自己作为自己的竞争对手
ll candidateIdx=li[indexOfCandidate]-1;
if(candidateIdx<=idx && candidateIdx>idx-needOver) {
rectify=oa[indexOfCandidate],rectifyIdx=1;
}
// hand 11 过不了原因是n=m,我没有考虑人不够的情况 不知道他给你指到哪里去了
if(idx-needOver-rectifyIdx<0) // <0 说明人不够了,一共就没那么多人(n=m)
return true;
diff=gainBallots+(currBallots+1)*needOver-(sumA[idx]-sumA[idx-needOver-rectifyIdx]-rectify);
// 就是下面循环的前缀和写法
// for(ll i=idx;cnt<needOver;i--) {
// diff+=(currBallots+1-a[i]);
// cnt++;
// }
if(diff>remain) // diff需要严格大于remain,不能等于,因为等于是可以超过的
return true;
return false;
}
inline ll bsearch(ll i) { // 二分模版
ll l=0,r=remain;
while(l<r) {
ll mid=l+r>>1;
if(check(mid,i))
r=mid; // mid是有可能是答案的,r=mid
else
l=mid+1;
}
return l;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++) {
cin>>a[i];
countedBallots+=a[i];
oa[i]=a[i];
}
remain=k-countedBallots;
sort(&a[1],&a[n+1]); // 还是得升序
for(int i=1;i<=n;i++) { // 前缀和
sumA[i]=sumA[i-1]+a[i];
}
for(int i=1;i<=n;i++) {
// 返回第一个比寻找元素大的元素 -&a[0]后就是索引 所以其实lower_bound()不太支持降序排列离散化
li[i]=upper_bound(&a[1],&a[n+1],oa[i])-&a[0];
if(n+1-li[i]<m)
stand=min(stand,oa[i]);
}
for(int i=1;i<=n;i++) {
if(oa[i]+remain<stand) { // 直接加都不行,跳过
ans[i]=-1;continue;
}
ans[i]=bsearch(i);
}
for(int i=1;i<=n;i++) {
cout<<ans[i]<<" ";
}
return 0;
}
// 只WA了一个点 https://atcoder.jp/contests/abc373/submissions/58815831
// AC https://www.luogu.com.cn/record/182236761
// AC https://atcoder.jp/contests/abc373/submissions/58823933

心路历程

我现在的想法就是我认为这就是博弈论,就是我投的票要竭力使这个人掉出m人的队列,而然后剩余的票一定是投在这个人身上
ans[i]=ceil((remain-diff)*1.0/(cnt+1));的来历

image

半成品代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include <algorithm>
#include <set>
#include <cmath>
using namespace std;
typedef long long ll;
const ll N=2e5+10;// 离散化
ll n,m,k,a[N],countedBallots,remain,li[N],ans[N],oa[N],toli[N]; // toli是通过a下标访问li的转介层
ll borderlineStand=0; // 临界标准,说人话就是不满足条件但最大的。
ll stand=1e18;// 标准与标准i,就是满足条件最小的值以及在a[i]的index
set<ll> vis;
void debug() {
cout<<"stand: "<<stand<<" borderlineStand:"<<borderlineStand<<endl;
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
for(int i=1;i<=n;i++) {
cout<<li[i]<<" ";
}
cout<<endl;
for(int i=1;i<=n;i++) {
cout<<toli[i]<<" ";
}
cout<<endl<<"ans: ";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++) {
cin>>a[i];
countedBallots+=a[i];
}
remain=k-countedBallots;
for(int i=1;i<=n;i++) {
oa[i]=a[i];
}
sort(&a[1],&a[n+1]); // 还是得升序
for(int i=1;i<=n;i++) {
// 返回第一个比寻找元素大的元素 -&a[0]后就是索引 所以其实lower_bound()不太支持降序排列离散化
li[i]=upper_bound(&a[1],&a[n+1],oa[i])-&a[0];
if(n+1-li[i]<m)
stand=min(stand,oa[i]);
else
borderlineStand=max(borderlineStand,oa[i]);
}
for(int i=1;i<=n;i++) {
if(vis.count(oa[i]))
continue;
ll s=lower_bound(&a[1],&a[n+1],oa[i])-&a[0];
ll e=upper_bound(&a[1],&a[n+1],oa[i])-&a[0];
vis.insert(oa[i]);
for(ll j=s;j<e;j++) {
toli[j]=i; // toli 也可以认为是排序后元素位置->排序前元素位置
}
}
for(int i=1;i<=n;i++) {
ll ia=li[i]-1,vi=oa[i],overi=n+1-li[i];
if(oa[i]+remain<stand) {
ans[i]=-1;continue;
}
if(oa[i]>=stand) { // 这里是原来就已经选上,如何在剩余选票中保住位置
ll diff=0,cnt=0,needOver=m-overi;
for(ll j=ia-1;cnt<needOver;j--) // 当cnt=needOver时,说明已经加完,此时不应该再加了
diff+=vi+1-a[j],cnt+=1; //要超到i前面去,至少要比i的值大1吧
if(remain-diff<0) {
ans[i]=0;continue;
}else { // remain-i-(diff+cnt*i)=0 (方程式,i为需要的)
ans[i]=ceil((remain-diff)*1.0/(cnt+1));
}
}
}
debug();
for(int i=1;i<=n;i++) {
cout<<ans[i]<<" ";
}
return 0;
}

https://atcoder.jp/contests/abc373/submissions/58808859

五彩缤纷的提交

我做着做着发现完全不需要分两种情况,这个提交是分两种情况的。

然后check()可以用前缀和优化

像这道题一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <iostream>
#include <algorithm>
#include <set>
#include <cmath>
using namespace std;
typedef long long ll;
const ll N=2e5+10;// 离散化
ll n,m,k,a[N],countedBallots,remain,li[N],ans[N],oa[N],sumA[N];
ll stand=1e18;// 标准与标准i,就是满足条件最小的值以及在a[i]的index
set<ll> vis;
inline bool check(ll gainBallots,ll indexOfCandidate) {
ll currBallots=oa[indexOfCandidate]+gainBallots;
if(currBallots<stand)
return false;
ll idx=upper_bound(&a[1],&a[n+1],currBallots)-&a[0]-1;
ll overi=n+1-(upper_bound(&a[1],&a[n+1],currBallots)-&a[0]); // 返回的是比他大的数的索引,要包含这个数,n+1-。。。
ll needOver=m-overi;
ll cnt=0,diff=gainBallots;
for(ll i=idx;cnt<needOver;i--) {
diff+=(currBallots+1-a[i]);
cnt++;
}
if(diff>remain) // diff需要严格大于remain,不能等于,因为等于是可以超过的
return true;
return false;
}
inline ll bsearch(ll i) { // 二分模版
ll l=stand-oa[i],r=remain;
while(l<r) {
ll mid=l+r>>1;
if(check(mid,i))
r=mid; // mid是有可能是答案的,r=mid
else
l=mid+1;
}
return l;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=n;i++) {
cin>>a[i];
countedBallots+=a[i];
}
remain=k-countedBallots;
for(int i=1;i<=n;i++) {
oa[i]=a[i];
}
sort(&a[1],&a[n+1]); // 还是得升序
for(int i=1;i<=n;i++) {
sumA[i]=sumA[i-1]+a[i];
}
for(int i=1;i<=n;i++) {
// 返回第一个比寻找元素大的元素 -&a[0]后就是索引 所以其实lower_bound()不太支持降序排列离散化
li[i]=upper_bound(&a[1],&a[n+1],oa[i])-&a[0];
if(n+1-li[i]<m)
stand=min(stand,oa[i]);
}
for(int i=1;i<=n;i++) {
if(vis.count(oa[i]))
continue;
ll s=lower_bound(&a[1],&a[n+1],oa[i])-&a[0];
ll e=upper_bound(&a[1],&a[n+1],oa[i])-&a[0];
vis.insert(oa[i]);
}
for(int i=1;i<=n;i++) {
ll ia=li[i]-1,vi=oa[i],overi=n+1-li[i];
if(oa[i]+remain<stand) { // 直接加都不行,跳过
ans[i]=-1;continue;
}
if(oa[i]>=stand) { // 这里是原来就已经选上,如何在剩余选票中保住位置
ll diff=0,cnt=0,needOver=m-overi;
for(ll j=ia-1;cnt<needOver;j--) // 当cnt=needOver时,说明已经加完,此时不应该再加了
diff+=vi-a[j],cnt+=1; //要超到i前面去,至少要比i的值大1吧
if(remain-diff<0) {
ans[i]=0;continue;
}else { // remain-i-(diff+cnt*i)=0 (方程式,i为需要的)
ans[i]=ceil((remain-diff)*1.0/(cnt+1));
}
}else { // 这里是原来没选上,如何在剩余选票中抢到位置
ans[i]=bsearch(i);
}
}
for(int i=1;i<=n;i++) {
cout<<ans[i]<<" ";
}
return 0;
}

题目大意

给定一棵树,边有边权。需要在树上选一条长度不超过 s 的路径(核心路径),使得树上所有点到这条路径的距离的最大值最小。

https://www.luogu.com.cn/record/178248030 50pts TLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include <iostream>
#include <vector>
#include <deque>
#include <unordered_map>
#include <string>
const int maxn=3e5+7;
using namespace std; // 记录路径 支路路径长度 记录直径及其先后顺序
int n,s,u,v,w,maxDis,disNode,A,B,pa[maxn],dist[maxn],upDis,upNode,I,cnt,flag[maxn],ldis[maxn],ans=2e9+7;
// ldis两端路径长度(可通过直径减去其算出另外一端)
int ecc,Lcalibre,Rcalibre,lGive,rGive,L,R,mC;
int sDis[maxn],eDis[maxn],S,E; // 当点为start || end 时对长度的贡献
bool vis[maxn];
vector<int> g[maxn];
vector<int> calibre;
deque <int> q;
unordered_map<string,int> l;
void dfs(int fa,int dis) {
for(vector<int>::iterator it=g[fa].begin();it!=g[fa].end();it++) {
if(vis[*it]==false && flag[*it]==0) {
vis[*it]=true,pa[*it]=fa;
string fait=to_string(fa)+'_'+to_string(*it);
if(dis+l[fait]>maxDis)
disNode=*it,maxDis=dis+l[fait];
dfs(*it,dis+l[fait]);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>s;
for(int i=1;i<=n-1;i++) {
cin>>u>>v>>w;
string uv=to_string(u)+'_'+to_string(v);
string vu=to_string(v)+'_'+to_string(u);
l[uv]=w;
l[vu]=w;
g[u].push_back(v);g[v].push_back(u);
}
vis[1]=true;
dfs(1,0);
// 复位以及存储 两遍dfs
A=disNode;
fill(&vis[1],&vis[n+1],false);
maxDis=0,vis[A]=true,pa[A]=A;
dfs(A,0);
B=disNode;
I=B;
while(true) {
flag[I]=++cnt;
string IupNode=to_string(I)+'_'+to_string(upNode);
ldis[I]=upDis+l[IupNode];
upDis=ldis[I],upNode=I;
calibre.push_back(I);
if(I==pa[I])
break;
I=pa[I];
}
mC=ldis[A]; // mC即直径长度
fill(&vis[1],&vis[n+1],false);
for(int i=0;i<calibre.size();i++) {
maxDis=0,vis[calibre[i]]=true;
dfs(calibre[i],0);
dist[calibre[i]]=maxDis;
// cout<<calibre[i]<<" ";
}
// cout<<endl;
// 填充sdis 通过递推
for(int i=0;i<calibre.size();i++) {
int ci=calibre[i],ci_1= i+1<calibre.size() ? calibre[i+1]:calibre[i];
sDis[ci]=max(dist[ci],S);
string ciCi_1=to_string(ci)+'_'+to_string(ci_1);
S=max(S+l[ciCi_1],dist[ci]);
}
// 填充edis 通过递推
for(int i=calibre.size()-1;i>=0;i--) {
int ci=calibre[i],ci_1= i-1>=0 ? calibre[i-1]:calibre[i];
eDis[ci]=max(dist[ci],E);
string ciCi_1=to_string(ci)+'_'+to_string(ci_1);
E=max(E+l[ciCi_1],dist[ci]);
}
// 双指针+单调队列(deque实现,因为要从后面出队,里面存的实际上是编号)
while(L<=R && R<calibre.size()) {
int cl=calibre[L],cr=calibre[R];
if(ldis[cr]-ldis[cl]>s) {
if(q.front()<L+1) q.pop_front(); // 判断队首元素是否过期
L++;
continue;
}
while(!q.empty() && dist[cr]>dist[calibre[q.back()]]) {
q.pop_back();
}
q.push_back(R);
ecc=max(sDis[cl],eDis[cr]);
ecc=max(ecc,dist[calibre[q.front()]]);
ans=min(ecc,ans);
// cout<<L<<" "<<R<<" "<<ans<<" "<<ecc<<endl;
R++;
}
cout<<ans<<endl;
// debug
// for(int i=1;i<=n;i++) {
// cout<<sDis[i]<<" ";
// }
// cout<<endl;
// for(int i=1;i<=n;i++) {
// cout<<eDis[i]<<" ";
// }
return 0;
}

AC代码与思路

反向建边,然后注意bfs的逻辑一定要清楚,要以什么为当前点,什么为父节点,不要混淆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
typedef long long ll;
const ll MAXN=2e5+10;
ll n,m,ans[MAXN];
bool vis[MAXN];
struct edge {
ll u,v,w;
};
vector<edge> e[MAXN];
edge make_edge(ll u,ll v,ll w) {
edge a;a.u=u;a.v=v;a.w=w;
return a;
}
deque <edge> q;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++) {
ll ui,vi,wi;
cin>>ui>>vi>>wi;
e[ui].push_back(make_edge(ui,vi,wi)); // 其实这里理论上只需要存vi,wi 但因为懒得改就多存了,1024MB用不掉
e[vi].push_back(make_edge(vi,ui,-wi)); // 反向建边,精髓
}
for(int i=1;i<=n;i++) {
if(vis[i])
continue;
q.clear(); // i为当前点
q.push_back(make_edge(0,i,0)); // 该连通块起始点可以认为是从i(该连通块起始点)到到0点
while (!q.empty()) {
ll ui=q.front().u,vi=q.front().v,wi=q.front().w;
q.pop_front();
if(vis[vi])
continue;
for(int j=0;j<e[vi].size();j++) {
if(!vis[e[vi][j].v])
q.push_back(make_edge(e[vi][j].u,e[vi][j].v,e[vi][j].w));
}
vis[vi]=true;
ans[vi]=ans[ui]+wi;
}
}
for(int i=1;i<=n;i++) {
cout<<ans[i]<<" ";
}
cout<<endl;
return 0;
}
//AC https://atcoder.jp/contests/abc373/submissions/58587858
//AC https://www.luogu.com.cn/record/181055323

心路历程

这个代码稍微有点搞笑,样例都没过

https://atcoder.jp/contests/abc373/submissions/58559275

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
const ll MAXN=2e5+10;
ll n,m,ans[MAXN];
struct edge {
ll u,v,w;
};
vector<edge> e[MAXN];
edge make_edge(ll u,ll v,ll w) {
edge a;a.u=u;a.v=v;a.w=w;
return a;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++) {
ll ui,vi,wi;
cin>>ui>>vi>>wi;
e[ui].push_back(make_edge(ui,vi,wi)); // 其实这里理论上只需要存vi,wi 但因为懒得改就多存了,1024MB用不掉
}
for(int i=1;i<=n;i++) {
while (!e[i].empty()) {
ll ui=e[i].back().u,vi=e[i].back().v,wi=e[i].back().w;
e[i].pop_back();
ans[vi]=ans[ui]+wi;
}
}
for(int i=1;i<=n;i++) {
cout<<ans[i]<<" ";
}
cout<<endl;
return 0;
}

我仔细想了一下这个问题,发现如果不知道次序就很难解,于是我仔细开始想拓扑排序的可能性。

想不到确实不是。

这句第一个条件其实是没有重复路,二条件是规定有向图,没提到过无环

image

而且就算知道次序也会坐牢

image

可以走反边5→2→1→3→6(其中 2→1 以及 3→6 是反边)