2023-2024 ACM-ICPC Latin American Regional Programming Contest

发布时间 2024-01-10 20:30:39作者: 空気力学の詩

Preface

这场终于找回一点感觉了,总体来说虽然有点唐但打的还不错

开场签到有点磕磕绊绊在加上前面只有我一个人在读题,因此出的比较慢

中期开始慢慢发力后祁神也放下了明天的数电考试复习过来助阵,轻松秒了一道几何

最后已经写了一个细节题L的徐神最后1h被另一个细节题G搞得屡次破防,最后也是没能调出,9题收场

比较可惜的是最后其实我搞出来了一个K的做法(基于随机),赛后写了下发现确实能过,但比赛时肯定选择冲更为稳妥的G,决策还是没问题的


A. Analyzing Contracts

防AK题,弃


B. Blackboard Game

被签到博弈腐乳辣

不难发现若每个数出现的次数都是\(3\)的倍数则后手必胜,否则先手必胜

#include<cstdio>
#include<iostream>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
int n,x; map <int,int> c;
int main()
{
	//freopen("B.in","r",stdin); freopen("B.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=3*n;++i) scanf("%d",&x),++c[x];
	bool flag=1; for (auto [_,t]:c) if (t%3!=0) flag=0;
	return puts(flag?"N":"Y"),0;
}

C. Candy Rush

刚开始想了个数据分治的\(O(n\sqrt{n\log n})\)的做法,后面发现直接用Hash来做到\(O(n\log n)\)

区间问题很容易想到前缀和,我们这里用一个\(k\)维向量来表示前缀和,每一位上的数表示这一段前缀该值出现的次数

不难发现一个区间\([l,r]\)合法的条件是\(pre_r-pre_{l-1}=k\times (1,1,1,\cdots)\),其中\(k\)是任意自然数

而由于\(pre\)里所有数始终是不降的,我们可以将\(pre_i\)中的每一维都减去其中最小的数,这样就想到与要找\(pre_{l-1}=pre_r\)

直接做显然很爆炸,但仔细一想我们可以借鉴字符串Hash的思路,直接给这个\(k\)维向量搞一个Hash值

由于这题的修改操作很简单,因此可以轻松维护,至于所有数的最小值可以用multiset乱搞一下

#include<cstdio>
#include<iostream>
#include<map>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
const int N=400005,mod1=998244353,mod2=1e9+7;
int n,k,c[N],bkt[N],ans; map <LL,int> fst; multiset <int> s;
struct Hasher
{
	int x,y;
	inline Hasher(CI X=0,CI Y=0)
	{
		x=X; y=Y;
	}
	inline LL get_val(void)
	{
		return ((1LL*x)<<31LL)|(1LL*y);
	}
	friend inline bool operator == (const Hasher& A,const Hasher& B)
	{
		return A.x==B.x&&A.y==B.y;
	}
	friend inline Hasher operator + (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
	}
	friend inline Hasher operator - (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
	}
	friend inline Hasher operator * (const Hasher& A,const Hasher& B)
	{
		return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
	}
}pw[N],cur,norm; const Hasher seed=Hasher(31,131);
int main()
{
	//freopen("C.in","r",stdin); freopen("C.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&c[i]);
	for (pw[0]=Hasher(1,1),i=1;i<=k;++i) s.insert(0),pw[i]=pw[i-1]*seed,norm=norm+pw[i];
	for (fst[0]=0,i=1;i<=n;++i)
	{
		s.erase(s.find(bkt[c[i]]));
		s.insert(++bkt[c[i]]); cur=cur+pw[c[i]];
		int min_t=*s.begin();
		Hasher tmp=cur-norm*Hasher(min_t,min_t);
		if (fst.count(tmp.get_val())) ans=max(ans,i-fst[tmp.get_val()]);
		else fst[tmp.get_val()]=i;
	}
	return printf("%d",ans),0;
}

D. Deciphering WordWhiz

签到模拟题,难点在于阅读题意

#include<cstdio>
#include<iostream>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int n,m,ext[30]; string base,words[N];
int main()
{
	//freopen("D.in","r",stdin); freopen("D.out","w",stdout);
	RI i,j; for (cin>>n,i=1;i<=n;++i) cin>>words[i]; base=words[1];
	for (i=0;i<5;++i) ext[base[i]-'a']=1;
	for (cin>>m,i=1;i<=m;++i)
	{
		string res; cin>>res; int ans=0;
		auto comp=[&](string s)
		{
			string ret=""; for (RI i=0;i<5;++i)
			if (s[i]==base[i]) ret+="*";
			else if (ext[s[i]-'a']) ret+="!";
			else ret+="X"; return ret;
		};
		for (j=1;j<=n;++j) if (comp(words[j])==res) ++ans;
		cout<<ans<<endl;
	}
	return 0;
}

E. Elevated Profits

题目都没看,防AK题+1


F. Forward and Backward

徐神开场写的一个Easy题,我题目都没看

#include <bits/stdc++.h>

using llsi = long long signed int;

int check(llsi n, llsi base) {
	std::vector<llsi> num;
	while(n) num.push_back(n % base), n /= base;
	for(size_t i = 0; i < num.size(); ++i) if(num[i] != num[num.size() - i - 1]) return 0;
	return 1;
}

int main() {
	llsi n;
	std::cin >> n;
	
	std::vector<llsi> ans;
	llsi ue = std::min(n, 1000000ll);
	for(llsi i = ue; i >= 2; --i) if(check(n, i)) ans.push_back(i);
	
	for(llsi i = 1; i < ue; ++i) {
		llsi base = (n - i) / i;
		if(base && base > 1000000ll && n % base == i && n / base == i)
			ans.push_back(base);
	}
	
	std::sort(ans.begin(), ans.end());
	
	if(ans.size()) for(size_t i = 0; i < ans.size(); ++i) std::cout << ans[i] << char(i == ans.size() - 1 ? 10 : 32);
	else                                                  std::cout << "*\n";
	
	return 0;
}

G. GPS on a Flat Earth

这题思路很简单,就是不断地将所有斜45度的正方形求交,最后剩下来的点集就是答案

但因为一些不可名状的细节这题很容易写挂,徐神应该还在补这个题代码就先坑着


H. Health in Hazard

祁神知道我们写不来几何特意跑过来秒了这道题

这题我题意都不知道,做法是二分+半平面交,感觉这两个算法结合的题目确实比较多

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define LD long double

const LD eps = 1e-8;
const LD INF = 1e9;
int sgn(LD x){return fabs(x)<=eps ? 0 : (x > eps ? 1 : -1);}

struct Pt{
	LD x, y;
	Pt operator-(Pt b)const{return Pt{x-b.x, y-b.y};}
	Pt operator+(Pt b)const{return Pt{x+b.x, y+b.y};}
	Pt operator*(LD b)const{return Pt{x*b, y*b};}
	LD crs(Pt b)const{return x*b.y-y*b.x;}	
	LD len()const{return sqrtl(x*x+y*y);}
	int quad()const{
		if (sgn(x)>0 && sgn(y)>=0){return 1;} if (sgn(x)<=0 && sgn(y)>0){return 2;}
		if (sgn(x)<0 && sgn(y)<=0){return 3;} if (sgn(x)>=0 && sgn(y)<0){return 4;}
	}
};

struct Line{
	Pt p, v;
	Line(){}
	Line(Pt _p, Pt _v):p(_p), v(_v){}
	bool operator<(Line b)const{
		if (v.quad()!=b.v.quad()) return v.quad()<b.v.quad();
		else{
			LD crs = v.crs(b.v);
			return sgn(crs)!=0 ? sgn(crs)>0 : sgn(v.crs(b.p-p))< 0;	
		}
	}
};
const Line bd[4] = {
	Line(Pt{-INF, -INF}, Pt{1, 0}), Line(Pt{INF, -INF}, Pt{0, 1}), 
	Line(Pt{INF, INF}, Pt{-1, 0}), Line(Pt{-INF, INF}, Pt{0, -1}), 
};
Pt pt_l_l(Line a, Line b){return a.p+a.v*(b.v.crs(a.p-b.p)/(a.v.crs(b.v)));}

bool halfPlane(vector<Line> &ln, vector<Pt> &res){
	for (int i=0; i<4; ++i) ln.push_back(bd[i]);
	sort(ln.begin(), ln.end());
	
	int cur=0;
	for (int i=1; i<(int)ln.size(); ++i){
		if (sgn(ln[cur].v.crs(ln[i].v))!=0) ln[++cur]=ln[i];
	}
	ln.erase(ln.begin()+cur+1, ln.end());
	
	int sz = ln.size();
	vector<Line> Ql(sz+5);
	vector<Pt> Qp(sz+5);
	int frl=0, edl=-1, frp=0, edp=-1;
	
	Ql[++edl] = ln[0];
	for (int i=1; i<sz; ++i){
		while (frp<=edp && sgn(ln[i].v.crs(Qp[edp]-ln[i].p))<=0) --edp, --edl;
		while (frp<=edp && sgn(ln[i].v.crs(Qp[frp]-ln[i].p))<=0) ++frp, ++frl;
		Qp[++edp] = pt_l_l(ln[i], Ql[edl]);
		Ql[++edl] = ln[i];
	}
	while (frp<=edp && sgn(Ql[frl].v.crs(Qp[edp]-Ql[frl].p)<=0)) --edp, --edl;
	if (frl<edl) Qp[++edp] = pt_l_l(Ql[edl], Ql[frl]);
	
	if (edp-frp<2) return false;
	else{
		res.clear();
		res.insert(res.end(), Qp.begin()+frp, Qp.begin()+edp+1);
		return true;	
	}
}

const int N = 2e5+5;
int n; LD D;
Line ln[N];

bool check(int x){
	vector<Line> vec;
	for (int i=1; i<=x; ++i) vec.push_back(ln[i]);
	vector<Pt> ch;
	halfPlane(vec, ch);
	int sz=ch.size();
	bool ok=false;
	for (int i=0; i<sz; ++i){
//		printf("i=%lld (%Ld %Ld)\n", i, ch[i].x, ch[i].y);
		if (sgn((ch[i]).len()-D)>0){
			ok=true;
			break;
		} 
	}	
	return ok;
}

signed main(){
	scanf("%lld%Lf", &n, &D);
	for (int i=1; i<=n; ++i){
		int x1, y1, x2, y2; scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
		Pt a=Pt{x1, y1}, b=Pt{x2, y2};
		if (sgn((b-a).crs(Pt{0, 0}-a)<0)) swap(a, b);	
		ln[i] = Line(a, b-a);
	}
//	for (int i=1; i<=n; ++i){
//		printf("i=%lld (%Lf %Lf)(%Lf %Lf)\n", i, ln[i].p.x, ln[i].p.y, ln[i].v.x, ln[i].v.y);
//	}
	if (check(n)){
		printf("*\n");
	}else{
		int L=1, R=n;
		while (L<R){
			int M = L+(R-L)/2;
			if (check(M)) L=M+1;
			else R=M;	
		}
		printf("%lld\n", L);
	}
	return 0;	
}

I. Inversions

一个Easy数数题,根据逆序对的统计方法手玩一下推个式子即可

对于原来的某个元素\(s_i\),设原串中在它之后并且小于\(s_i\)的元素个数为\(A\),整个原串小于\(s_i\)的元素个数为\(B\)

\(s_i\)的贡献就是首项为\(A\),公差为\(B\),项数为\(n\)的等差数列之和

#include<cstdio>
#include<iostream>
#include<cstring>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=1e9+7,inv2=(mod+1)/2;
int n,t,bkt[30],A[N],B[N],ans; char s[N];
signed main()
{
	//freopen("I.in","r",stdin); freopen("I.out","w",stdout);
	RI i,j; for (scanf("%s%lld",s+1,&t),n=strlen(s+1),i=n;i>=1;--i)
	{
		for (j=s[i]-'a'-1;j>=0;--j) A[i]+=bkt[j]; ++bkt[s[i]-'a'];
	}
	for (i=1;i<=n;++i)
	{
		for (j=s[i]-'a'-1;j>=0;--j) B[i]+=bkt[j];
		(ans+=(2LL*A[i]+(t-1)*B[i])%mod*(t%mod)%mod*inv2%mod)%=mod;
	}
	return printf("%lld",ans),0;
}

J. Journey of the Robber

本来想了个比较复杂的点分树做法,后面在徐神提醒下发现定序之后就很简单,遂爬上去20min写完一发入魂

首先树上距离经典考虑点分治,但这题我们不直接来搞而是先把点分树建出来

考虑按编号从大到小依次将点激活,我们询问一个点的时候只要沿着这个点在点分树上一直向上跳,在每个点处查询到子树内距离最近的点即可

由于这题问的是最小因此不需要容斥来自自己子树的也不会错,因此修改也是类似地往上更新即可

如果用\(O(1)\)求LCA的话这题复杂度就是一个\(\log\)的,但时限4.5s不用白不用直接倍增走起

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
const int N=100005,INF=1e9;
typedef pair <int,int> pi;
int n,x,y,ans[N]; vector <int> v[N];
class Tree_Solver
{
	private:
		int anc[N][20],dep[N];
	public:
		inline void DFS(CI now=1,CI fa=0)
		{
			anc[now][0]=fa; dep[now]=dep[fa]+1;
			for (RI i=0;i<18;++i) if (anc[now][i]) anc[now][i+1]=anc[anc[now][i]][i]; else break;
			for (auto to:v[now]) if (to!=fa) DFS(to,now);
		}
		inline int getLCA(int x,int y)
		{
			RI i; if (dep[x]<dep[y]) swap(x,y);
			for (i=19;i>=0;--i) if (dep[anc[x][i]]>=dep[y]) x=anc[x][i];
			if (x==y) return x;
			for (i=19;i>=0;--i) if (anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];
			return anc[x][0];
		}
		inline int getdis(CI x,CI y)
		{
			return dep[x]+dep[y]-2*dep[getLCA(x,y)];
		}
}T;
namespace Point_Divide_Tree
{
	int rt,ots,sz[N],mx[N],fa[N]; pi mi[N]; bool vis[N];
	inline void getrt(CI now=1,CI fa=0)
	{
		sz[now]=1; mx[now]=0; for (auto to:v[now])
		if (to!=fa&&!vis[to]) getrt(to,now),sz[now]+=sz[to],mx[now]=max(mx[now],sz[to]);
		if (mx[now]=max(mx[now],ots-sz[now]),mx[now]<mx[rt]) rt=now;
	}
	inline void solve(int now)
	{
		vis[now]=1; for (auto to:v[now]) if (!vis[to])
		mx[rt=0]=INF,ots=sz[to],getrt(to,now),fa[rt]=now,solve(rt);
	}
	inline void divide(CI n)
	{
		for (RI i=1;i<=n;++i) mi[i]=pi(INF,0);
		mx[rt=0]=INF; ots=n; getrt(); solve(rt);
		//for (RI i=1;i<=n;++i) if (fa[i]) printf("%d->%d\n",fa[i],i);
	}
}
using namespace Point_Divide_Tree;
int main()
{
	//freopen("J.in","r",stdin); freopen("J.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (T.DFS(),divide(n),i=n;i>=1;--i)
	{
		if (i!=n)
		{
			pi res=pi(INF,0);
			for (x=i;x;x=fa[x])	res=min(res,pi(mi[x].fi+T.getdis(i,x),mi[x].se));
			ans[i]=res.se;
		} else ans[i]=n;
		for (x=i;x;x=fa[x])	mi[x]=min(mi[x],pi(T.getdis(i,x),i));
	}
	for (i=1;i<=n;++i) printf("%d ",ans[i]);
	return 0;
}

K. Keen on Order

玄学题,如果比赛时候知道随机真能跑过我早就爬上去20min秒了这个题了

首先注意到当\(k\)较大时显然总存在某个排列满足题意,因为直觉感觉构造一个不存在某个排列不是其子序列的序列长度是\(O(k^2)\)级别的

因此不妨直接数据分治,当\(k>20\)时直接大力随机排列并检验,而\(k\le 20\)时可以用经典的状压来处理

\(f_{mask}\)表示已经在排列中填了状态为\(mask\)的数时匹配到原序列的最靠后的位置,转移的话非常trivial,这部分是\(O(2^k\times k)\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#define RI register int
#define CI const int&
using namespace std;
const int N=305;
int n,k,a[N],nxt[N][N],vis[N],f[1<<25],pre[1<<25];
int main()
{
	//freopen("K.in","r",stdin); freopen("K.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=0;i<=n+1;++i) for (j=0;j<=n+1;++j) nxt[i][j]=n+1;
	for (i=0;i<=n;++i) for (j=i+1;j<=n;++j) nxt[i][a[j]]=min(nxt[i][a[j]],j);
	if (k>=20)
	{
		int ans[N]; for (i=1;i<=k;++i) ans[i]=i;
		for (srand(time(0));;)
		{
			random_shuffle(ans+1,ans+k+1);
			int lst=0; for (i=1;i<=k;++i) lst=nxt[lst][ans[i]];
			if (lst>n)
			{
				for (i=1;i<=k;++i) printf("%d ",ans[i]); break;
			}
		}
		return 0;
	}
	for (i=0;i<(1<<k);++i) pre[i]=-1;
	for (f[0]=0,i=1;i<(1<<k);++i)
	{
		for (j=0;j<k;++j) if ((i>>j)&1)
		if (nxt[f[i^(1<<j)]][j+1]>f[i]) f[i]=nxt[f[i^(1<<j)]][j+1],pre[i]=j;
	}
	if (f[(1<<k)-1]<=n) return puts("*"),0;
	vector <int> ans;
	for (int S=(1<<k)-1;S;S=S^(1<<pre[S])) ans.push_back(pre[S]+1);
	reverse(ans.begin(),ans.end());
	for (auto x:ans) printf("%d ",x);
	return 0;
}

当然题解中给出了一种比较科学的构造方法,就是先找一个数使得其在原序列中的第一次出现位置尽可能靠后

然后第一个位置放这个数,接下来再找之前没出现过的并且在这之后的第一次出现位置尽可能靠后的数,以此类推

不难发现这样找出的序列长度是\(k+k-1+\cdots +1\)级别的,因此也可以通过

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=305;
int n,k,a[N],nxt[N][N],vis[N],f[1<<25],pre[1<<25];
int main()
{
	//freopen("K.in","r",stdin); freopen("K.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=0;i<=n+1;++i) for (j=0;j<=n+1;++j) nxt[i][j]=n+1;
	for (i=0;i<=n;++i) for (j=i+1;j<=n;++j) nxt[i][a[j]]=min(nxt[i][a[j]],j);
	if (k>=20)
	{
		int lst=0; for (i=1;i<=k;++i)
		{
			int mx=0,pos=-1;
			for (j=1;j<=k;++j) if (!vis[j]&&nxt[lst][j]>mx) mx=nxt[lst][j],pos=j;
			vis[pos]=1; lst=nxt[lst][pos]; printf("%d ",pos);
			if (lst>n) break;
		}
		for (i=1;i<=k;++i) if (!vis[i]) printf("%d ",i);
		return 0;
	}
	for (i=0;i<(1<<k);++i) pre[i]=-1;
	for (f[0]=0,i=1;i<(1<<k);++i)
	{
		for (j=0;j<k;++j) if ((i>>j)&1)
		if (nxt[f[i^(1<<j)]][j+1]>f[i]) f[i]=nxt[f[i^(1<<j)]][j+1],pre[i]=j;
	}
	if (f[(1<<k)-1]<=n) return puts("*"),0;
	vector <int> ans;
	for (int S=(1<<k)-1;S;S=S^(1<<pre[S])) ans.push_back(pre[S]+1);
	reverse(ans.begin(),ans.end());
	for (auto x:ans) printf("%d ",x);
	return 0;
}

L. Latam++

徐神我们的红太阳,开场秒了这个题,后面发现原来这是我们队这场写的过的人最少的一个题了

大致思路就是拿个栈来模拟,但需要讨论并且细节巨多,在数次破防后徐神总算是过了这个题

#include <bits/stdc++.h>

using llsi = long long signed int;

llsi ans = 0;

bool calc(const char *&s) {
	llsi res = 0;
	llsi stack = 0;
	bool is_legal = true;
	char pre = '(';
	
	if(*s == ')') return false;
	
	for(;; /* std::cerr << *s << ' ' << is_legal << char(10) */) switch(*s) {
		case '\0':
			// std::cerr << "FUCK\n";
			return false;
		case ')':
			s += 1;
//			std::cerr << "is_legal = " << is_legal << char(10);
//			std::cerr << "ans = " << ans << char(10);
			return is_legal && (islower(pre) || pre == ')');
		case '+':
		case '-':
		case '*':
		case '/':
			if(!islower(pre) && pre != ')')
				is_legal = false,
				stack = 0;
			pre = *s++;
			break;
		case '(':
			s += 1;
			if(islower(pre) || pre == ')') {
				is_legal = false;
				if(!calc(s)) stack = 0;
				else ans += stack = 1;
			} else {
				if(calc(s)) {
					ans += stack += 1;
				} else {
					is_legal = false;
					stack = 0;
				}
			}
			pre = ')';
			break;
		default:
			if(!islower(pre) &&
				pre != '+' &&
				pre != '-' &&
				pre != '*' && 
				pre != '/' &&
				pre != '(') stack = 0, is_legal = false;
			pre = *s++;
			ans += stack += 1;
	}
}

int main(void) {
	std::ios::sync_with_stdio(false);
	std::string s;
	std::getline(std::cin, s);
	
	const char *ss = s.c_str();
	while(*ss) {
		while(*ss && *ss == ')') ss += 1;
		calc(ss);
	}
	
	std::cout << ans << std::endl;
	return 0;
}

M. Meeting Point

小清新图论题,但感觉还是略显套路

首先以\(G\)为起点跑出一个最短路数组\(dis1\),再以\(P\)为起点,并且将\(G\)从图中删去后再跑出一个最短路数组\(dis2\)

考虑枚举最后的终点\(T\),首先显然要满足\(dis1_P=dis1_T\),不然\(G\)就不是\(P\to T\)的中点了

其次还要检验是否所有\(P\to T\)的最短路都经过\(G\),这点等价于\(dis2_T>dis1_P+dis1_T\)

用Dijkstra预处理后枚举每个点依次判断即可

#include<cstdio>
#include<iostream>
#include<queue>
#define int long long
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
typedef pair <int,int> pi;
const int N=100005,INF=1e18;
int n,m,S,G,x,y,z,cnt;
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        char Fin[S],*A,*B;
    public:
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        #undef tc
}F;
struct Graph
{
	vector <pi> v[N]; int dis[N],vis[N];
	inline void addedge(CI x,CI y,CI z)
	{
		v[x].push_back(pi(y,z)); v[y].push_back(pi(x,z));
	}
	inline void Dijkstra(CI s)
	{
		for (RI i=1;i<=n;++i) dis[i]=INF;
		priority_queue <pi> hp; hp.push(pi(dis[s]=0,s));
		while (!hp.empty())
		{
			int now=hp.top().second; hp.pop();
			if (vis[now]) continue; vis[now]=1;
			for (auto [to,w]:v[now])
			if (dis[to]>dis[now]+w) hp.push(pi(-(dis[to]=dis[now]+w),to));
		}
	}
}A,B;
signed main()
{
	//freopen("M.in","r",stdin); freopen("M.out","w",stdout);
	RI i; for (F.read(n),F.read(m),F.read(S),F.read(G),i=1;i<=m;++i)
	{
		F.read(x); F.read(y); F.read(z); A.addedge(x,y,z);
		if (x!=G&&y!=G) B.addedge(x,y,z);
	}
	for (A.Dijkstra(G),B.Dijkstra(S),i=1;i<=n;++i)
	if (A.dis[S]==A.dis[i]&&B.dis[i]>2LL*A.dis[S]) printf("%lld ",i),++cnt;
	if (!cnt) puts("*");
	return 0;
}

Postscript

拉美场果然适合给长时间没训练的人提信心